Field access

This commit is contained in:
Laurenz 2022-04-19 16:37:16 +02:00
parent 255d4c620f
commit 7a2cc3e7d2
6 changed files with 80 additions and 27 deletions

View File

@ -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<Self::Output> {
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())?

View File

@ -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() } {

View File

@ -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

View File

@ -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,

View File

@ -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 => {}

24
tests/typ/code/field.typ Normal file
View File

@ -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}