valhallac

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

commit bb647cb407c25a32430fdcaea94bab89a32904ce
parent d912dc4659fd5744e26810e444d5c635d3c34f39
Author: Demonstrandum <moi@knutsen.co>
Date:   Sun, 28 Jul 2019 23:46:31 +0100

Bytecode compiler simple arithmetic and type coercion.

Diffstat:
M.gitignore | 2++
MCargo.toml | 3+++
Asamples/casting.vh | 3+++
Msrc/compiler/block.rs | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Asrc/compiler/casts.rs | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/compiler/element.rs | 54+++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/compiler/instructions.rs | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Asrc/compiler/internal_functions.rs | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/compiler/mod.rs | 2++
Asrc/err.rs | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib.rs | 23+++++++++++++++++------
Msrc/syntax/ast.rs | 34+++++++++++++++++++++++++---------
Dsrc/syntax/err.rs | 53-----------------------------------------------------
Msrc/syntax/lexer.rs | 6+++---
Msrc/syntax/mod.rs | 11++++-------
Msrc/syntax/operators.rs | 10++--------
Msrc/syntax/parser.rs | 14++++++++------
Msrc/syntax/token.rs | 10+++++-----
Mtest.vh | 9++++-----
19 files changed, 445 insertions(+), 124 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -9,6 +9,8 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk +# VS Code Settings +.vscode/ # OS files .DS_Store diff --git a/Cargo.toml b/Cargo.toml @@ -26,3 +26,5 @@ lazy_static = "1.3.0" regex = "1" snailquote = "0.2.0" unicode-width = "0.1.5" +enum-primitive-derive = "^0.1" +num-traits = "^0.1"+ \ No newline at end of file diff --git a/samples/casting.vh b/samples/casting.vh @@ -0,0 +1,3 @@ +2 + 3.2 -- 2 natural gets cast to 2.0 real. +-3 + 4 -- 4 natural gets cased to 4 integer. +4 + 5 -- 4 and 5 stay natural. diff --git a/src/compiler/block.rs b/src/compiler/block.rs @@ -1,13 +1,23 @@ +use std::fmt; + use super::super::syntax; use syntax::ast; use super::element; use super::instructions; -use element::Element; +use element::{Element, Symbol}; use instructions::{Instr, Operators}; -#[derive(Debug)] +use super::internal_functions; +use super::casts; + +fn append_unique<'a>(v : &mut Vec<Element<'a>>, e : Element<'a>) -> usize { + let index = v.iter().position(|c| c == &e); + if index.is_none() { v.push(e); } + index.unwrap_or(v.len() - 1) +} + pub struct LocalBlock<'a> { constants : Vec<Element<'a>>, locals : Vec<Element<'a>>, @@ -23,25 +33,73 @@ impl<'a> LocalBlock<'a> { } } - pub fn generate(&mut self, node : ast::Nodes) { + fn push_const_instr(&mut self, e : Element<'a>) { + let index = append_unique(&mut self.constants, e); + self.instructions.push(Instr::Operator(Operators::PUSH_CONST as u8)); + self.instructions.push(Instr::Operand(index as u16)); + } + + fn emit(&mut self, node : &'a ast::Nodes) { match node { ast::Nodes::Num(num_node) => { - let elem = match num_node.value { - ast::Numerics::Natural(n) => Element::ENatural(n), - ast::Numerics::Integer(n) => Element::EInteger(n), - ast::Numerics::Real(n) => Element::EReal(n) - }; - let index = self.append_const(elem); - self.instructions.push(Instr::Operator(Operators::PUSH_CONST as u8)); - self.instructions.push(Instr::Operand(index as u16)) + let elem = casts::numerics_to_element(&num_node.value); + self.push_const_instr(elem); + }, + ast::Nodes::Str(str_node) => self.push_const_instr(Element::EString(str_node.value.to_owned())), + ast::Nodes::Sym(sym_node) => self.push_const_instr(Element::ESymbol(Symbol::new(&sym_node.value))), + ast::Nodes::Call(call_node) => { + if call_node.is_binary() { + let ident = call_node.callee.call().unwrap().callee.ident().unwrap(); + let args = vec![ + &call_node.callee.call().unwrap().operands[0], + &call_node.operands[0] + ]; + + // each_and_every_one + if args.iter().all(|n| n.is_numeric()) { + let nums = args.iter().map(|node| { + casts::numerics_to_element(&node.num().unwrap().value) + }).collect::<Vec<Element>>(); + let casted_args = casts::try_cast(nums); + if let Some(cast_succ) = casted_args { + let inop = internal_functions::get_internal_op(&ident.value, Some(&cast_succ)); + if let Some(op) = inop { + self.push_const_instr(cast_succ[0].clone()); + self.push_const_instr(cast_succ[1].clone()); + self.instructions.push(op); + } + } + } else { + let inop = internal_functions::get_internal_op(&ident.value, None); + if let Some(op) = inop { + self.emit(args[0]); + self.emit(args[1]); + self.instructions.push(op) + } + } + } }, _ => () }; } - fn append_const(&mut self, e : Element<'a>) -> usize { - let index = self.constants.iter().position(|c| c == &e); - if index.is_none() { self.constants.push(e); } - index.unwrap_or(self.constants.len() - 1) + pub fn generate(&mut self, nodes : &'a Vec<ast::Nodes>) { + for node in nodes { + self.emit(node); + } } } + +impl<'a> fmt::Display for LocalBlock<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "===Constants===============\n")?; + for (i, c) in self.constants.iter().enumerate() { + write!(f, "{: >3} | {} |\n", i, c)?; + } + write!(f, "===Bytecodes===============\n")?; + for inst in &self.instructions { + write!(f, "{}", inst)?; + } + write!(f, "") + } +}+ \ No newline at end of file diff --git a/src/compiler/casts.rs b/src/compiler/casts.rs @@ -0,0 +1,58 @@ +use super::element; +use element::Element; + +use super::super::syntax; +use syntax::ast; + +pub fn numerics_to_element<'a>(num : &ast::Numerics) -> Element<'a> { + match num { + ast::Numerics::Natural(n) => Element::ENatural(*n), + ast::Numerics::Integer(n) => Element::EInteger(*n), + ast::Numerics::Real(n) => Element::EReal(*n) + } +} + +pub enum Casts { // In order of cast strength. + REAL, + INT, + NAT +} + +macro_rules! conversion { + ($arg:expr, $to:path, $base:ident) => { + match $arg { + Element::ENatural(n) => $to(*n as $base), + Element::EInteger(n) => $to(*n as $base), + Element::EReal(n) => $to(*n as $base), + _ => panic!("Internal error, tried to cast non-numeric to numeric.") + }; + }; +} + +pub fn cast_to<'a> (cast : Casts, args : &Vec<Element>) -> Vec<Element<'a>> { + let mut new_args : Vec<Element> = vec![]; + for arg in args { + let new = match cast { + Casts::REAL => conversion!(arg, Element::EReal, f64), + Casts::INT => conversion!(arg, Element::EInteger, isize), + Casts::NAT => conversion!(arg, Element::ENatural, usize), + }; + new_args.push(new); + } + new_args +} + +pub fn try_cast<'a>(args : Vec<Element<'a>>) -> Option<Vec<Element<'a>>> { + if args.iter().all(Element::is_numeric) { + for arg in &args { + let converted = match arg { + Element::EReal(_) => Some(cast_to(Casts::REAL, &args)), + Element::EInteger(_) => Some(cast_to(Casts::INT, &args)), + _ => None + }; + if let Some(v) = converted { return Some(v); } + } + return Some(cast_to(Casts::NAT, &args)); + } + None +}+ \ No newline at end of file diff --git a/src/compiler/element.rs b/src/compiler/element.rs @@ -1,11 +1,31 @@ use std::fmt; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; +use snailquote::escape; + +#[derive(Clone, Copy)] pub struct Symbol<'a> { - hash : u32, + hash : u64, string : &'a str } -impl<'a> fmt::Debug for Symbol<'a> { +fn hash_symbol(string : &str) -> u64 { + let mut s = DefaultHasher::new(); + string.hash(&mut s); + s.finish() +} + +impl<'a> Symbol<'a> { + pub fn new(string : &'a str) -> Self { + Symbol { + hash: hash_symbol(string), + string + } + } +} + +impl<'a> fmt::Display for Symbol<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, ":{}", self.string) } @@ -17,11 +37,36 @@ impl<'a> PartialEq for Symbol<'a> { } } -#[derive(PartialEq, Debug)] +#[derive(Clone, PartialEq)] pub enum Element<'a> { ENatural(usize), EInteger(isize), EReal(f64), EString(String), ESymbol(Symbol<'a>), -}- \ No newline at end of file +} + +impl<'a> Element<'a> { + pub fn is_numeric(&self) -> bool { + match *self { + Element::ENatural(_) + | Element::EInteger(_) + | Element::EReal(_) => true, + _ => false + } + } +} + + +impl<'a> fmt::Display for Element<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = match self { + Element::ENatural(t) => format!("{: <5} => (Nat) ", t), + Element::EInteger(t) => format!("{: <5} => (Int) ", t), + Element::EReal(t) => format!("{: <5} => (Real) ", if t.fract() == 0f64 { format!("{:.1}", t) } else { f64::to_string(t) }), + Element::EString(t) => format!("{: <5} => (String)", format!("\"{}\"", escape(t))), + Element::ESymbol(t) => format!("{: <5} => (Sym) ", t.to_string()), + }; + write!(f, "{}", s) + } +} diff --git a/src/compiler/instructions.rs b/src/compiler/instructions.rs @@ -1,10 +1,28 @@ +use std::fmt; + +use num_traits::{FromPrimitive, ToPrimitive}; + #[derive(Debug)] pub enum Instr { Operator(u8), Operand(u16) } +impl fmt::Display for Instr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = match &self { + Instr::Operand(n) => format!("{: >5}\n", n), + Instr::Operator(n) => { + format!("({:08b}):{}", n, Operators::from_u8(*n).unwrap()) + } + }; + write!(f, "{}", s) + } +} + #[repr(u8)] +#[allow(non_camel_case_types)] +#[derive(Primitive)] pub enum Operators { HALT = 0, PUSH_CONST = 1, @@ -15,4 +33,63 @@ pub enum Operators { DUP = 6, DUP_N = 7, SWAP = 8, -}- \ No newline at end of file + + N_ADD = 40, + I_ADD = 41, + R_ADD = 42, + U_ADD = 43, + CONCAT = 44, + N_SUB = 45, + I_SUB = 46, + R_SUB = 47, + U_SUB = 48, + N_MUL = 49, + I_MUL = 50, + R_MUL = 51, + U_MUL = 52, + N_DIV = 53, + I_DIV = 54, + R_DIV = 55, + U_DIV = 56, +} + + +impl fmt::Display for Operators { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = match &self { + Operators::HALT => "HALT", + Operators::PUSH_CONST => "PUSH_CONST", + Operators::PUSH_LOCAL => "PUSH_LOCAL", + Operators::PUSH_SUPER => "PUSH_SUPER", + Operators::POP => "POP\n", + Operators::STORE_LOCAL => "STORE_LOCAL", + Operators::DUP => "DUP\n", + Operators::DUP_N => "DUP_N", + Operators::SWAP => "SWAP\n", + + Operators::N_ADD => "N_ADD\n", + Operators::I_ADD => "I_ADD\n", + Operators::R_ADD => "R_ADD\n", + Operators::U_ADD => "U_ADD\n", + Operators::CONCAT => "CONCAT\n", + + Operators::N_SUB => "N_SUB\n", + Operators::I_SUB => "I_SUB\n", + Operators::R_SUB => "R_SUB\n", + Operators::U_SUB => "U_SUB\n", + + Operators::N_MUL => "N_MUL\n", + Operators::I_MUL => "I_MUL\n", + Operators::R_MUL => "R_MUL\n", + Operators::U_MUL => "U_MUL\n", + + Operators::N_DIV => "N_DIV\n", + Operators::I_DIV => "I_DIV\n", + Operators::R_DIV => "R_DIV\n", + Operators::U_DIV => "U_DIV\n", + + _ => "INVALID_OPCODE\n" + }; + write!(f, "{}", s) + } +} diff --git a/src/compiler/internal_functions.rs b/src/compiler/internal_functions.rs @@ -0,0 +1,51 @@ +use super::element; +use element::Element; + +use super::instructions; +use instructions::{Instr, Operators}; + +use super::casts; + +/// Gets the appropriate operator for the internal functions. +/// Assumes all args have equal type. +pub fn get_internal_op(ident : &str, args : Option<&Vec<Element>>) -> Option<Instr> { + match ident { + "+" => { + if args.is_none() { return Some(Instr::Operator(Operators::U_ADD as u8)); } + Some(Instr::Operator(match args.unwrap()[0] { + Element::ENatural(_) => Operators::N_ADD, + Element::EInteger(_) => Operators::I_ADD, + Element::EReal(_) => Operators::R_ADD, + _ => Operators::U_ADD + } as u8)) + }, + "-" => { + if args.is_none() { return Some(Instr::Operator(Operators::U_SUB as u8)); } + Some(Instr::Operator(match args.unwrap()[0] { + Element::ENatural(_) => Operators::N_SUB, + Element::EInteger(_) => Operators::I_SUB, + Element::EReal(_) => Operators::R_SUB, + _ => Operators::U_SUB + } as u8)) + }, + "*" => { + if args.is_none() { return Some(Instr::Operator(Operators::U_MUL as u8)); } + Some(Instr::Operator(match args.unwrap()[0] { + Element::ENatural(_) => Operators::N_MUL, + Element::EInteger(_) => Operators::I_MUL, + Element::EReal(_) => Operators::R_MUL, + _ => Operators::U_MUL + } as u8)) + }, + "/" => { + if args.is_none() { return Some(Instr::Operator(Operators::U_DIV as u8)); } + Some(Instr::Operator(match args.unwrap()[0] { + Element::ENatural(_) => Operators::N_DIV, + Element::EInteger(_) => Operators::I_DIV, + Element::EReal(_) => Operators::R_DIV, + _ => Operators::U_DIV + } as u8)) + } + _ => None + } +}+ \ No newline at end of file diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs @@ -1,5 +1,7 @@ //! Compilation of the syntax tree. mod element; mod instructions; +mod casts; +mod internal_functions; pub mod block; mod marshal; \ No newline at end of file diff --git a/src/err.rs b/src/err.rs @@ -0,0 +1,55 @@ +use crate::syntax::token; + +use std::fs; +use std::fmt; +use std::io::{BufRead, BufReader}; + +pub enum Types { + LexError, + ParseError, + TypeError, + CompError, +} + +impl fmt::Display for Types { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let printable = match *self { + Types::LexError => "Lexicographical Error", + Types::ParseError => "Grammar Error", + Types::TypeError => "Typing Error", + Types::CompError => "Compilation Error", + }; + write!(f, "{}", printable) + } +} + +pub fn issue(class : Types, filename : &str, token : &token::Token, message : &str) { + let file = fs::File::open(filename).expect("Invalid filename for error message."); + let line = BufReader::new(file).lines().nth((token.location.line - 1) as usize).unwrap().unwrap(); + + eprintln!("{}", message); + eprintln!(" ==> {class} in (`{file}`:{line}:{col}):\n{space}|\n{line_str}| {stuff}", + class=class, file=filename, line=token.location.line, + col=token.location.col, space=" ".repeat(5), + line_str=format!("{: >4} ", token.location.line), stuff=line); + eprintln!("{space}|{: >offset$}", + "^".repeat(token.location.span as usize), space=" ".repeat(5), + offset=((token.location.col + token.location.span) as usize)); +} + +#[macro_export] +macro_rules! issue { + ($type:path, $file:expr, $token:expr, $message:expr) => { + { + err::issue($type, $file, $token, $message); + std::process::exit(1) + } + }; + ($type:path, $file:expr, $token:expr, $message:expr, $($form:expr),*) => { + { + err::issue($type, $file, $token, &format!($message, $($form),*)); + std::process::exit(1) + } + }; +} + diff --git a/src/lib.rs b/src/lib.rs @@ -2,17 +2,28 @@ //! the generated AST to Brokkr-bytecode for the //! Valhalla set theoretic programming language. +#[macro_use] +extern crate enum_primitive_derive; +extern crate num_traits; + +/// Error messages. +#[macro_use] +mod err; + /// Syntax submodule, responsible for lexical analysis, /// parsing and static analysis. mod syntax; + +/// Compiler, transforms AST into stack-based bytecode +/// instructions for the Brokkr VM, and marshals the instructions. mod compiler; -pub fn parse(filename : &str) { - syntax::parse_file(filename); +pub fn parse(filename : &str) { + let root = syntax::parse_file(filename); let mut code_block = compiler::block::LocalBlock::new(); - code_block.generate(syntax::ast::NumNode::new(3.14)); - code_block.generate(syntax::ast::NumNode::new(34)); - code_block.generate(syntax::ast::NumNode::new(3.14)); - println!("Code Block:\n{:#?}", code_block) + + + code_block.generate(&root.branches); + println!("Code Block:\n{}", code_block) } diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs @@ -126,7 +126,7 @@ pub struct SymNode { /// Call Node has a pointer to the callee node /// and a list of operand nodes. pub struct CallNode { - /// Pointer to heap allocated calling node. + /// Pointer to heap allocated calling node. pub callee : Box<Nodes>, /// Pointer to list of operand nodes. pub operands : Vec<Nodes> @@ -159,7 +159,7 @@ impl fmt::Display for Nodes { Nodes::Ident(node) => format!("%ident{{ :value \"{}\" }}", node.value), Nodes::Num(node) => format!("%num{{ :value {} }}", node.value), Nodes::Str(node) => format!("%str{{ :value \"{}\" }}", node.value), - Nodes::Sym(node) => format!("%sym{{ :value \"{}\" }}", node.value), + Nodes::Sym(node) => format!("%sym{{ :value \":{}\" }}", node.value), Nodes::Call(node) => format!( "%call{{\n :callee ({})\n :operands [|\n {}\n |]\n}}", node.callee, node.operands.iter().map(Nodes::to_string).collect::<Vec<String>>().join("\n ")), @@ -190,11 +190,18 @@ impl Nodes { pub fn is_atomic(&self) -> bool { match self { - Nodes::Ident(_) => true, - Nodes::Num(_) => true, - Nodes::Str(_) => true, - Nodes::Sym(_) => true, - Nodes::Empty(_) => true, + Nodes::Ident(_) + | Nodes::Num(_) + | Nodes::Str(_) + | Nodes::Sym(_) + | Nodes::Empty(_) => true, + _ => false + } + } + + pub fn is_numeric(&self) -> bool { + match self { + Nodes::Num(_)=> true, _ => false } } @@ -216,7 +223,7 @@ impl StrNode { } impl SymNode { - pub fn new(value : &str) -> Nodes { Nodes::Sym(SymNode { value: value.to_string() }) } + pub fn new(value : &str) -> Nodes { Nodes::Sym(SymNode { value: value[1..].to_string() }) } } impl CallNode { @@ -226,6 +233,15 @@ impl CallNode { operands: operands, }) } + + pub fn is_unary(&self) -> bool { + self.callee.ident().is_some() && !self.operands.is_empty() + } + + pub fn is_binary(&self) -> bool { + let sub_call = self.callee.call(); + sub_call.is_some() && !self.operands.is_empty() && sub_call.unwrap().is_unary() + } } impl EmptyNode { @@ -243,7 +259,7 @@ impl Root { } } -const TAB : &str = " "; +const TAB : &str = " "; pub fn pretty_print(node : &Nodes, depth : usize) -> String { let tab = TAB.repeat(depth); diff --git a/src/syntax/err.rs b/src/syntax/err.rs @@ -1,53 +0,0 @@ -use super::token; - -use std::fs; -use std::fmt; -use std::io::{BufRead, BufReader}; - -pub enum Types { - LexError, - ParseError, - TypeError, -} - -impl fmt::Display for Types { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let printable = match *self { - Types::LexError => "Lexicographical Error", - Types::ParseError => "Grammar Error", - Types::TypeError => "Typing Error" - }; - write!(f, "{}", printable) - } -} - -pub fn issue(class : Types, filename : &str, token : &token::Token, message : &str) { - let file = fs::File::open(filename).expect("Invalid filename for error message."); - let line = BufReader::new(file).lines().nth((token.location.line - 1) as usize).unwrap().unwrap(); - - eprintln!("{}", message); - eprintln!(" ==> {class} in (`{file}`:{line}:{col}):\n{space}|\n{line_str}| {stuff}", - class=class, file=filename, line=token.location.line, - col=token.location.col, space=" ".repeat(5), - line_str=format!("{: >4} ", token.location.line), stuff=line); - eprintln!("{space}|{: >offset$}", - "^".repeat(token.location.span as usize), space=" ".repeat(5), - offset=((token.location.col + token.location.span) as usize)); -} - -#[macro_export] -macro_rules! issue { - ($type:path, $file:expr, $token:expr, $message:expr) => { - { - super::err::issue($type, $file, $token, $message); - std::process::exit(1) - } - }; - ($type:path, $file:expr, $token:expr, $message:expr, $($form:expr),*) => { - { - super::err::issue($type, $file, $token, &format!($message, $($form),*)); - std::process::exit(1) - } - }; -} - diff --git a/src/syntax/lexer.rs b/src/syntax/lexer.rs @@ -66,7 +66,7 @@ macro_rules! try_match { /// the generated token-stream (as a Vec<Token>). pub fn lex(string : &str) -> Vec<Token> { let mut token_stream : Vec<Token> = Vec::new(); - + let mut current_char_ptr = 0; let string_size = string.bytes().count(); @@ -74,7 +74,7 @@ pub fn lex(string : &str) -> Vec<Token> { let mut line = 1; let mut col = 1; - // Step through + // Step through while current_char_ptr < string_size { // Align to character boundary. if let Some(slice) = &string.get(current_char_ptr..) { @@ -101,7 +101,7 @@ pub fn lex(string : &str) -> Vec<Token> { col += string.get(old_char_ptr..current_char_ptr) .expect("Comment ended or started not on char boundary.") .width() as u32; - + continue; } diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs @@ -2,10 +2,10 @@ /// location manages line and column location of /// lexical tokens as well as their span. -mod location; +pub mod location; /// Provides token classes and methods -mod token; +pub mod token; /// Abstract Syntax Tree nodes and methods. pub mod ast; @@ -13,10 +13,6 @@ pub mod ast; /// Dealing with associativity and precedence. mod operators; -/// Error messages. -#[macro_use] -pub mod err; - /// Lexer splits code up into a token-stream /// of relevant lexical tokens, making the /// parsing step a lot easier. @@ -30,7 +26,7 @@ use token::ShowStream; /// Parses a given file, calling various methods from /// the `syntax` sub-module. -pub fn parse_file(filename : &str) { +pub fn parse_file(filename : &str) -> ast::Root { let code = fs::read_to_string(filename) .expect("Could not open file for reading."); println!("Code:\n{}\n", code); @@ -40,4 +36,5 @@ pub fn parse_file(filename : &str) { let tree = parser::parse(stream, filename); println!("AST:\n{}\n", tree); + tree } \ No newline at end of file diff --git a/src/syntax/operators.rs b/src/syntax/operators.rs @@ -37,12 +37,6 @@ impl<'a> Operator<'a> { pub fn is_unary(&self) -> bool { self.has_arity(1) } pub fn is_binary(&self) -> bool { self.has_arity(2) } - - pub fn is_variable(&self) -> bool { self.has_arity(0) } - - pub fn is_func(&self) -> bool { - self.precedence == 190 && self.arity >= 1 - } } /// Wrapper for table of known operators. @@ -98,12 +92,12 @@ impl<'a> PrecedenceTable<'a> { op( "=", 30, Side::Right, 2), op( "if", 20, Side::Neither, 2), op("unless", 20, Side::Neither, 2), - op( ",", 10, Side::Right, 2), + op( ",", 10, Side::Right, 2), op( "=>", 1, Side::Neither, 2), op( "(", 0, Side::Neither, 1), op( ")", 0, Side::Neither, 1), ]}; - + table } diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs @@ -1,7 +1,9 @@ use super::token; use super::ast; use super::operators; -use super::err; + +#[macro_use] +use super::super::err; use token::{Token, TokenType}; use ast::{Nodes, Numerics}; @@ -20,7 +22,7 @@ struct ParseEnvironment<'a> { pub stream : Vec<Token>, pub optable : operators::PrecedenceTable<'a>, pub file : &'a str, - + ignore_newline : bool } @@ -35,7 +37,7 @@ impl<'a> ParseEnvironment<'a> { ignore_newline: false } } - + pub fn start(&mut self) { let mut current = self.stream.first(); while current.is_some() && current.unwrap().class != TokenType::EOF { @@ -93,7 +95,7 @@ impl<'a> ParseEnvironment<'a> { return ast::EmptyNode::new(); } - + self.ignore_newline = true; self.skip_newlines(); let expr = self.expr(0); @@ -126,7 +128,7 @@ impl<'a> ParseEnvironment<'a> { let next = &(&self.stream[0].string).clone(); if self.ignore_newline && next == "\n" { - self.stream.remove(0); + self.stream.remove(0); continue; } if next == "\0" || next == "\n" || next == ")" { break; } @@ -194,7 +196,7 @@ mod test { assert_eq!(ast::NumNode::new(-2).num().unwrap().value, Numerics::Integer(-2isize)); assert_eq!(ast::NumNode::new(-2i32).num().unwrap().value, Numerics::Integer(-2isize)); assert_eq!(ast::NumNode::new(-2isize).num().unwrap().value, Numerics::Integer(-2isize)); - + assert_eq!(ast::NumNode::new(-2.62).num().unwrap().value, Numerics::Real(-2.62f64)); assert_eq!(ast::NumNode::new(2.62).num().unwrap().value, Numerics::Real(2.62f64)); diff --git a/src/syntax/token.rs b/src/syntax/token.rs @@ -85,11 +85,11 @@ impl Token { /// Checks if the token represents an atomic datum. pub fn is_atomic(&self) -> bool { match self.class { - TokenType::Ident => true, - TokenType::Num => true, - TokenType::Op => true, - TokenType::Sym => true, - TokenType::Str => true, + TokenType::Ident + | TokenType::Num + | TokenType::Op + | TokenType::Sym + | TokenType::Str => true, _ => false, } } diff --git a/test.vh b/test.vh @@ -1,4 +1,4 @@ -2 -34 -2.3 --2- \ No newline at end of file +4 + 3 +5 - 4 +3 * 4.0 +3 + :a