diff --git a/crates/haku/src/compiler.rs b/crates/haku/src/compiler.rs index a458c5e..c60fb11 100644 --- a/crates/haku/src/compiler.rs +++ b/crates/haku/src/compiler.rs @@ -361,52 +361,43 @@ 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 == "=" { - c.emit(Diagnostic::error( - src.ast.span(op), - "defs `a = b` may only appear at the top level", - )); - return Ok(()); + 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", + )); + 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) { + let argument_count = 2; + c.chunk.emit_opcode(Opcode::System)?; + c.chunk.emit_u8(index)?; + c.chunk.emit_u8(argument_count)?; + } else { + c.emit(Diagnostic::error( + src.ast.span(op), + "this binary operator is currently unimplemented", + )); + } + Ok(()) + } } - - compile_expr(c, src, left)?; - compile_expr(c, src, right)?; - if let Some(index) = system::resolve(SystemFnArity::Binary, name) { - let argument_count = 2; - c.chunk.emit_opcode(Opcode::System)?; - c.chunk.emit_u8(index)?; - c.chunk.emit_u8(argument_count)?; - } else { - c.emit(Diagnostic::error( - src.ast.span(op), - "this unary 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(()) } diff --git a/crates/haku/src/lexer.rs b/crates/haku/src/lexer.rs index 3ebff7f..781d7d4 100644 --- a/crates/haku/src/lexer.rs +++ b/crates/haku/src/lexer.rs @@ -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), diff --git a/crates/haku/src/parser.rs b/crates/haku/src/parser.rs index 3a99adf..fa1af6d 100644 --- a/crates/haku/src/parser.rs +++ b/crates/haku/src/parser.rs @@ -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), diff --git a/crates/haku/src/token.rs b/crates/haku/src/token.rs index 047caf0..96c8282 100644 --- a/crates/haku/src/token.rs +++ b/crates/haku/src/token.rs @@ -28,6 +28,8 @@ pub enum TokenKind { Greater, GreaterEqual, Not, + Dot, + Pipe, // Punctuation Newline,