diff options
| -rw-r--r-- | LICENSE | 21 | ||||
| -rw-r--r-- | progs/tests/simple.sy | 32 | ||||
| -rw-r--r-- | src/compiler.rs | 348 | ||||
| -rw-r--r-- | src/error.rs | 42 | ||||
| -rw-r--r-- | src/lib.rs | 73 | ||||
| -rw-r--r-- | src/main.rs | 2 | ||||
| -rw-r--r-- | src/tokenizer.rs | 5 | ||||
| -rw-r--r-- | sylt_macro/src/lib.rs | 78 |
8 files changed, 431 insertions, 170 deletions
@@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Edvard Thörnros & Gustav Sörnäs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/progs/tests/simple.sy b/progs/tests/simple.sy index 0f69ae2..84bc86d 100644 --- a/progs/tests/simple.sy +++ b/progs/tests/simple.sy @@ -1,11 +1,23 @@ -a := 0 -{ - b := 99999 - { - a := 99999 - a - } - b - a -= 1 +// +// import A + +// +f :: fn { + g! + print q } -a <=> -1 + +// +q :: 1 + +// +a := 1 + +qq :: fn { + g! + print q +} + + +// Steg 1: Hitta sektioner +// Dela sektioner, compilera felera sektioner efter varandra diff --git a/src/compiler.rs b/src/compiler.rs index ec3a52c..680d1f1 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -1,11 +1,11 @@ -use std::{borrow::Cow, path::{Path, PathBuf}}; +use std::{path::{Path, PathBuf}}; use std::cell::RefCell; use std::collections::{HashMap, hash_map::Entry}; use std::rc::Rc; use crate::{Blob, Block, Op, Prog, RustFunction, Type, Value}; use crate::error::{Error, ErrorKind}; -use crate::tokenizer::{Token, TokenStream}; +use crate::tokenizer::{Token, PlacedToken, TokenStream}; macro_rules! nextable_enum { ( $name:ident { $( $thing:ident ),* $( , )? } ) => { @@ -46,7 +46,7 @@ macro_rules! parse_branch { ($compiler:expr, $block:expr, [ $( $call:expr ),* ]) => { { let block_length = $block.ops.len(); - let token_length = $compiler.curr; + let token_length = $compiler.current_token; let num_errors = $compiler.errors.len(); let mut stored_errors = Vec::new(); @@ -63,7 +63,7 @@ macro_rules! parse_branch { return true; } $compiler.panic = false; - $compiler.curr = token_length; + $compiler.current_token = token_length; let thrown_errors = $compiler.errors.len() - num_errors - 1; stored_errors.extend($compiler.errors.split_off(thrown_errors)); $block.ops.truncate(block_length); @@ -82,7 +82,7 @@ macro_rules! parse_branch { ($compiler:expr, $block:expr, $call:expr) => { { let block_length = $block.ops.len(); - let token_length = $compiler.curr; + let token_length = $compiler.current_token; let num_errors = $compiler.errors.len(); let mut stored_errors = Vec::new(); // Closures for early return on success. @@ -97,7 +97,7 @@ macro_rules! parse_branch { return true; } $compiler.panic = false; - $compiler.curr = token_length; + $compiler.current_token = token_length; let thrown_errors = $compiler.errors.len() - num_errors - 1; stored_errors.extend($compiler.errors.split_off(thrown_errors)); $block.ops.truncate(block_length); @@ -107,6 +107,77 @@ macro_rules! parse_branch { }; } +macro_rules! push_frame { + ($compiler:expr, $block:expr, $code:tt) => { + { + $compiler.current_context_mut().push(Frame::new()); + + // Return value stored as a variable + let var = Variable::new("", true, Type::Unknown); + $compiler.define(var).unwrap(); + + $code + + let frame = $compiler.current_context_mut().pop().unwrap(); + // 0-th slot is the function itself. + for var in frame.stack.iter().skip(1) { + if !(var.read || var.upvalue) { + let e = ErrorKind::SyntaxError( + var.line, + Token::Identifier(var.name.clone() + )); + $compiler.error_on_line( + e, + var.line, + Some(format!("Unused value '{}'.", var.name)) + ); + } + $compiler.panic = false; + } + // The 0th slot is the return value, which is passed out + // from functions, and should not be popped. + 0 + } + }; +} + +macro_rules! push_scope { + ($compiler:expr, $block:expr, $code:tt) => { + let ss = $compiler.stack().len(); + $compiler.frame_mut().scope += 1; + + $code; + + $compiler.frame_mut().scope -= 1; + + let mut errors = Vec::new(); + for var in $compiler.frame().stack.iter().skip(ss).rev() { + if !(var.read || var.upvalue) { + let e = ErrorKind::SyntaxError( + var.line, + Token::Identifier(var.name.clone() + )); + errors.push(( + e, + var.line, + format!("Usage of undefined value: '{}'.", var.name),) + ); + } + if var.captured { + add_op($compiler, $block, Op::PopUpvalue); + } else { + add_op($compiler, $block, Op::Pop); + } + } + + for (e, l, m) in errors.iter() { + $compiler.error_on_line(e.clone(), *l, Some(m.clone())); + $compiler.panic = false; + } + $compiler.stack_mut().truncate(ss); + }; +} + nextable_enum!(Prec { No, Assert, @@ -243,6 +314,22 @@ impl Frame { } } +type CompilerContext = Vec<Frame>; + +struct Section<'a> { + path: PathBuf, + tokens: &'a [PlacedToken], +} + +impl<'a> Section<'a> { + fn new(path: PathBuf, tokens: &'a [PlacedToken]) -> Self { + Section { + path, + tokens + } + } +} + #[derive(Debug)] enum Name { Slot(usize, usize), @@ -250,12 +337,12 @@ enum Name { Space(HashMap<String, usize>), } -pub(crate) struct Compiler { - curr: usize, - tokens: TokenStream, - current_file: PathBuf, +pub(crate) struct Compiler<'a> { + current_token: usize, + current_section: usize, + sections: Vec<Section<'a>>, - frames: Vec<Frame>, + contextes: HashMap<PathBuf, CompilerContext>, panic: bool, errors: Vec<Error>, @@ -272,90 +359,85 @@ pub(crate) struct Compiler { names: HashMap<String, Name>, } -macro_rules! push_frame { - ($compiler:expr, $block:expr, $code:tt) => { - { - $compiler.frames.push(Frame::new()); - - // Return value stored as a variable - let var = Variable::new("", true, Type::Unknown); - $compiler.define(var).unwrap(); - - $code - - let frame = $compiler.frames.pop().unwrap(); - // 0-th slot is the function itself. - for var in frame.stack.iter().skip(1) { - if !(var.read || var.upvalue) { - let e = ErrorKind::SyntaxError( - var.line, - Token::Identifier(var.name.clone() - )); - $compiler.error_on_line( - e, - var.line, - Some(format!("Unused value '{}'.", var.name)) - ); - } - $compiler.panic = false; - } - // The 0th slot is the return value, which is passed out - // from functions, and should not be popped. - 0 - } - }; +/// Helper function for adding operations to the given block. +fn add_op(compiler: &Compiler, block: &mut Block, op: Op) -> usize { + block.add(op, compiler.line()) } -macro_rules! push_scope { - ($compiler:expr, $block:expr, $code:tt) => { - let ss = $compiler.stack().len(); - $compiler.frame_mut().scope += 1; +fn split_sections<'a>(file_name: PathBuf, tokens: &'a TokenStream) -> Vec<Section> { + let mut sections = Vec::new(); - $code; + let mut last = 0; + let mut curr = 0; + while curr < tokens.len() { + if match (tokens.get(curr + 0), tokens.get(curr + 1), tokens.get(curr + 2)) { + (Some((Token::LeftBrace, _)), ..) + => { + let mut blocks = 0; + loop { + curr += 1; + match tokens.get(curr) { + Some((Token::LeftBrace, _)) => { + blocks += 1; + } - $compiler.frame_mut().scope -= 1; + Some((Token::RightBrace, _)) => { + curr += 1; + blocks -= 1; + if blocks <= 0 { + break; + } + } - let mut errors = Vec::new(); - for var in $compiler.frame().stack.iter().skip(ss).rev() { - if !(var.read || var.upvalue) { - let e = ErrorKind::SyntaxError( - var.line, - Token::Identifier(var.name.clone() - )); - errors.push(( - e, - var.line, - format!("Variable is unused: '{}'.", var.name),) - ); - } - if var.captured { - add_op($compiler, $block, Op::PopUpvalue); - } else { - add_op($compiler, $block, Op::Pop); - } - } + None => { + break; + } - for (e, l, m) in errors.iter() { - $compiler.error_on_line(e.clone(), *l, Some(m.clone())); - $compiler.panic = false; + _ => {} + } + } + false + }, + + (Some((Token::Identifier(_), _)), + Some((Token::ColonColon, _)), + Some((Token::Fn, _))) + => true, + + (Some((Token::Identifier(_), _)), + Some((Token::ColonColon, _)), + Some(_)) + => true, + + (Some((Token::Identifier(_), _)), + Some((Token::ColonEqual, _)), + Some(_)) + => true, + + _ => false, + } { + sections.push(Section::new(file_name.clone(), &tokens[last..curr])); + last = curr; } - $compiler.stack_mut().truncate(ss); - }; + curr += 1; + } + sections.push(Section::new(file_name, &tokens[last..curr])); + sections } -/// Helper function for adding operations to the given block. -fn add_op(compiler: &Compiler, block: &mut Block, op: Op) -> usize { - block.add(op, compiler.line()) -} +impl<'a> Compiler<'a> { + pub(crate) fn new(current_file: &Path, tokens: &'a TokenStream) -> Self { + let current_file = current_file.to_path_buf(); + let sections = split_sections(current_file.clone(), tokens); -impl Compiler { - pub(crate) fn new(current_file: &Path, tokens: TokenStream) -> Self { + let mut contextes = HashMap::new(); + contextes.insert(current_file, vec![Frame::new()]); Self { - curr: 0, - tokens, - current_file: PathBuf::from(current_file), + current_token: 0, + current_section: 0, + sections, - frames: vec![Frame::new()], + contextes, panic: false, errors: vec![], @@ -385,7 +467,8 @@ impl Compiler { | Value::Bool(_) | Value::String(_) | Value::Tuple(_) - | Value::Nil) { + | Value::Nil) + { let entry = self.values.entry(value.clone()); if let Entry::Occupied(entry) = entry { *entry.get() @@ -406,14 +489,31 @@ impl Compiler { self.strings.len() - 1 } + fn section(&self) -> &Section { + &self.sections[self.current_section] + } + + fn current_file(&self) -> &Path { + &self.section().path + } + + fn current_context(&self) -> &CompilerContext { + self.contextes.get(self.current_file()).unwrap() + } + + fn current_context_mut(&mut self) -> &mut CompilerContext { + let file = self.current_file().to_path_buf(); + self.contextes.get_mut(&file).unwrap() + } + fn frame(&self) -> &Frame { - let last = self.frames.len() - 1; - &self.frames[last] + let last = self.current_context().len() - 1; + &self.current_context()[last] } fn frame_mut(&mut self) -> &mut Frame { - let last = self.frames.len() - 1; - &mut self.frames[last] + let last = self.current_context().len() - 1; + &mut self.current_context_mut()[last] } /// Marks a variable as read. Also marks upvalues. @@ -424,15 +524,15 @@ impl Compiler { } - if if let Some(up) = self.frames[frame_id].upvalues.get(var.slot) { + if if let Some(up) = self.current_context()[frame_id].upvalues.get(var.slot) { up.name == var.name } else { false } { - let mut inner_var = self.frames[frame_id].upvalues[var.slot].clone(); + let mut inner_var = self.current_context()[frame_id].upvalues[var.slot].clone(); inner_var.slot = inner_var.outer_slot; self.mark_read(frame_id - 1, &inner_var); - self.frames[frame_id].upvalues[var.slot].read = true; + self.current_context_mut()[frame_id].upvalues[var.slot].read = true; } else { - self.frames[frame_id].stack[var.slot].read = true; + self.current_context_mut()[frame_id].stack[var.slot].read = true; } } @@ -468,22 +568,26 @@ impl Compiler { self.panic = true; self.errors.push(Error { kind, - file: self.current_file.clone(), + file: self.current_file().to_path_buf(), line, message, }); } + fn init_section(&mut self, section: usize) { + self.current_token = 0; + self.current_section = section; + } fn peek(&self) -> Token { self.peek_at(0) } fn peek_at(&self, at: usize) -> Token { - if self.tokens.len() <= self.curr + at { + if self.section().tokens.len() <= self.current_token + at { crate::tokenizer::Token::EOF } else { - self.tokens[self.curr + at].0.clone() + self.section().tokens[self.current_token + at].0.clone() } } @@ -494,16 +598,28 @@ impl Compiler { fn eat(&mut self) -> Token { let t = self.peek(); - self.curr += 1; + self.current_token += 1; + match t { + Token::GitConflictBegin => { + self.current_token -= 1; + let start = self.line(); + self.current_token += 1; + while !matches!(self.eat(), Token::GitConflictEnd) {} + self.panic = false; + self.error_on_line(ErrorKind::GitConflictError(start, self.line()), start, None); + self.panic = true; + } + _ => {} + } t } /// The line of the current token. fn line(&self) -> usize { - if self.curr < self.tokens.len() { - self.tokens[self.curr].1 + if self.section().tokens.len() == 0 { + 0xCAFEBABE } else { - self.tokens[self.tokens.len() - 1].1 + self.section().tokens[std::cmp::min(self.current_token, self.section().tokens.len() - 1)].1 } } @@ -689,8 +805,8 @@ impl Compiler { } } - fn find_and_capture_variable<'a, I>(name: &str, mut iterator: I) -> Option<Variable> - where I: Iterator<Item = &'a mut Frame> { + fn find_and_capture_variable<'b, I>(name: &str, mut iterator: I) -> Option<Variable> + where I: Iterator<Item = &'b mut Frame> { if let Some(frame) = iterator.next() { if let Some(res) = frame.find_local(name) { frame.stack[res.slot].captured = true; @@ -720,7 +836,7 @@ impl Compiler { return Some(res); } - Self::find_and_capture_variable(name, self.frames.iter_mut().rev()) + Self::find_and_capture_variable(name, self.current_context_mut().iter_mut().rev()) } fn find_constant(&mut self, name: &str) -> usize { @@ -846,15 +962,15 @@ impl Compiler { self.stack_mut()[top].active = true; self.stack()[top].name.clone() } else { - format!("λ {}@{:03}", self.current_file.display(), self.line()) + format!("λ {}@{:03}", self.current_file().display(), self.line()) }; let mut args = Vec::new(); let mut return_type = Type::Void; - let mut function_block = Block::new(&name, &self.current_file); + let mut function_block = Block::new(&name, self.current_file()); let block_id = self.blocks.len(); - let temp_block = Block::new(&name, &self.current_file); + let temp_block = Block::new(&name, self.current_file()); self.blocks.push(Rc::new(RefCell::new(temp_block))); let _ret = push_frame!(self, function_block, { @@ -953,7 +1069,7 @@ impl Compiler { // Variables if let Some(var) = self.find_variable(&name) { - self.mark_read(self.frames.len() - 1, &var); + self.mark_read(self.current_context().len() - 1, &var); if var.upvalue { add_op(self, block, Op::ReadUpvalue(var.slot)); } else { @@ -1017,7 +1133,7 @@ impl Compiler { fn constant_statement(&mut self, name: &str, typ: Type, block: &mut Block) { // Magical global constants - if self.frames.len() <= 1 && self.peek() == Token::Fn { + if self.current_context().len() <= 1 && self.peek() == Token::Fn { self.function(block, Some(name)); // Remove the function, since it's a constant and we already // added it. @@ -1305,7 +1421,7 @@ impl Compiler { _ => unreachable!(), }; if let Some(var) = self.find_variable(&name) { - self.mark_read(self.frames.len() - 1, &var); + self.mark_read(self.current_context().len() - 1, &var); if var.upvalue { add_op(self, block, Op::ReadUpvalue(var.slot)); } else { @@ -1473,7 +1589,7 @@ impl Compiler { } - pub(crate) fn compile(&mut self, name: &str, file: &Path, functions: &[(String, RustFunction)]) -> Result<Prog, Vec<Error>> { + pub(crate) fn compile(&'a mut self, name: &str, file: &Path, functions: &[(String, RustFunction)]) -> Result<Prog, Vec<Error>> { self.functions = functions .to_vec() .into_iter() @@ -1484,10 +1600,14 @@ impl Compiler { let _ = self.define(main); let mut block = Block::new(name, file); - while self.peek() != Token::EOF { - self.statement(&mut block); - expect!(self, Token::Newline | Token::EOF, - "Expect newline or EOF after expression."); + for section in 0..self.sections.len() { + self.init_section(section); + while self.peek() != Token::EOF { + println!("compiling {} -- statement -- {:?}", section, self.line()); + self.statement(&mut block); + expect!(self, Token::Newline | Token::EOF, + "Expect newline or EOF after expression."); + } } let tmp = self.add_constant(Value::Unknown); add_op(self, &mut block, Op::Constant(tmp)); @@ -1509,7 +1629,7 @@ impl Compiler { } } - for var in self.frames.pop().unwrap().stack.iter().skip(1) { + for var in self.current_context_mut().pop().unwrap().stack.iter().skip(1) { if !(var.read || var.upvalue) { let e = ErrorKind::SyntaxError(var.line, Token::Identifier(var.name.clone())); let m = format!("Unused value '{}'.", var.name); diff --git a/src/error.rs b/src/error.rs index c2ad228..9aca985 100644 --- a/src/error.rs +++ b/src/error.rs @@ -32,6 +32,8 @@ pub enum ErrorKind { /// (line, token) SyntaxError(usize, Token), + /// (start, end) + GitConflictError(usize, usize), } #[derive(Debug, Clone)] @@ -67,9 +69,8 @@ impl fmt::Display for ErrorKind { write!(f, "Argument types do not match, expected [{:?}] but got [{:?}]", expected, given) } - ErrorKind::IndexOutOfBounds(value, len, slot) => { - write!(f, "Failed to index for {:?} - length is {} but index is {}", - value, len, slot) + ErrorKind::IndexError(value, slot) => { + write!(f, "Cannot index value '{:?}' with type '{:?}'.", value, slot) } ErrorKind::ExternTypeMismatch(name, types) => { write!(f, "Extern function '{}' doesn't accept argument(s) with type(s) {:?}", @@ -81,27 +82,32 @@ impl fmt::Display for ErrorKind { .fold(String::new(), |a, v| { format!("{}{:?}, ", a, v) }); write!(f, "Cannot apply {:?} to values {}", op, values) } - ErrorKind::AssertFailed => { - write!(f, "Assertion failed") + ErrorKind::UnknownField(obj, field) => { + write!(f, "Cannot find field '{}' on {:?}", field, obj) } - ErrorKind::SyntaxError(line, token) => { - write!(f, "Syntax Error on line {} at token {:?}", line, token) + ErrorKind::ArgumentCount(expected, given) => { + write!(f, "Incorrect argument count, expected {} but got {}.", + expected, given) } - ErrorKind::Unreachable => { - write!(f, "Reached unreachable code.") + ErrorKind::IndexOutOfBounds(value, len, slot) => { + write!(f, "Failed to index for {:?} - length is {} but index is {}", + value, len, slot) + } + ErrorKind::AssertFailed => { + write!(f, "Assertion failed") } ErrorKind::InvalidProgram => { write!(f, "{}", "[!!] Invalid program [!!]".bold()) } - ErrorKind::IndexError(value, slot) => { - write!(f, "Cannot index value '{:?}' with type '{:?}'.", value, slot) + ErrorKind::Unreachable => { + write!(f, "Reached unreachable code.") } - ErrorKind::UnknownField(obj, field) => { - write!(f, "Cannot find field '{}' on {:?}", field, obj) + ErrorKind::SyntaxError(line, token) => { + write!(f, "Syntax Error on line {} at token {:?}", line, token) } - ErrorKind::ArgumentCount(expected, given) => { - write!(f, "Incorrect argument count, expected {} but got {}.", - expected, given) + ErrorKind::GitConflictError(start_line, end_line) => { + write!(f, "Git conflict markers found between lines {} and {}", + start_line, end_line) } } } @@ -109,7 +115,7 @@ impl fmt::Display for ErrorKind { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let prompt = "*****".red(); + let prompt = " "; let message = match &self.message { Some(s) => format!("\n{} {}", prompt, s), None => String::from(""), @@ -123,7 +129,7 @@ impl fmt::Display for Error { String::new() }; - write!(f, "\n {} {}:{} \n{} {}{}{}\n", "ERR".red(), + write!(f, "{} {}:{}\n{} {}{}{}", "ERROR".red(), self.file.display().blue(), self.line.blue(), prompt, self.kind, message, line) } } @@ -21,12 +21,13 @@ mod tokenizer; /// Compiles a file and links the supplied functions as callable external /// functions. Use this if you want your programs to be able to yield. -pub fn compile_file(path: &Path, - print: bool, - functions: Vec<(String, RustFunction)> - ) -> Result<vm::VM, Vec<Error>> { +pub fn compile_file( + path: &Path, + print: bool, + functions: Vec<(String, RustFunction)> +) -> Result<vm::VM, Vec<Error>> { let tokens = tokenizer::file_to_tokens(path); - match compiler::Compiler::new(path, tokens).compile("main", path, &functions) { + match compiler::Compiler::new(path, &tokens).compile("main", path, &functions) { Ok(prog) => { let mut vm = vm::VM::new(); vm.print_blocks = print; @@ -54,7 +55,7 @@ pub fn run_string(s: &str, print: bool, functions: Vec<(String, RustFunction)>) } fn run(tokens: TokenStream, path: &Path, print: bool, functions: Vec<(String, RustFunction)>) -> Result<(), Vec<Error>> { - match compiler::Compiler::new(path, tokens).compile("main", path, &functions) { + match compiler::Compiler::new(path, &tokens).compile("main", path, &functions) { Ok(prog) => { let mut vm = vm::VM::new(); vm.print_blocks = print; @@ -880,7 +881,8 @@ mod tests { #[test] fn $fn() { crate::tests::panic_after(std::time::Duration::from_millis(500), || { - match $crate::run_string($prog, true, Vec::new()) { + let prog = std::concat!("q :: fn {", $prog, "\n{}\n}\nq()"); + match $crate::run_string(&prog, true, Vec::new()) { Ok(()) => {}, Err(errs) => { for e in errs.iter() { @@ -897,7 +899,8 @@ mod tests { #[test] fn $fn() { crate::tests::panic_after(std::time::Duration::from_millis(500), || { - $crate::assert_errs!($crate::run_string($prog, true, Vec::new()), $errs); + let prog = std::concat!("q :: fn {", $prog, "\n{}\n}\nq()"); + $crate::assert_errs!($crate::run_string(&prog, true, Vec::new()), $errs); }) } } @@ -1059,6 +1062,7 @@ a } add(1, 1) <=> 2 add(10, 20) <=> 30", + /* calls_inside_calls: "one := fn -> int { ret 1 } @@ -1082,6 +1086,7 @@ a ret inner(a) } f(g, 2) <=> 4", + */ multiple_returns: "f := fn a: int -> int { if a == 1 { ret 2 @@ -1108,6 +1113,7 @@ a factorial(6) <=> 720 factorial(12) <=> 479001600", +/* returning_closures: " f : fn -> fn -> int = fn -> fn -> int { x : int = 0 @@ -1130,15 +1136,17 @@ b() <=> 3 a() <=> 4 ", +*/ ); test_multiple!( blob, simple: "blob A {}", + field: "blob A { a: int }", + /* instantiate: "blob A {} a := A() a", - field: "blob A { a: int }", field_assign: "blob A { a: int } a := A() a.a = 2", @@ -1156,6 +1164,7 @@ a() <=> 4 a.b = 3 a.a + a.b <=> 5 5 <=> a.a + a.b", + */ blob_infer: " blob A { } a : A = A() @@ -1172,7 +1181,7 @@ a ); test_file!(scoping, "progs/tests/scoping.sy"); - test_file!(for_, "progs/tests/for.sy"); + // test_file!(for_, "progs/tests/for.sy"); test_multiple!( op_assign, @@ -1180,6 +1189,7 @@ a sub: "a := 2\na -= 1\na <=> 1", mul: "a := 2\na *= 2\na <=> 4", div: "a := 2\na /= 2\na <=> 1", +/* cluster: " blob A { a: int } a := A() @@ -1192,6 +1202,7 @@ a.a /= 2 a.a <=> 1 a.a -= 1 a.a <=> 0" +*/ ); test_multiple!( @@ -1357,6 +1368,7 @@ a ", +/* constant_function: " a() a :: fn {} @@ -1381,21 +1393,6 @@ q :: fn -> int { ret k() } ", - - constant_function_closure: " -q := 1 - -f :: fn -> int { - q += 1 - ret q -} - -f() <=> 2 -f() <=> 3 -f() <=> 4 -f() <=> 5 -", - constants_in_inner_functions: " q : int = 0 @@ -1414,7 +1411,33 @@ q <=> 2 g() q <=> 3 ", +*/ + constant_function_closure: " +q := 1 + +f :: fn -> int { + q += 1 + ret q +} + +f() <=> 2 +f() <=> 3 +f() <=> 4 +f() <=> 5 +", + ); + +/* + test_string!(conflict_markers, " +<<<<<<< HEAD +print extern_test(4.0) +======= +print extern_test(5.0) +>>>>>>> 2 +", + [ErrorKind::SyntaxError(_, _), ErrorKind::GitConflictError(2, 6)] ); +*/ } diff --git a/src/main.rs b/src/main.rs index bc68d40..28e4e79 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,7 @@ struct Args { fn main() { let args = parse_args(); let file = args.file.unwrap_or_else(|| Path::new("progs/tests/simple.sy").to_owned()); - let errs = match run_file(&file, args.print, vec![(String::from("extern_test"), extern_test)]) { + let errs = match run_file(&file, args.print, sylt_macro::link!(extern_test as test)) { Err(it) => it, _ => return, }; diff --git a/src/tokenizer.rs b/src/tokenizer.rs index b54e194..2c8e5e8 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -127,6 +127,11 @@ pub enum Token { #[token("\n")] Newline, + #[token("<<<<<<<")] + GitConflictBegin, + #[token(">>>>>>>")] + GitConflictEnd, + #[regex(r"//[^\n]*", logos::skip)] Comment, diff --git a/sylt_macro/src/lib.rs b/sylt_macro/src/lib.rs index fc43b5c..3b8b37c 100644 --- a/sylt_macro/src/lib.rs +++ b/sylt_macro/src/lib.rs @@ -73,15 +73,89 @@ pub fn extern_function(tokens: TokenStream) -> TokenStream { #[allow(unused_variables)] match __values { #(#typecheck_blocks),* - _ => Err(sylt::error::ErrorKind::ExternTypeMismatch(stringify!(#function).to_string(), __values.iter().map(|v| sylt::Type::from(v)).collect())) + _ => Err(sylt::error::ErrorKind::ExternTypeMismatch( + stringify!(#function).to_string(), + __values.iter().map(|v| sylt::Type::from(v)).collect() + )) } } else { match __values { #(#eval_blocks),* - _ => Err(sylt::error::ErrorKind::ExternTypeMismatch(stringify!(#function).to_string(), __values.iter().map(|v| sylt::Type::from(v)).collect())) + _ => Err(sylt::error::ErrorKind::ExternTypeMismatch( + stringify!(#function).to_string(), + __values.iter().map(|v| sylt::Type::from(v)).collect() + )) } } } }; TokenStream::from(tokens) } + +struct LinkRename { + _as: Token![as], + name: syn::Ident, +} + +impl Parse for LinkRename { + fn parse(input: ParseStream) -> Result<Self> { + Ok(Self { + _as: input.parse()?, + name: input.parse()?, + }) + } +} + +struct Link { + path: syn::Path, + rename: Option<LinkRename>, +} + +impl Parse for Link { + fn parse(input: ParseStream) -> Result<Self> { + Ok(Self { + path: input.parse()?, + rename: input.parse().ok(), + }) + } +} + +struct Links { + links: Vec<Link>, +} + +impl Parse for Links { + fn parse(input: ParseStream) -> Result<Self> { + let mut res = Self { + links: Vec::new(), + }; + while !input.is_empty() { + res.links.push(input.parse()?); + let _comma: Option<Token![,]> = input.parse().ok(); + } + Ok(res) + } +} + + +#[proc_macro] +pub fn link(tokens: TokenStream) -> TokenStream { + let links: Links = parse_macro_input!(tokens); + + let links: Vec<_> = links.links.iter().map(|link| { + let name = if let Some(rename) = &link.rename { + &rename.name + } else { + &link.path.segments.last().unwrap().ident + }; + let path = &link.path; + quote! { + (stringify!(#name).to_string(), #path) + } + }).collect(); + + let tokens = quote! { + vec![ #(#links),* ] + }; + TokenStream::from(tokens) +} |
