Skip to content
This repository was archived by the owner on Jan 25, 2024. It is now read-only.

Commit fe0d8dc

Browse files
committed
evaluate arithmetic
1 parent e1ec7a0 commit fe0d8dc

File tree

10 files changed

+648
-21
lines changed

10 files changed

+648
-21
lines changed

Cargo.lock

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ version = "0.1.0"
1313
[dependencies]
1414
dirs = "2.0.2"
1515
env_logger = "0.7.1"
16+
gc = { version = "0.4", features = ["derive"] }
1617
lazy_static = "1.4"
1718
libc = "0.2.66"
1819
log = "0.4.8"

playground/math.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3 + (4 * 5)

src/eval.rs

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
use crate::parse::BinOpKind;
2+
use crate::scope::*;
3+
use crate::value::*;
4+
use crate::EvalError;
5+
use gc::{Finalize, Gc, GcCell, Trace};
6+
use rnix::TextRange;
7+
use std::borrow::Borrow;
8+
9+
type TreeResult = Result<Box<Tree>, EvalError>;
10+
11+
/// Used to lazily calculate the value of a Tree. This should be
12+
/// tolerant of parsing and evaluation errors from child Trees.
13+
#[derive(Debug, Clone, Trace, Finalize)]
14+
pub enum TreeSource {
15+
Literal {
16+
value: NixValue,
17+
},
18+
Paren {
19+
inner: TreeResult,
20+
},
21+
BinOp {
22+
op: BinOpKind,
23+
left: TreeResult,
24+
right: TreeResult,
25+
},
26+
BoolAnd {
27+
left: TreeResult,
28+
right: TreeResult,
29+
},
30+
BoolOr {
31+
left: TreeResult,
32+
right: TreeResult,
33+
},
34+
Implication {
35+
left: TreeResult,
36+
right: TreeResult,
37+
},
38+
UnaryInvert {
39+
value: TreeResult,
40+
},
41+
UnaryNegate {
42+
value: TreeResult,
43+
},
44+
}
45+
46+
/// Syntax node that has context and can be lazily evaluated.
47+
#[derive(Clone, Trace, Finalize)]
48+
pub struct Tree {
49+
#[unsafe_ignore_trace]
50+
pub range: Option<TextRange>,
51+
pub value: GcCell<Option<Gc<NixValue>>>,
52+
pub source: TreeSource,
53+
pub scope: Gc<Scope>,
54+
}
55+
56+
impl std::fmt::Debug for Tree {
57+
// The scope can be recursive, so we don't want to print it by default
58+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59+
f.debug_struct("Tree")
60+
.field("value", &self.value)
61+
.field("source", &self.source)
62+
.field("range", &self.range)
63+
.finish()
64+
}
65+
}
66+
67+
impl Tree {
68+
/// Lazily evaluate a Tree, caching its value
69+
pub fn eval(&self) -> Result<Gc<NixValue>, EvalError> {
70+
use std::ops::Deref;
71+
let value_borrow = self.value.borrow();
72+
if let Some(ref value) = value_borrow.deref() {
73+
Ok(value.clone())
74+
} else {
75+
drop(value_borrow);
76+
// We can later build a stack trace by wrapping errors here
77+
let value = self.eval_uncached()?;
78+
*self.value.borrow_mut() = Some(value.clone());
79+
Ok(value)
80+
}
81+
}
82+
83+
fn eval_uncached(&self) -> Result<Gc<NixValue>, EvalError> {
84+
match &self.source {
85+
TreeSource::Paren { inner } => inner.as_ref()?.eval(),
86+
TreeSource::Literal { value } => Ok(Gc::new(value.clone())),
87+
TreeSource::BoolAnd { left, right } => {
88+
if left.as_ref()?.eval()?.as_bool()? {
89+
right.as_ref()?.eval()
90+
} else {
91+
Ok(Gc::new(NixValue::Bool(false)))
92+
}
93+
}
94+
TreeSource::BoolOr { left, right } => {
95+
if !left.as_ref()?.eval()?.as_bool()? {
96+
right.as_ref()?.eval()
97+
} else {
98+
Ok(Gc::new(NixValue::Bool(true)))
99+
}
100+
}
101+
TreeSource::Implication { left, right } => {
102+
if !left.as_ref()?.eval()?.as_bool()? {
103+
Ok(Gc::new(NixValue::Bool(true)))
104+
} else {
105+
right.as_ref()?.eval()
106+
}
107+
}
108+
TreeSource::BinOp { op, left, right } => {
109+
use BinOpKind::*;
110+
use NixValue::*;
111+
let tmp1 = left.as_ref()?.eval()?;
112+
let tmp2 = right.as_ref()?.eval()?;
113+
let left = tmp1.borrow();
114+
let right = tmp2.borrow();
115+
let out = match (op, left, right) {
116+
(Add, Integer(x), Integer(y)) => Integer(x + y),
117+
(Add, Float(x), Float(y)) => Float(x + y),
118+
(Add, Integer(x), Float(y)) => Float(*x as f64 + y),
119+
(Add, Float(x), Integer(y)) => Float(x + *y as f64),
120+
121+
(Sub, Integer(x), Integer(y)) => Integer(x - y),
122+
(Sub, Float(x), Float(y)) => Float(x - y),
123+
(Sub, Integer(x), Float(y)) => Float(*x as f64 - y),
124+
(Sub, Float(x), Integer(y)) => Float(x - *y as f64),
125+
126+
(Mul, Integer(x), Integer(y)) => Integer(x * y),
127+
(Mul, Float(x), Float(y)) => Float(x * y),
128+
(Mul, Integer(x), Float(y)) => Float(*x as f64 * y),
129+
(Mul, Float(x), Integer(y)) => Float(x * *y as f64),
130+
131+
(Div, Integer(x), Integer(y)) => Integer(
132+
x.checked_div(*y)
133+
.ok_or_else(|| EvalError::Unexpected("division by zero".to_string()))?,
134+
),
135+
(Div, Float(x), Float(y)) => Float(x / y),
136+
(Div, Integer(x), Float(y)) => Float(*x as f64 / y),
137+
(Div, Float(x), Integer(y)) => Float(x / *y as f64),
138+
139+
// It seems like the Nix reference implementation compares floats exactly
140+
// https://github.com/NixOS/nix/blob/4a5aa1dbf6/src/libutil/comparator.hh#L26
141+
(Equal, Integer(x), Integer(y)) => Bool(x == y),
142+
(Equal, Float(x), Float(y)) => Bool(x == y),
143+
(Equal, Integer(x), Float(y)) => Bool(*x as f64 == *y),
144+
(Equal, Float(x), Integer(y)) => Bool(*x == *y as f64),
145+
(Equal, Bool(x), Bool(y)) => Bool(x == y),
146+
147+
(NotEqual, Integer(x), Integer(y)) => Bool(x != y),
148+
(NotEqual, Float(x), Float(y)) => Bool(x != y),
149+
(NotEqual, Integer(x), Float(y)) => Bool(*x as f64 != *y),
150+
(NotEqual, Float(x), Integer(y)) => Bool(*x != *y as f64),
151+
(NotEqual, Bool(x), Bool(y)) => Bool(x != y),
152+
153+
(Less, Integer(x), Integer(y)) => Bool(x < y),
154+
(Less, Float(x), Float(y)) => Bool(x < y),
155+
(Less, Integer(x), Float(y)) => Bool((*x as f64) < *y),
156+
(Less, Float(x), Integer(y)) => Bool(*x < *y as f64),
157+
158+
(LessOrEq, Integer(x), Integer(y)) => Bool(x <= y),
159+
(LessOrEq, Float(x), Float(y)) => Bool(x <= y),
160+
(LessOrEq, Integer(x), Float(y)) => Bool(*x as f64 <= *y),
161+
(LessOrEq, Float(x), Integer(y)) => Bool(*x <= *y as f64),
162+
163+
(Greater, Integer(x), Integer(y)) => Bool(x > y),
164+
(Greater, Float(x), Float(y)) => Bool(x > y),
165+
(Greater, Integer(x), Float(y)) => Bool(*x as f64 > *y),
166+
(Greater, Float(x), Integer(y)) => Bool(*x > (*y as f64)),
167+
168+
(GreaterOrEq, Integer(x), Integer(y)) => Bool(x >= y),
169+
(GreaterOrEq, Float(x), Float(y)) => Bool(x >= y),
170+
(GreaterOrEq, Integer(x), Float(y)) => Bool(*x as f64 >= *y),
171+
(GreaterOrEq, Float(x), Integer(y)) => Bool(*x >= (*y as f64)),
172+
173+
_ => {
174+
return Err(EvalError::Unexpected(format!(
175+
"{:?} {:?} {:?} unsupported",
176+
left, op, right
177+
)))
178+
}
179+
};
180+
Ok(Gc::new(out))
181+
}
182+
TreeSource::UnaryInvert { value } => {
183+
Ok(Gc::new(NixValue::Bool(!value.as_ref()?.eval()?.as_bool()?)))
184+
}
185+
TreeSource::UnaryNegate { value } => {
186+
Ok(Gc::new(match value.as_ref()?.eval()?.borrow() {
187+
NixValue::Integer(x) => NixValue::Integer(-x),
188+
NixValue::Float(x) => NixValue::Float(-x),
189+
_ => {
190+
return Err(EvalError::TypeError(
191+
"cannot negate a non-number".to_string(),
192+
))
193+
}
194+
}))
195+
}
196+
}
197+
}
198+
199+
/// Used for recursing to find the Tree at a cursor position
200+
pub fn children(&self) -> Vec<&Box<Tree>> {
201+
match &self.source {
202+
TreeSource::Paren { inner } => vec![inner],
203+
TreeSource::Literal { value: _ } => vec![],
204+
TreeSource::BinOp { op: _, left, right } => vec![left, right],
205+
TreeSource::BoolAnd { left, right } => vec![left, right],
206+
TreeSource::BoolOr { left, right } => vec![left, right],
207+
TreeSource::Implication { left, right } => vec![left, right],
208+
TreeSource::UnaryInvert { value } => vec![value],
209+
TreeSource::UnaryNegate { value } => vec![value],
210+
}
211+
.into_iter()
212+
.map(|x| x.as_ref())
213+
.filter_map(Result::ok)
214+
.collect()
215+
}
216+
}

