implement .
and |
operators
`f . x` is the same as `f x`, and is mostly useful when used as an argument to another function call. this allows us to simulate record field syntax very well: `d.Num` is the same as `d Num`, but with high precedence `a |f b` is the same as `f a b` and effectively allows you to turn any function into an arithmetic operator. it's the main reason behind + - * / having the same precedence---they now chain very nicely with pipes closes #126 (`|` operator) closes #127 (`.` operator)
This commit is contained in:
parent
69cc34d07e
commit
ec7ee9626f
4 changed files with 125 additions and 50 deletions
|
@ -361,14 +361,18 @@ fn compile_binary<'a>(c: &mut Compiler<'a>, src: &Source<'a>, node_id: NodeId) -
|
|||
}
|
||||
let name = src.ast.span(op).slice(src.code);
|
||||
|
||||
if name == "=" {
|
||||
match name {
|
||||
":" => {
|
||||
// Invalid use of a def inside an expression.
|
||||
c.emit(Diagnostic::error(
|
||||
src.ast.span(op),
|
||||
"defs `a = b` may only appear at the top level",
|
||||
"defs `a: b` may only appear at the top level",
|
||||
));
|
||||
return Ok(());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
"." => compile_dot_call(c, src, left, right),
|
||||
"|" => compile_pipe_call(c, src, left, right),
|
||||
_ => {
|
||||
compile_expr(c, src, left)?;
|
||||
compile_expr(c, src, right)?;
|
||||
if let Some(index) = system::resolve(SystemFnArity::Binary, name) {
|
||||
|
@ -379,34 +383,21 @@ fn compile_binary<'a>(c: &mut Compiler<'a>, src: &Source<'a>, node_id: NodeId) -
|
|||
} else {
|
||||
c.emit(Diagnostic::error(
|
||||
src.ast.span(op),
|
||||
"this unary operator is currently unimplemented",
|
||||
"this binary operator is currently unimplemented",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_call<'a>(c: &mut Compiler<'a>, src: &Source<'a>, node_id: NodeId) -> CompileResult {
|
||||
let mut walk = src.ast.walk(node_id);
|
||||
let Some(func) = walk.node() else {
|
||||
return Ok(());
|
||||
};
|
||||
fn emit_nary_call<'a>(
|
||||
c: &mut Compiler<'a>,
|
||||
src: &Source<'a>,
|
||||
func: NodeId,
|
||||
argument_count: u8,
|
||||
) -> CompileResult {
|
||||
let name = src.ast.span(func).slice(src.code);
|
||||
|
||||
let mut argument_count = 0;
|
||||
while let Some(arg) = walk.node() {
|
||||
compile_expr(c, src, arg)?;
|
||||
argument_count += 1;
|
||||
}
|
||||
|
||||
let argument_count = u8::try_from(argument_count).unwrap_or_else(|_| {
|
||||
c.emit(Diagnostic::error(
|
||||
src.ast.span(node_id),
|
||||
"function call has too many arguments",
|
||||
));
|
||||
0
|
||||
});
|
||||
|
||||
if let (NodeKind::Ident, Some(index)) = (
|
||||
src.ast.kind(func),
|
||||
system::resolve(SystemFnArity::Nary, name),
|
||||
|
@ -423,6 +414,81 @@ fn compile_call<'a>(c: &mut Compiler<'a>, src: &Source<'a>, node_id: NodeId) ->
|
|||
c.chunk.emit_opcode(Opcode::Call)?;
|
||||
c.chunk.emit_u8(argument_count)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_call<'a>(c: &mut Compiler<'a>, src: &Source<'a>, node_id: NodeId) -> CompileResult {
|
||||
let mut walk = src.ast.walk(node_id);
|
||||
let Some(func) = walk.node() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let mut argument_count = 0;
|
||||
while let Some(arg) = walk.node() {
|
||||
compile_expr(c, src, arg)?;
|
||||
argument_count += 1;
|
||||
}
|
||||
|
||||
let argument_count = u8::try_from(argument_count).unwrap_or_else(|_| {
|
||||
c.emit(Diagnostic::error(
|
||||
src.ast.span(node_id),
|
||||
"function call has too many arguments",
|
||||
));
|
||||
0
|
||||
});
|
||||
|
||||
emit_nary_call(c, src, func, argument_count)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_dot_call<'a>(
|
||||
c: &mut Compiler<'a>,
|
||||
src: &Source<'a>,
|
||||
func: NodeId,
|
||||
right: NodeId,
|
||||
) -> CompileResult {
|
||||
compile_expr(c, src, right)?;
|
||||
emit_nary_call(c, src, func, 1)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_pipe_call<'a>(
|
||||
c: &mut Compiler<'a>,
|
||||
src: &Source<'a>,
|
||||
left: NodeId,
|
||||
call: NodeId,
|
||||
) -> CompileResult {
|
||||
if src.ast.kind(call) != NodeKind::Call {
|
||||
c.emit(Diagnostic::error(
|
||||
src.ast.span(call),
|
||||
"the right side of a pipe `|` must be a function call",
|
||||
));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut walk = src.ast.walk(call);
|
||||
let Some(func) = walk.node() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
compile_expr(c, src, left)?;
|
||||
let mut argument_count = 1;
|
||||
while let Some(arg) = walk.node() {
|
||||
compile_expr(c, src, arg)?;
|
||||
argument_count += 1;
|
||||
}
|
||||
|
||||
let argument_count = u8::try_from(argument_count).unwrap_or_else(|_| {
|
||||
c.emit(Diagnostic::error(
|
||||
src.ast.span(call),
|
||||
"function call has too many arguments",
|
||||
));
|
||||
0
|
||||
});
|
||||
|
||||
emit_nary_call(c, src, func, argument_count)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -215,6 +215,8 @@ fn token(l: &mut Lexer<'_>) -> (TokenKind, Span, bool) {
|
|||
'!' => one_or_two(l, TokenKind::Not, '=', TokenKind::NotEqual),
|
||||
'<' => one_or_two(l, TokenKind::Less, '=', TokenKind::LessEqual),
|
||||
'>' => one_or_two(l, TokenKind::Greater, '=', TokenKind::GreaterEqual),
|
||||
'.' => one(l, TokenKind::Dot),
|
||||
'|' => one(l, TokenKind::Pipe),
|
||||
|
||||
'\n' => return newline(l, has_left_space),
|
||||
'(' => one(l, TokenKind::LParen),
|
||||
|
|
|
@ -327,17 +327,20 @@ fn tighter(left: (TokenKind, Spaces), right: (TokenKind, Spaces)) -> Tighter {
|
|||
| TokenKind::LessEqual
|
||||
| TokenKind::Greater
|
||||
| TokenKind::GreaterEqual => 2,
|
||||
TokenKind::Plus | TokenKind::Minus | TokenKind::Star | TokenKind::Slash => 3,
|
||||
// 4: reserve for `.`
|
||||
|
||||
TokenKind::Plus
|
||||
| TokenKind::Minus
|
||||
| TokenKind::Star
|
||||
| TokenKind::Slash
|
||||
| TokenKind::Pipe => 3,
|
||||
|
||||
TokenKind::Dot => 4,
|
||||
_ if is_prefix_token((kind, spaces)) => 5,
|
||||
_ => return None, // not an infix operator
|
||||
};
|
||||
Some(match kind {
|
||||
// There are a few types of operators which are independent of tightness.
|
||||
|
||||
// For : and =, it does not matter if they're spelled one way or the other, because
|
||||
// there is only one way to use them (at the beginning of the expression).
|
||||
TokenKind::Colon | TokenKind::Equal => Tightness::Loose(index),
|
||||
// There are a few types of operators which work independent of tightness.
|
||||
TokenKind::Colon | TokenKind::Equal | TokenKind::Pipe => Tightness::Loose(index),
|
||||
|
||||
// For unary -, we treat it as having Tight spacing rather than Call, else it would
|
||||
// be allowed to begin function calls.
|
||||
|
@ -652,7 +655,9 @@ fn infix(p: &mut Parser, op: (TokenKind, Spaces)) -> NodeKind {
|
|||
| TokenKind::LessEqual
|
||||
| TokenKind::Greater
|
||||
| TokenKind::GreaterEqual
|
||||
| TokenKind::Colon => infix_binary(p, op),
|
||||
| TokenKind::Colon
|
||||
| TokenKind::Dot
|
||||
| TokenKind::Pipe => infix_binary(p, op),
|
||||
TokenKind::Minus if spaces.are_balanced() => infix_binary(p, op),
|
||||
|
||||
TokenKind::Equal => infix_let(p, op),
|
||||
|
|
|
@ -28,6 +28,8 @@ pub enum TokenKind {
|
|||
Greater,
|
||||
GreaterEqual,
|
||||
Not,
|
||||
Dot,
|
||||
Pipe,
|
||||
|
||||
// Punctuation
|
||||
Newline,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue