valhallac

Compiler for set-theoretic programming language.
git clone git://git.knutsen.co/valhallac
Log | Files | Refs | README | LICENSE

commit 72b3c7a9d43eadedbaf1fa0dddfc784feb034487
parent bb44d5a8955a79096b6944d6acd7f6c6644c148d
Author: Demonstrandum <moi@knutsen.co>
Date:   Mon, 22 Jul 2019 01:16:41 +0100

Function Currying & partial application for infix. Yay!

Diffstat:
MREADME.md | 18++++++++++++++++++
Msamples/currying_infix.vh | 14++++++++++++--
Asamples/func_apl.vh | 12++++++++++++
Asamples/functions.vh | 7+++++++
Msrc/syntax/lexer.rs | 2+-
Msrc/syntax/mod.rs | 2+-
Msrc/syntax/operators.rs | 22+++++++++++-----------
Msrc/syntax/parser.rs | 83++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mtest.vh | 12++++++++----
9 files changed, 125 insertions(+), 47 deletions(-)

diff --git a/README.md b/README.md @@ -4,6 +4,24 @@ # Valhalla Language +## IN DEVELOPMENT + +What's been done so far on the front end: +- [ ] Parser + - [x] Lexical analysis, full UTF-8 support, handling: identifiers, symbols, numbers, strings (utf-8 with good number of escapes), all sorts of braces for vectors, sets, grouping, etc. + - [x] Infix, prefix and suffix notation. + - [x] Correct parsing of precedence, arity and associativity. + - [x] The base operators for the language. + - [x] Proper function calls, Currying is properly implemented for functions (Haskell-like syntax). + - [x] (Cool) error messages, with line and column number and read-out of the line. + - [ ] Macros (incl. macro definitions and macro application). +- [ ] Compiler (generating bytecode to assemble an executable file). + +The VM, i.e. the backend for the language, is being developed independently +and will have its own progress and check-list updates. + +### Description + This repository contains the front-end (parser and bytecode compilation) which understands the syntax and semantics, as well as doing static type analysis and code diff --git a/samples/currying_infix.vh b/samples/currying_infix.vh @@ -1,3 +1,13 @@ (2 + 3) -- Eq. of: ((2 +) 3), can write: ((+ 3) 2) (2 +) -(+ 3) -- Partial application- \ No newline at end of file +(+ 3) -- Partial application + + +---- +2 + 3 +-- same as +(+) 2 3 +-- same as +(2 +) 3 +-- same as +(+ 3) 2+ \ No newline at end of file diff --git a/samples/func_apl.vh b/samples/func_apl.vh @@ -0,0 +1,11 @@ +map (1 +) list +-- The same as +(map) ((1) +) (list) +-- which is the same as: +(((map) ((1) +)) (list)) +-- because of currying, `a b c` <=> `((a b) c)` + +-- Function application has highest binding, so: +a b c + 3 +-- is the same as +(a b c) + 3+ \ No newline at end of file diff --git a/samples/functions.vh b/samples/functions.vh @@ -0,0 +1,6 @@ +plus : Nat -> Nat -> Nat + +postulate do: + plus n 0 = n + plus 0 n = n + plus (succ n) m = succ (plus n m)+ \ No newline at end of file diff --git a/src/syntax/lexer.rs b/src/syntax/lexer.rs @@ -42,7 +42,7 @@ const IDENT_CHARS : &str = r"\p{L}\?!'\-_"; lazy_static! { static ref OP : Regex = re!(r"\A([,\+\.\*\|\\/\&%\$\^\~<¬=@>\-]+|:{2,})"); static ref IDENT : Regex = re!(&format!(r"\A([{id}][{id}\p{{N}}]*)", id=IDENT_CHARS)); - static ref SYM : Regex = re!(&format!(r"\A(:[{id}\p{{N}}]+)", id=IDENT_CHARS)); + static ref SYM : Regex = re!(r"\A(:[^\s]+)"); static ref NUM : Regex = re!(r"\A(\-?(?:(?:0[xX][0-9a-f]+)|(?:0[bB][01]+)|(?:0[Oo][0-7]+)|(?:(?:[0-9]+(?:\.[0-9]+)?(?:e[\+\-]?[0-9]+)?))))"); } diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs @@ -30,7 +30,7 @@ use token::ShowStream; /// Parses a given file, calling various methods from /// the `syntax` sub-module. -pub fn parse_file(filename : &'static str) { +pub fn parse_file(filename : &str) { let code = fs::read_to_string(filename) .expect("Could not open file for reading."); println!("Code:\n{}\n", code); diff --git a/src/syntax/operators.rs b/src/syntax/operators.rs @@ -10,16 +10,16 @@ pub enum Side { /// - Its precedence (as an i32), the higher the int, the higher the precedence. /// - Associativity, which can either be left, right, or no associativity. /// - The number of arguments it takes / its arity. Either one, or two. -#[derive(Copy, Clone)] -pub struct Operator { - pub name : &'static str, +#[derive(Clone, Copy)] +pub struct Operator<'a> { + pub name : &'a str, pub precedence : i32, pub associativity : Side, pub arity : i32, } -impl Operator { - pub fn new(name : &'static str, precedence : i32, associativity : Side, arity : i32) -> Self { +impl<'a> Operator<'a> { + pub fn new(name : &'a str, precedence : i32, associativity : Side, arity : i32) -> Self { Operator { name: name.clone(), precedence, @@ -46,8 +46,8 @@ impl Operator { } /// Wrapper for table of known operators. -pub struct PrecedenceTable { - pub table : Vec<Operator> +pub struct PrecedenceTable<'a> { + pub table : Vec<Operator<'a>> } #[macro_export] @@ -57,7 +57,7 @@ macro_rules! push_op { }; } -impl PrecedenceTable { +impl<'a> PrecedenceTable<'a> { pub fn new() -> Self { let op = Operator::new; let table = PrecedenceTable { table: vec![ @@ -107,18 +107,18 @@ impl PrecedenceTable { table } - pub fn new_op(&mut self, name : &'static str, prec : i32, assoc : Side, arity : i32) -> Operator { + pub fn new_op(&mut self, name : &'a str, prec : i32, assoc : Side, arity : i32) -> Operator { let op = Operator::new(name, prec, assoc, arity); self.table.push(op); op } - pub fn new_fun(&mut self, name : &'static str, max_arity : i32) -> Operator { + pub fn new_fun(&mut self, name : &'a str, max_arity : i32) -> Operator { self.new_op(name, 19, Side::Neither, max_arity) } pub fn lookup(&self, name : &str, arity : i32) -> Option<&Operator> { - self.table.iter().filter(|o| o.name == name && o.arity == arity).next() + self.table.iter().filter(|o| o.name == name && o.arity == arity).nth(0) } pub fn exists(&self, name : &str) -> bool { diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs @@ -6,7 +6,7 @@ use super::err; use token::{Token, TokenType}; use ast::{Nodes, Numerics}; -pub fn parse(stream : Vec<Token>, file : &'static str) -> ast::Root { +pub fn parse(stream : Vec<Token>, file : &str) -> ast::Root { let mut environment = ParseEnvironment::new(stream, file); environment.optable.new_fun("max", 4); @@ -15,15 +15,15 @@ pub fn parse(stream : Vec<Token>, file : &'static str) -> ast::Root { environment.root } -struct ParseEnvironment { +struct ParseEnvironment<'a> { pub root : ast::Root, pub stream : Vec<Token>, - pub optable : operators::PrecedenceTable, - pub file : &'static str + pub optable : operators::PrecedenceTable<'a>, + pub file : &'a str } -impl ParseEnvironment { - pub fn new(stream : Vec<Token>, file : &'static str) -> Self { +impl<'a> ParseEnvironment<'a> { + pub fn new(stream : Vec<Token>, file : &'a str) -> Self { ParseEnvironment { root: ast::Root::new(), stream: stream, @@ -48,23 +48,28 @@ impl ParseEnvironment { fn null_den(&mut self, token : &Token) -> Nodes { match token.class { - TokenType::Ident => ast::IdentNode::new(&token.string), - TokenType::Op => { + TokenType::Op | TokenType::Ident => { let is_op = self.optable.exists(&token.string); if is_op { - return match self.stream[0].class { - TokenType::RParen => { - ast::CallNode::new(ast::IdentNode::new(&token.string), vec![]) - }, - _ => ast::CallNode::new( - ast::CallNode::new( - ast::IdentNode::new(&token.string), - vec![]), - vec![self.expr(500)]) - }; + let prefix = self.optable.lookup(&token.string, 1); + if prefix.is_none() { + return match self.stream[0].class { + TokenType::RParen => { + ast::CallNode::new(ast::IdentNode::new(&token.string), vec![]) + }, + _ => ast::CallNode::new( + ast::CallNode::new( + ast::IdentNode::new(&token.string), + vec![ast::EmptyNode::new()]), + vec![self.expr(500)]) + }; + } else { // It is a prefix unary operator. + return ast::CallNode::new( + ast::IdentNode::new(&token.string), + vec![self.expr(500)]); + } } - issue!(err::Types::ParseError, self.file, token, - "`{}` is not an operator.", token.string); + ast::IdentNode::new(&token.string) }, TokenType::Num => ast::NumNode::new(&*token.string), TokenType::Str => ast::StrNode::new(&token.string), @@ -95,19 +100,41 @@ impl ParseEnvironment { || self.stream[0].class == TokenType::Term { return left; } - if !self.optable.exists(&self.stream[0].string) { - return issue!(err::Types::ParseError, self.file, &self.stream[0], - "`{}` is not a binary operator.", &self.stream[0].string); - } - while self.optable.precedence(&self.stream[0].string).unwrap_or(0) > right_prec { - if self.stream[0].class == TokenType::EOF { break; } - let op = self.optable.lookup(&self.stream.remove(0).string, 2).unwrap(); - left = self.left_den(left, op.clone()); + while self.optable.precedence(&self.stream[0].string).unwrap_or(190) > right_prec { + let next = &(&self.stream[0].string).clone(); + + if next == "\0" || next == "\n" || next == ")" { break; } + + let maybe_op = self.optable.lookup(next, 2); + if let Some(op) = maybe_op { + self.stream.remove(0); + let cloned = operators::Operator::new(next, op.precedence, op.associativity, 2); + left = self.left_den(left, cloned); + } else { // Function call. + let mut pushed = false; + match left { + Nodes::Call(ref mut call) => { + if call.operands.is_empty() { + call.operands.push(self.expr(190)); + pushed = true; + } + } + _ => () + }; + if !pushed { + left = self.func_appl(left); + } + } } return left; } + fn func_appl(&mut self, left : Nodes) -> Nodes { + println!("Creating function call with:\n --> {}", left); + ast::CallNode::new(left, vec![self.expr(190)]) + } + fn left_den(&mut self, left : Nodes, op : operators::Operator) -> Nodes { let first_appl = ast::CallNode::new(ast::IdentNode::new(op.name), vec![left]); if self.stream[0].class == TokenType::RParen { diff --git a/test.vh b/test.vh @@ -1,3 +1,7 @@ -(2 + 3) -- Eq. of: ((2 +) 3), can write: ((+ 3) 2) -(2 +) -(+ 3) -- Partial application- \ No newline at end of file +2 + 3 +-- same as +(+) 2 3 +-- same as +(2 +) 3 +-- same as +(+ 3) 2+ \ No newline at end of file