src/lookup.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
use crate::{
2-
utils::{self, Datatype, Var},
3-
App,
4-
};
1+
use crate::{App, eval::Tree, utils::{self, Datatype, Var}};
52
use lsp_types::Url;
63
use rnix::{types::*, value::Value as ParsedValue, SyntaxNode};
74
use std::{
@@ -14,6 +11,8 @@ use lazy_static::lazy_static;
1411

1512
use std::{process, str};
1613
use regex;
14+
use gc::Gc;
15+
use crate::scope::Scope;
1716

1817
lazy_static! {
1918
static ref BUILTINS: Vec<String> = vec![
@@ -114,6 +113,7 @@ impl App {
114113
info.name,
115114
))
116115
}
116+
117117
pub fn scope_from_node(
118118
&mut self,
119119
file: &mut Rc<Url>,
@@ -147,14 +147,16 @@ impl App {
147147
let path = utils::uri_path(&file)?;
148148
node = match self.files.entry((**file).clone()) {
149149
Entry::Occupied(entry) => {
150-
let (ast, _code) = entry.get();
150+
let (ast, _code, _) = entry.get();
151151
ast.root().inner()?.clone()
152152
}
153153
Entry::Vacant(placeholder) => {
154154
let content = fs::read_to_string(&path).ok()?;
155155
let ast = rnix::parse(&content);
156156
let node = ast.root().inner()?.clone();
157-
placeholder.insert((ast, content));
157+
let gc_root = Gc::new(Scope::Root(path));
158+
let evaluated = Tree::parse(node.clone(), gc_root);
159+
placeholder.insert((ast, content, evaluated));
158160
node
159161
}
160162
};

0 commit comments

Comments
 (0)