From 7a2cc3e7d29d16c5cf9b5a93a688e14da93c8662 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 19 Apr 2022 16:37:16 +0200 Subject: [PATCH] Field access --- src/eval/mod.rs | 28 ++++++++++++++++++++-------- src/parse/mod.rs | 29 ++++++++++------------------- src/syntax/ast.rs | 21 +++++++++++++++++++++ src/syntax/highlight.rs | 1 + src/syntax/mod.rs | 4 ++++ tests/typ/code/field.typ | 24 ++++++++++++++++++++++++ 6 files changed, 80 insertions(+), 27 deletions(-) create mode 100644 tests/typ/code/field.typ diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 4a616b58b..d9651cced 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -215,6 +215,7 @@ impl Eval for Expr { Self::Array(v) => v.eval(ctx, scp).map(Value::Array), Self::Dict(v) => v.eval(ctx, scp).map(Value::Dict), Self::Group(v) => v.eval(ctx, scp), + Self::FieldAccess(v) => v.eval(ctx, scp), Self::FuncCall(v) => v.eval(ctx, scp), Self::MethodCall(v) => v.eval(ctx, scp), Self::Closure(v) => v.eval(ctx, scp), @@ -434,6 +435,23 @@ impl BinaryExpr { } } +impl Eval for FieldAccess { + type Output = Value; + + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + let object = self.object().eval(ctx, scp)?; + Ok(match object { + Value::Dict(dict) => dict.get(self.field().take()).at(self.span())?.clone(), + + v => bail!( + self.object().span(), + "cannot access field on {}", + v.type_name() + ), + }) + } +} + impl Eval for FuncCall { type Output = Value; @@ -442,14 +460,8 @@ impl Eval for FuncCall { let args = self.args().eval(ctx, scp)?; Ok(match callee { - Value::Array(array) => { - array.get(args.into_index()?).map(Value::clone).at(self.span())? - } - - Value::Dict(dict) => { - dict.get(args.into_key()?).map(Value::clone).at(self.span())? - } - + Value::Array(array) => array.get(args.into_index()?).at(self.span())?.clone(), + Value::Dict(dict) => dict.get(args.into_key()?).at(self.span())?.clone(), Value::Func(func) => { let point = || Tracepoint::Call(func.name().map(ToString::to_string)); func.call(ctx, args).trace(point, self.span())? diff --git a/src/parse/mod.rs b/src/parse/mod.rs index be947170b..c387de0c2 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -368,10 +368,9 @@ fn expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) -> ParseResult { }; loop { - // Exclamation mark, parenthesis or bracket means this is a function - // call. + // Parenthesis or bracket means this is a function call. if let Some(NodeKind::LeftParen | NodeKind::LeftBracket) = p.peek_direct() { - func_call(p, marker)?; + marker.perform(p, NodeKind::FuncCall, |p| args(p, true, true))?; continue; } @@ -379,8 +378,14 @@ fn expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) -> ParseResult { break; } - if p.at(&NodeKind::Dot) { - method_call(p, marker)?; + // Method call or field access. + if p.eat_if(&NodeKind::Dot) { + ident(p)?; + if let Some(NodeKind::LeftParen | NodeKind::LeftBracket) = p.peek_direct() { + marker.perform(p, NodeKind::MethodCall, |p| args(p, true, true))?; + } else { + marker.end(p, NodeKind::FieldAccess); + } continue; } @@ -715,20 +720,6 @@ fn content_block(p: &mut Parser) { }); } -/// Parse a function call. -fn func_call(p: &mut Parser, callee: Marker) -> ParseResult { - callee.perform(p, NodeKind::FuncCall, |p| args(p, true, true)) -} - -/// Parse a method call. -fn method_call(p: &mut Parser, marker: Marker) -> ParseResult { - marker.perform(p, NodeKind::MethodCall, |p| { - p.eat_assert(&NodeKind::Dot); - ident(p)?; - args(p, true, true) - }) -} - /// Parse the arguments to a function call. fn args(p: &mut Parser, direct: bool, brackets: bool) -> ParseResult { match if direct { p.peek_direct() } else { p.peek() } { diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 608566913..8af359bf2 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -237,6 +237,8 @@ pub enum Expr { Unary(UnaryExpr), /// A binary operation: `a + b`. Binary(BinaryExpr), + /// A field access: `properties.age`. + FieldAccess(FieldAccess), /// An invocation of a function: `f(x, y)`. FuncCall(FuncCall), /// An invocation of a method: `array.push(v)`. @@ -280,6 +282,7 @@ impl TypedNode for Expr { NodeKind::DictExpr => node.cast().map(Self::Dict), NodeKind::UnaryExpr => node.cast().map(Self::Unary), NodeKind::BinaryExpr => node.cast().map(Self::Binary), + NodeKind::FieldAccess => node.cast().map(Self::FieldAccess), NodeKind::FuncCall => node.cast().map(Self::FuncCall), NodeKind::MethodCall => node.cast().map(Self::MethodCall), NodeKind::ClosureExpr => node.cast().map(Self::Closure), @@ -310,6 +313,7 @@ impl TypedNode for Expr { Self::Group(v) => v.as_red(), Self::Unary(v) => v.as_red(), Self::Binary(v) => v.as_red(), + Self::FieldAccess(v) => v.as_red(), Self::FuncCall(v) => v.as_red(), Self::MethodCall(v) => v.as_red(), Self::Closure(v) => v.as_red(), @@ -789,6 +793,23 @@ pub enum Associativity { Right, } +node! { + /// A field access: `properties.age`. + FieldAccess: FieldAccess +} + +impl FieldAccess { + /// The object with the field. + pub fn object(&self) -> Expr { + self.0.cast_first_child().expect("field access is missing object") + } + + /// The name of the field. + pub fn field(&self) -> Ident { + self.0.cast_last_child().expect("field access call is missing name") + } +} + node! { /// An invocation of a function: `f(x, y)`. FuncCall: FuncCall diff --git a/src/syntax/highlight.rs b/src/syntax/highlight.rs index 004ff9576..10dfce69f 100644 --- a/src/syntax/highlight.rs +++ b/src/syntax/highlight.rs @@ -209,6 +209,7 @@ impl Category { NodeKind::Named => None, NodeKind::UnaryExpr => None, NodeKind::BinaryExpr => None, + NodeKind::FieldAccess => None, NodeKind::FuncCall => None, NodeKind::MethodCall => None, NodeKind::CallArgs => None, diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index d18b6a3d5..71646cb28 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -653,6 +653,8 @@ pub enum NodeKind { UnaryExpr, /// A binary operation: `a + b`. BinaryExpr, + /// A field access: `properties.age`. + FieldAccess, /// An invocation of a function: `f(x, y)`. FuncCall, /// An invocation of a method: `array.push(v)`. @@ -898,6 +900,7 @@ impl NodeKind { Self::Named => "named argument", Self::UnaryExpr => "unary expression", Self::BinaryExpr => "binary expression", + Self::FieldAccess => "field access", Self::FuncCall => "function call", Self::MethodCall => "method call", Self::CallArgs => "call arguments", @@ -1021,6 +1024,7 @@ impl Hash for NodeKind { Self::Named => {} Self::UnaryExpr => {} Self::BinaryExpr => {} + Self::FieldAccess => {} Self::FuncCall => {} Self::MethodCall => {} Self::CallArgs => {} diff --git a/tests/typ/code/field.typ b/tests/typ/code/field.typ new file mode 100644 index 000000000..b30d72e08 --- /dev/null +++ b/tests/typ/code/field.typ @@ -0,0 +1,24 @@ +// Test field access. +// Ref: false + +--- +#let dict = (nothing: "ness", hello: "world") +#test(dict.nothing, "ness") +{ + let world = dict + .hello + + test(world, "world") +} + +--- +// Error: 2-13 dictionary does not contain key: "invalid" +{(:).invalid} + +--- +// Error: 2-7 cannot access field on boolean +{false.ok} + +--- +// Error: 8-12 expected identifier, found boolean +{false.true}