Skip to content

Commit

Permalink
parse binary expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
dhconnelly committed Nov 26, 2023
1 parent 7e3aa06 commit 69be2cc
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 44 deletions.
36 changes: 18 additions & 18 deletions src/analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,21 +68,6 @@ impl Analyzer {
Analyzer { ctx }
}

fn with_locals<S, T>(locals: T) -> Analyzer
where
S: ToString,
T: IntoIterator<Item = (S, Type)>,
{
let ctx = locals.into_iter().fold(
SymbolTable::default(),
|mut acc, (name, typ)| {
acc.def_local(name.to_string(), typ);
acc
},
);
Analyzer::with_context(ctx)
}

fn func(&mut self, f: Func) -> Result<TypedFunc> {
// TODO: look for return statements when we handle return types
self.ctx.push_frame();
Expand Down Expand Up @@ -213,6 +198,17 @@ mod test {
parser::Parser::new(scanner::scan(input))
}

fn with_locals<S: ToString, T: IntoIterator<Item = (S, Type)>>(
locals: T,
) -> Analyzer {
let mut ctx = SymbolTable::default();
ctx.push_frame();
locals
.into_iter()
.for_each(|(name, typ)| ctx.def_local(name.to_string(), typ));
Analyzer::with_context(ctx)
}

#[test]
fn test_hello() {
let input = b"
Expand Down Expand Up @@ -456,11 +452,15 @@ mod test {
#[test]
fn test_binary() {
let input: Vec<(Analyzer, &[u8])> = vec![
(Analyzer::with_locals(vec![("x", Type::Int)]), b"x + 7"),
(Analyzer::with_locals(vec![("x", Type::Str)]), b"x + \"s\""),
(Analyzer::with_locals(vec![("x", Type::Str)]), b"x + 7"),
(Analyzer::default(), b"14 + 7"),
(Analyzer::default(), b"\"a\" + \"b\""),
(with_locals(vec![("x", Type::Int)]), b"x + 7"),
(with_locals(vec![("x", Type::Str)]), b"x + \"s\""),
(with_locals(vec![("x", Type::Str)]), b"x + 7"),
];
let expected = vec![
Ok(Type::Int),
Ok(Type::Str),
Ok(Type::Int),
Ok(Type::Str),
Err(Error::InvalidOpTypes {
Expand Down
40 changes: 29 additions & 11 deletions src/ast.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::token::OpToken;
use crate::types::{FnType, Resolution, Type, Typed};
use std::fmt;

Expand Down Expand Up @@ -64,8 +65,8 @@ pub struct Call<AST: ASTSpec = UntypedAST> {
pub resolved_type: AST::CallCargo,
}

impl Call<UntypedAST> {
pub fn untyped(target: Expr, args: Vec<Expr>) -> Call<UntypedAST> {
impl Call {
pub fn untyped(target: Expr, args: Vec<Expr>) -> Call {
Call { target: Box::new(target), args, resolved_type: () }
}
}
Expand Down Expand Up @@ -113,8 +114,8 @@ pub struct Ident<AST: ASTSpec = UntypedAST> {
pub resolution: AST::IdentCargo,
}

impl Ident<UntypedAST> {
pub fn untyped<S: Into<String>>(name: S) -> Ident<UntypedAST> {
impl Ident {
pub fn untyped<S: Into<String>>(name: S) -> Ident {
Ident { name: name.into(), resolution: () }
}
}
Expand All @@ -133,6 +134,20 @@ impl Typed for TypedIdent {
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Op {
Add,
Sub,
Mul,
Div,
}

impl From<OpToken> for Op {
fn from(value: OpToken) -> Self {
match value {
OpToken::Plus => Op::Add,
OpToken::Minus => Op::Sub,
OpToken::Star => Op::Mul,
OpToken::Slash => Op::Div,
}
}
}

#[derive(Debug, PartialEq, Eq, Clone)]
Expand All @@ -143,6 +158,12 @@ pub struct Binary<AST: ASTSpec = UntypedAST> {
pub cargo: AST::BinaryCargo,
}

impl Binary {
pub fn untyped(op: Op, lhs: Expr, rhs: Expr) -> Binary {
Binary { op, lhs: Box::new(lhs), rhs: Box::new(rhs), cargo: () }
}
}

impl Typed for Binary<TypedAST> {
fn typ(&self) -> Type {
self.cargo.clone()
Expand Down Expand Up @@ -201,11 +222,8 @@ pub struct Param<AST: ASTSpec = UntypedAST> {
pub resolved_type: AST::ParamCargo,
}

impl Param<UntypedAST> {
pub fn untyped<S: Into<String>>(
name: S,
typ: TypeSpec,
) -> Param<UntypedAST> {
impl Param {
pub fn untyped<S: Into<String>>(name: S, typ: TypeSpec) -> Param {
Param { name: name.into(), typ, resolved_type: () }
}
}
Expand Down Expand Up @@ -274,13 +292,13 @@ pub struct Func<AST: ASTSpec = UntypedAST> {
pub resolved_type: AST::FuncCargo,
}

impl Func<UntypedAST> {
impl Func {
pub fn untyped<S: Into<String>>(
name: S,
params: Vec<Param>,
body: Block,
ret: TypeSpec,
) -> Func<UntypedAST> {
) -> Func {
Func { name: name.into(), params, body, ret, resolved_type: () }
}
}
Expand Down
102 changes: 90 additions & 12 deletions src/parser.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// TODO: write BNF grammar
use crate::ast::{Def::*, Expr::*, Stmt::*, *};
use crate::scanner;
use crate::token::{Token, Token::*};
use crate::token::{OpToken::*, Token::*, *};
use std::io;
use std::iter;
use std::result;
Expand Down Expand Up @@ -32,15 +32,15 @@ impl<R: io::BufRead> Parser<R> {
self.scanner.peek().is_none()
}

fn eat<T, F: FnMut(&Token) -> Option<T>>(
fn eat<T, F: FnMut(&Token) -> Option<T>, S: ToString>(
&mut self,
mut f: F,
want: String,
want: S,
) -> Result<T> {
let got = self.scanner.next().ok_or(Error::UnexpectedEOF)??;
match f(&got) {
Some(t) => Ok(t),
None => Err(Error::Invalid { want, got }),
None => Err(Error::Invalid { want: want.to_string(), got }),
}
}

Expand All @@ -58,6 +58,13 @@ impl<R: io::BufRead> Parser<R> {
)
}

fn eat_op(&mut self) -> Result<OpToken> {
self.eat(
|tok| if let OpTok(op) = tok { Some(*op) } else { None },
"op",
)
}

fn list<T, F: FnMut(&mut Self) -> Result<T>>(
&mut self,
mut f: F,
Expand All @@ -73,7 +80,7 @@ impl<R: io::BufRead> Parser<R> {
}

fn primary(&mut self) -> Result<Expr> {
let prim = match self.scanner.next().ok_or(Error::UnexpectedEOF)?? {
match self.scanner.next().ok_or(Error::UnexpectedEOF)?? {
Str(value) => Ok(StrExpr(Literal::new(value))),
Int(value) => Ok(IntExpr(Literal::new(value))),
IdentTok(name) => Ok(IdentExpr(Ident { name, resolution: () })),
Expand All @@ -83,17 +90,45 @@ impl<R: io::BufRead> Parser<R> {
Ok(expr)
}
got => Err(Error::Invalid { want: String::from("prim"), got }),
}?;
}
}

fn call(&mut self) -> Result<Expr> {
let target = self.primary()?;
if let Some(Ok(Lparen)) = self.scanner.peek() {
self.eat_tok(Lparen)?;
let args = self.list(|p| p.expr())?;
self.eat_tok(Rparen)?;
Ok(CallExpr(Call::untyped(prim, args)))
Ok(CallExpr(Call::untyped(target, args)))
} else {
Ok(prim)
Ok(target)
}
}

fn mul_div(&mut self) -> Result<Expr> {
let mut expr = self.call()?;
while matches!(self.scanner.peek(), Some(Ok(OpTok(Star | Slash)))) {
let op = self.eat_op()?;
expr = BinaryExpr(Binary::untyped(op.into(), expr, self.call()?));
}
Ok(expr)
}

fn add_sub(&mut self) -> Result<Expr> {
// TODO: unify this with |mul_div| and |list|
let mut expr = self.mul_div()?;
while matches!(self.scanner.peek(), Some(Ok(OpTok(Plus | Minus)))) {
let op = self.eat_op()?;
expr =
BinaryExpr(Binary::untyped(op.into(), expr, self.mul_div()?));
}
Ok(expr)
}

pub fn expr(&mut self) -> Result<Expr> {
self.add_sub()
}

fn type_spec(&mut self) -> Result<TypeSpec> {
// TODO: parse more complicated types
Ok(TypeSpec::Simple(self.eat_ident()?))
Expand Down Expand Up @@ -141,10 +176,6 @@ impl<R: io::BufRead> Parser<R> {
Ok(Param::untyped(name, typ))
}

pub fn expr(&mut self) -> Result<Expr> {
self.primary()
}

pub fn fn_expr(&mut self) -> Result<Func> {
self.eat_tok(FnTok)?;
let name = self.eat_ident()?;
Expand Down Expand Up @@ -176,6 +207,7 @@ pub fn parse<R: io::BufRead>(scanner: scanner::Scanner<R>) -> Result<Program> {
#[cfg(test)]
mod test {
use super::*;
use crate::ast::Op::*;

fn parse(input: &[u8]) -> Parser<&[u8]> {
Parser { scanner: scanner::scan(input).peekable() }
Expand Down Expand Up @@ -272,6 +304,52 @@ mod test {
assert_eq!(expected, actual);
}

#[test]
fn test_binary() {
let input = b"x + y + (5 + 4) + foo(7, 10) + z + ((a + b) + c)";
let expected = BinaryExpr(Binary::untyped(
Op::Add,
BinaryExpr(Binary::untyped(
Add,
BinaryExpr(Binary::untyped(
Add,
BinaryExpr(Binary::untyped(
Add,
BinaryExpr(Binary::untyped(
Add,
IdentExpr(Ident::untyped("x")),
IdentExpr(Ident::untyped("y")),
)),
BinaryExpr(Binary::untyped(
Add,
IntExpr(Literal::new(5)),
IntExpr(Literal::new(4)),
)),
)),
CallExpr(Call::untyped(
IdentExpr(Ident::untyped("foo")),
vec![
IntExpr(Literal::new(7)),
IntExpr(Literal::new(10)),
],
)),
)),
IdentExpr(Ident::untyped("z")),
)),
BinaryExpr(Binary::untyped(
Add,
BinaryExpr(Binary::untyped(
Add,
IdentExpr(Ident::untyped("a")),
IdentExpr(Ident::untyped("b")),
)),
IdentExpr(Ident::untyped("c")),
)),
));
let actual = parse(input).expr().unwrap();
assert_eq!(expected, actual);
}

#[test]
fn test_fn() {
let input = b" fn hello ( world: int, all: str ) { foo(27); } ";
Expand Down
4 changes: 2 additions & 2 deletions src/scanner.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::token::*;
use crate::token::{OpToken::*, *};
use std::io;
use std::iter;
use std::num;
Expand Down Expand Up @@ -120,7 +120,7 @@ impl<R: io::BufRead> iter::Iterator for Scanner<R> {
use Token::*;
self.skip_whitespace();
let result = self.peek()?.and_then(|b| match b {
b'+' => self.advance_emit(1, Plus),
b'+' => self.advance_emit(1, OpTok(Plus)),
b'=' => self.advance_emit(1, Eq),
b'(' => self.advance_emit(1, Lparen),
b')' => self.advance_emit(1, Rparen),
Expand Down
11 changes: 10 additions & 1 deletion src/token.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum OpToken {
Plus,
Minus,
Star,
Slash,
}

// TODO: stop importing * everywhere and make the names sane
#[derive(Debug, PartialEq, Clone)]
pub enum Token {
Lparen,
Expand All @@ -13,5 +22,5 @@ pub enum Token {
Int(i64),
LetTok,
FnTok,
Plus,
OpTok(OpToken),
}

0 comments on commit 69be2cc

Please sign in to comment.