valhallac

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

commit 2c4facb682e16a8ac0d8315988d8621a2f6a48d8
parent b9972fba75840a84982cea4ecbf1481c8ffb827c
Author: Demonstrandum <moi@knutsen.co>
Date:   Sat, 13 Jun 2020 15:23:18 +0100

Rewrite error reporting and source site tracking.

Diffstat:
MCargo.toml | 2+-
Dbuild.rs | 42------------------------------------------
Msrc/bin.rs | 14+++++++++++---
Msrc/compiler/block.rs | 44++++++++++++++++++++++++++++----------------
Dsrc/err.rs | 117-------------------------------------------------------------------------------
Asrc/issue.rs | 229+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib.rs | 34++++++++++++++++++++++++++++++++--
Asrc/site.rs | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/syntax/analysis/constant_fold.rs | 8++++----
Msrc/syntax/analysis/type_balancer.rs | 18+++++++++---------
Msrc/syntax/analysis/type_checker.rs | 21+++++++++++----------
Msrc/syntax/analysis/type_resolver.rs | 118+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/syntax/ast.rs | 74+++++++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/syntax/lexer.rs | 69+++++++++++++++++++++++++++++++++++++++++----------------------------
Msrc/syntax/parser.rs | 200++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Msrc/syntax/token.rs | 12++++++------
Astatic/build.rs | 7+++++++
Astatic/read_version.rs | 42++++++++++++++++++++++++++++++++++++++++++
Mtest_source.vh | 9+++++++++
Atests/expect_fail/invalid_operations_1.vh | 25+++++++++++++++++++++++++
20 files changed, 796 insertions(+), 403 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -16,7 +16,7 @@ readme = "README.md" version = "0.1.0" edition = "2018" -build = "build.rs" +build = "static/build.rs" [features] debug = ["snailquote"] diff --git a/build.rs b/build.rs @@ -1,42 +0,0 @@ -use std::env; -use std::fs::File; -use std::io::{Read, Write}; -use std::path::Path; -use toml; - -fn main() -> Result<(), Box<dyn std::error::Error>> { - let out_dir = env::var("OUT_DIR")?; - let dest_path = Path::new(&out_dir).join("version.rs"); - let mut f = File::create(&dest_path).unwrap(); - - let mut config_f = File::open("Cargo.toml")?; - let mut config_str = String::new(); - config_f.read_to_string(&mut config_str)?; - - let config: toml::Value = toml::from_str(&config_str) - .unwrap(); - - println!("{:#?}", config); - - match &config["package"]["version"] { - toml::Value::String(version) => { - if let &[major, minor, tiny] = version - .split(".") - .map(|s| s.parse::<u8>().unwrap()) - .collect::<Vec<_>>().as_slice() { - - f.write_all(format!(" - const fn read_version() -> (u8, u8, u8) {{ - return ({}, {}, {}); - }} - ", major, minor, tiny).as_bytes())?; - } else { - panic!("Version string should be three numbers \ - separated by two dots."); - } - } - _ => panic!("Version in `Config.toml' should be a string!") - } - - Ok(()) -} diff --git a/src/bin.rs b/src/bin.rs @@ -1,5 +1,4 @@ use ::valhallac; -//use valhallac::err; use std::env; use std::{fs::File, path::Path}; @@ -24,7 +23,7 @@ enum Flags { Version } -// TODO: Halt on urecognised options. +// TODO: Halt on unrecognised options. /// Collect flags and options passed to the executable. fn collect_flags() -> HashMap<Flags, String> { let mut map = HashMap::new(); @@ -90,6 +89,8 @@ lazy_static! { } pub fn main() -> Result<(), Box<dyn std::error::Error>> { + valhallac::set_panic(); + let mut args = env::args(); args.next(); @@ -120,12 +121,19 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> { for file in files { not_debug!(verbose, { - println!("\n{}{} `{}'...", *INFO, + println!("{}{} `{}'...", *INFO, "Parsing".bold().blue(), file.underline().white()); }); // Parse source into tree. let root = valhallac::parse(&file); + unsafe { + if !valhallac::PANIC_MESSAGE.is_empty() { + // An error in parsing, halt compilation. + panic!("Parse error, will not compile bad tree."); + } + } + // Then compile into series of instructions, // stored as a code block. not_debug!(verbose, { diff --git a/src/compiler/block.rs b/src/compiler/block.rs @@ -3,9 +3,9 @@ use std::fmt; use std::collections::{HashMap, VecDeque}; -use super::super::err; +use crate::issue; -use super::super::syntax; +use crate::syntax; use syntax::ast; use syntax::ast::{Nodes, StaticTypes}; @@ -136,14 +136,16 @@ impl<'a> LocalBlock<'a> { fn ident_assignment(&mut self, left : &'a ast::IdentNode, right : &'a Nodes) { if self.types_to_check.is_empty() { - issue!(TypeError, &self.filename, err::LINE, self.current_line, + fatal!(TypeError, left.site.with_filename(&self.filename), "You must state what set `{}' is a member of. - No type-annotation found.", left.value); + No type-annotation found.", left.value) + .print(); } if self.locals_map.contains_key(&left.value) { - issue!(CompError, &self.filename, err::LINE, self.current_line, + fatal!(CompError, left.site.with_filename(&self.filename), "Cannot mutate value of `{}', - as it is already bound.", left.value); + as it is already bound.", left.value) + .print(); } let index = self.insert_local(left.value.to_owned()); @@ -214,7 +216,7 @@ impl<'a> LocalBlock<'a> { } fn emit(&mut self, node : &'a Nodes) { - let current_line = node.location().line as usize; + let current_line = node.site().location.line.unwrap(); if self.current_line != current_line { let len = self.instructions.len(); if len > 1 @@ -265,10 +267,10 @@ impl<'a> LocalBlock<'a> { StaticTypes::TInteger => 0x02, StaticTypes::TReal => 0x03, StaticTypes::TString => 0x04, - _ => issue!(CompError, &self.filename, - err::LINE, self.current_line, + _ => fatal!(CompError, arg.site().with_filename(&self.filename), "__raw_print cannot display `{}' types.", arg.yield_type()) + .crash_and_burn() }; self.emit(arg); @@ -296,20 +298,28 @@ impl<'a> LocalBlock<'a> { "Real" => 0b0000_0011, "Int" => 0b0000_0010, "Nat" => 0b0000_0001, - _ => issue!(TypeError, &self.filename, err::LINE, self.current_line, - "Compiler does not know how to cast to `{}'.", cast_name) + _ => fatal!(TypeError, + args[1].site().with_filename(&self.filename), + "Compiler does not know how to cast to `{}'.", + cast_name) + .crash_and_burn() }; let cast_from = match args[0].yield_type() { ast::StaticTypes::TReal => 0b0000_0011, ast::StaticTypes::TInteger => 0b0000_0010, ast::StaticTypes::TNatural => 0b0000_0001, - _ => issue!(TypeError, &self.filename, err::LINE, self.current_line, - "Compiler does not know how to cast from `{}'.", args[0].yield_type()) + _ => fatal!(TypeError, + args[0].site().with_filename(&self.filename), + "Compiler does not know how to cast from `{}'.", + args[0].yield_type()) + .crash_and_burn() }; self.push_operand(cast_from << 8 | cast_to); } else { - issue!(CompError, &self.filename, err::LINE, self.current_line, + issue!(CompError, + args[1].site().with_filename(&self.filename), "Cast-type provided to `cast' has to be a type-name.") + .print(); } return; } @@ -330,8 +340,10 @@ impl<'a> LocalBlock<'a> { // If the LHS is not an ident, it is not a // valid annotation. if args[0].ident().is_none() { - issue!(CompError, &self.filename, err::LINE, self.current_line, - "Left of `:` type annotator must be an identifier."); + issue!(CompError, + args[0].site().with_filename(&self.filename), + "Left of `:` type annotator must be an identifier.") + .print(); } let left = args[0].ident().unwrap(); diff --git a/src/err.rs b/src/err.rs @@ -1,117 +0,0 @@ -#![allow(non_camel_case_types)] -#![allow(clippy::pub_enum_variant_names)] - -use crate::syntax::{token::Token, location::Loc}; - -use std::fs; -use std::fmt; -use std::io::{BufRead, BufReader}; - -use colored; -use colored::*; - -use unindent::unindent; - -pub struct LINE; -pub struct LOC; - -pub enum IssueType { - LexError, LexWarn, - ParseError, ParseWarn, - TypeError, TypeWarn, - CompError, CompWarn -} - -impl fmt::Display for IssueType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let printable = match *self { - IssueType::LexError => "Lexicographical Error".red(), - IssueType::ParseError => "Grammar Error".red(), - IssueType::TypeError => "Typing Error".red(), - IssueType::CompError => "Compilation Error".red(), - IssueType::LexWarn => "Lexicographical Warning".yellow(), - IssueType::ParseWarn => "Grammar Warning".yellow(), - IssueType::TypeWarn => " Typing Warning".yellow(), - IssueType::CompWarn => " Compilation Warning".yellow(), - }; - write!(f, "{}", printable) - } -} - -pub fn loc_issue(class : IssueType, filename : &str, loc : &Loc, message : &str) { - eprintln!(); - let file = fs::File::open(filename) - .expect("Invalid filename for error message."); - let line = BufReader::new(file).lines().nth((loc.line - 1) as usize) - .expect(&format!("Line ({}) does not exist, file is too short.", loc.line)) - .expect("Could not get line."); - - let formatted = unindent(message).split('\n').collect::<Vec<&str>>().join("\n "); - eprintln!("{}{} {}", "issue".bold().red(), ":".white(), formatted.bold()); - eprint!("{}", "".clear()); - eprintln!(" ==> {class} in (`{file}`:{line}:{col}):\n{space}|\n{line_str}| {stuff}", - class=class.to_string().bold(), file=filename, line=loc.line, - col=loc.col, space=" ".repeat(5), - line_str=format!("{: >4} ", loc.line.to_string().bold()), stuff=line); - eprintln!("{space}|{: >offset$}", - "^".repeat(loc.span as usize).yellow().bold(), space=" ".repeat(5), - offset=((loc.col + loc.span) as usize)); -} - -pub fn token_issue(class : IssueType, filename : &str, token : &Token, message : &str) { - loc_issue(class, filename, &token.location, message); -} - -pub fn line_issue(class : IssueType, filename : &str, line_n : usize, message : &str) { - eprintln!(); - let file = fs::File::open(filename) - .expect("Invalid filename for error message."); - let line = BufReader::new(file).lines().nth((line_n - 1) as usize) - .unwrap() - .unwrap(); - - let formatted = unindent(message).split('\n').collect::<Vec<&str>>().join("\n "); - eprintln!("{}{} {}", "issue".bold().red(), ":".white(), formatted.bold()); - eprint!("{}", "".clear()); - eprintln!(" ==> {class} in (`{file}`:{line}):\n{space}|\n{line_str}| {stuff}", - class=class.to_string().bold(), file=filename, line=line_n, - space=" ".repeat(5), - line_str=format!("{: >4} ", line_n.to_string().bold()), stuff=line); - eprintln!(" |"); -} - -#[macro_export] -macro_rules! warn { - ($type:ident, $file:expr, err::LINE, $line:expr, $message:expr) => {{ - err::line_issue(err::IssueType::$type, - $file, $line, $message); - }}; - ($type:ident, $file:expr, err::LINE, $line:expr, $message:expr, $($form:expr),*) => {{ - err::line_issue(err::IssueType::$type, - $file, $line, &format!($message, $($form),*)); - }}; - ($type:ident, $file:expr, err::LOC, $loc:expr, $message:expr) => {{ - err::loc_issue(err::IssueType::$type, - $file, $loc, $message); - }}; - ($type:ident, $file:expr, err::LOC, $loc:expr, $message:expr, $($form:expr),*) => {{ - err::loc_issue(err::IssueType::$type, - $file, $loc, &format!($message, $($form),*)); - }}; - ($type:ident, $file:expr, $token:expr, $message:expr) => {{ - err::token_issue(err::IssueType::$type, - $file, $token, $message); - }}; - ($type:ident, $file:expr, $token:expr, $message:expr, $($form:expr),*) => {{ - err::token_issue(err::IssueType::$type, - $file, $token, &format!($message, $($form),*)); - }}; -} - -#[macro_export] -macro_rules! issue { - ($type:ident, $($args:tt)*) => {{ - warn!($type, $($args)*); - std::process::exit(1) - }}; -} diff --git a/src/issue.rs b/src/issue.rs @@ -0,0 +1,229 @@ +#![allow(non_camel_case_types)] +#![allow(clippy::pub_enum_variant_names)] + +use crate::site::{Site, Location}; + +use std::fs; +use std::fmt; +use std::io::{BufRead, BufReader}; + +use colored; +use colored::*; + +use unindent::unindent; + +#[derive(Clone, Copy)] +pub enum Kind { + LexError, LexWarn, + ParseError, ParseWarn, + TypeError, TypeWarn, + CompError, CompWarn +} + +#[derive(Clone)] +pub struct Issue { + pub kind : Kind, + pub site : Site, + pub message : String, + note_message : Option<String>, + pub is_fatal : bool, +} + +impl fmt::Display for Kind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let printable = match *self { + Kind::LexError => "Lexicographical Error".red(), + Kind::ParseError => "Grammar Error".red(), + Kind::TypeError => "Typing Error".red(), + Kind::CompError => "Compilation Error".red(), + Kind::LexWarn => "Lexicographical Warning".yellow(), + Kind::ParseWarn => "Grammar Warning".yellow(), + Kind::TypeWarn => "Typing Warning".yellow(), + Kind::CompWarn => "Compilation Warning".yellow(), + }; + write!(f, "{}", printable) + } +} + +impl Issue { + #[must_use = "Issue must be displayed"] + pub fn new(kind : Kind, site : Site, fmt_msg : String) -> Self { + Self { + kind, + site: site.clone(), + note_message: None, + message: unindent(&fmt_msg) + .split('\n') + .collect::<Vec<&str>>() + .join("\n "), + is_fatal: false + } + } + + #[must_use = "Issue must be displayed"] + pub fn fatal(mut self) -> Self { + self.is_fatal = true; + self + } + + #[must_use = "Issue must be displayed"] + pub fn note(mut self, msg : &str) -> Self { + self.note_message = Some(msg.to_owned()); + self + } + + pub fn print(self) -> Self { + unsafe { + crate::PANIC_MESSAGE = "Compilation could not continue."; + }; + + eprintln!("\n{}", self); + if self.is_fatal { + std::process::exit(1); + } + self + } + + pub fn crash_and_burn(self) -> ! { + self.print(); + std::process::exit(1) + } +} + +impl fmt::Display for Issue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f, "{}{} {}", + "issue".bold().red(), + ":".white(), + self.message.bold())?; + write!(f, "{}", "".clear())?; + + write!(f, " ==> {kind}", + kind = self.kind.to_string().bold())?; + + let mut got_file = false; + + if let Some(path) = &self.site.path { + got_file = true; + write!(f, " in ({}", path.to_string_lossy())?; + } else if self.site.repl { + got_file = true; + write!(f, " in (<REPL>")?; + } + + if let Some(line) = self.site.location.line { + if got_file { + write!(f, ":{}", line)?; + } else { + got_file = true; + write!(f, " in ({}", line)?; + } + } + + if let Some(column) = self.site.location.column { + if got_file { + write!(f, ":{}", column)?; + } else { + got_file = true; + write!(f, " at (column: {}", column)?; + } + } + + if got_file { + write!(f, ")")?; + } + + let indent = 5; + + let mut opened_file = false; + let mut multi_line = false; + if let Some(path) = &self.site.path { + let file_result = fs::File::open(path); + if let Ok(file) = file_result { + opened_file = true; + if let Location { + line: Some(line), + lines: Some(lines), + .. + } = self.site.location { + if lines == 1 { + let mut line_content = if let Some(Ok(line_str)) = + BufReader::new(file) + .lines().nth(line - 1) { + line_str + } else { + "[**] THIS LINE DOES NOT EXIST! \ + Either the file was deleted, \ + or this is a bug!".to_string() + }; + if let Location { + column: Some(column), + last_column: Some(last_column), + .. + } = self.site.location { + let (i, j) = (column - 1, last_column - 1); + line_content = format!("{}{}{}", + &line_content[..i], + &line_content[i..j] + .white().bold(), + &line_content[j..]); + } + writeln!(f, ":\n{space}|\n{line_num}| {line_str}", + space = " ".repeat(indent), + line_num = format!("{: >4} ", line).bold(), + line_str = line_content)?; + } else { // Error spans multiple lines + multi_line = true; + // TODO: Display the lines. + } + } + } + } + + if let Some(column) = self.site.location.column { + if opened_file { + if multi_line { + // TODO: Show the arrows for the longest line in the + // multiple lines. + } else { + let columns = self.site.location.columns + .unwrap_or(1); + writeln!(f, "{space}|{: >offset$}", + "^".repeat(columns).yellow().bold(), + space=" ".repeat(indent), + offset=(column + columns))?; + } + } + } + + Ok(()) + } +} + +#[macro_export] +macro_rules! issue { + ($type:ident, $site:expr, $message:expr) => { + #[must_use = "Issue must be displayed"] { + issue::Issue::new(issue::Kind::$type, $site.clone(), + String::from($message)) + } + }; + + ($type:ident, $site:expr, $message:expr, $($form:expr),*) => { + #[must_use = "Issue must be displayed"] { + issue::Issue::new(issue::Kind::$type, $site.clone(), + format!($message, $($form),*)) + } + }; +} + +#[macro_export] +macro_rules! fatal { + ($type:ident, $($args:tt)*) => { + #[must_use = "Issue must be displayed"] { + let mut value = issue!($type, $($args)*); + value.is_fatal = true; + value + } + }; +} diff --git a/src/lib.rs b/src/lib.rs @@ -6,13 +6,18 @@ #![allow(clippy::single_match)] #![allow(clippy::new_ret_no_self)] +#![feature(stmt_expr_attributes)] + include!(concat!(env!("OUT_DIR"), "/version.rs")); pub const VERSION : (u8, u8, u8) = read_version(); -/// Error messages. +/// Source code sites (location, line, filename, etc.). +pub mod site; + +/// Issue messages (warnings, errors, info, etc.). #[macro_use] -pub mod err; +pub mod issue; /// Syntax submodule, responsible for lexical analysis, /// parsing and static analysis. @@ -22,10 +27,12 @@ pub mod syntax; /// instructions for the Brokkr VM, and marshals the instructions. pub mod compiler; +/// Parses the contents of a file with path `filename : &str`. pub fn parse(filename : &str) -> syntax::ast::Root { syntax::parse_file(filename) } +/// Compile the parse tree. pub fn compile(root : &syntax::ast::Root) -> compiler::block::LocalBlock { let mut code_block = compiler::block::LocalBlock::new("<main>", &root.filename); @@ -39,3 +46,26 @@ pub fn compile(root : &syntax::ast::Root) -> compiler::block::LocalBlock { pub fn binary_blob(block : &compiler::block::LocalBlock) -> Vec<u8> { compiler::marshal::generate_binary(block) } + +// Set panic message for compiler bugs and issue messages. +use std::panic; +use colored::*; + +pub static mut PANIC_MESSAGE : &str = ""; + +pub fn set_panic() { + unsafe { + panic::set_hook(Box::new(|msg| { + if PANIC_MESSAGE.is_empty() { + eprintln!("\n{}", "The compiler panicked! This is a bug." + .white().bold()); + eprintln!("{} {}\n", ">>>".blue(), msg.to_string().white()); + } else { + eprintln!(" {} {} {}", + "::".white().bold(), + "Halt".blue().bold(), + PANIC_MESSAGE.white()); + } + })); + } +} diff --git a/src/site.rs b/src/site.rs @@ -0,0 +1,114 @@ +use std::path::PathBuf; + +/// Location from within source. +#[derive(Clone, Copy)] +pub struct Location { + /// Specific line (first line). + pub line : Option<usize>, + /// First column on first line. + pub column : Option<usize>, + /// Last column on the last line. + pub last_column: Option<usize>, + + /// Number of lines it spans. + pub lines : Option<usize>, + /// Number of columns it spans (according to unicode character width). + pub columns : Option<usize>, + /// Number of bytes the selection spans (includes line-feeds). + pub span : Option<usize>, + + /// Amount of bytes from the beginning of the file to the first column. + pub byte_offset : Option<usize>, +} + +/// Only to be used for fake nodes. +pub const NULL_LOCATION : Location = Location { + line: None, + column: None, + last_column: None, + lines: None, + columns: None, + span: None, + byte_offset: None, +}; + +/// Describes exactly where the source comes from. +#[derive(Clone)] +pub struct Site { + /// Source may or may not come from a file. + pub path : Option<PathBuf>, + /// Source is from a REPL instance. + pub repl : bool, + + /// The specific piece of source / AST node may come from + /// a specific column and line from within the file. + pub location : Location, + + /// Is the node in a real location? + pub fake : bool, +} + +pub const FAKE_SITE : Site = Site { + path: None, + repl: false, + location: NULL_LOCATION, + fake: true, +}; + +impl Location { + /// Last line in selection. + #[inline] + pub fn last_line(&self) -> Option<usize> { + match self.line { + Some(line) => if let Some(lines) = self.lines { + Some(line + lines - 1) + } else { None } + None => None + } + } + + /// Number of characters from the last column to the beginning of file, + /// (offset in bytes from the top of file to last column). + #[inline] + pub fn eos(&self) -> Option<usize> { + match self.byte_offset { + Some(bof) => if let Some(span) = self.span { + Some(bof + span) + } else { None } + None => None + } + } +} + +impl std::default::Default for Location { + fn default() -> Self { NULL_LOCATION } +} + +impl Site { + pub fn new() -> Self { + Self::default() + } + + pub fn single_line(line : usize, col : usize, + cols : usize, bytes : usize, ptr : usize) -> Self { + let mut s = Self::default(); + s.location.line = Some(line); + s.location.column = Some(col); + s.location.columns = Some(cols); + s.location.span = Some(bytes); + s.location.lines = Some(1); + s.location.last_column = Some(col + cols); + s.location.byte_offset = Some(ptr); + s + } + + pub fn with_filename(&self, name : &str) -> Self { + let mut s = self.clone(); + s.path = Some(PathBuf::from(name)); + s + } +} + +impl std::default::Default for Site { + fn default() -> Self { FAKE_SITE } +} diff --git a/src/syntax/analysis/constant_fold.rs b/src/syntax/analysis/constant_fold.rs @@ -23,11 +23,11 @@ fn const_fold(node : &Nodes) -> Nodes { callee: Box::new(const_fold(&*call.callee.call().unwrap().callee)), operands: vec![left.clone()], return_type: call.callee.yield_type(), - location: call.callee.call().unwrap().location + site: call.callee.call().unwrap().site.clone() })), operands: vec![right.clone()], return_type: call.return_type.clone(), - location: call.location + site: call.site.clone() }); let is_num_left = left.num().is_some(); @@ -50,7 +50,7 @@ fn const_fold(node : &Nodes) -> Nodes { return def; } }; - return Nodes::Num(ast::NumNode { value, location: call.location }); + return Nodes::Num(ast::NumNode { value, site: call.site.clone() }); } else { return def; } @@ -59,7 +59,7 @@ fn const_fold(node : &Nodes) -> Nodes { callee: Box::new(const_fold(&*call.callee)), operands: vec![const_fold(&call.operands[0])], return_type: call.return_type.clone(), - location: call.location + site: call.site.clone() }); } return node.to_owned(); diff --git a/src/syntax/analysis/type_balancer.rs b/src/syntax/analysis/type_balancer.rs @@ -11,11 +11,11 @@ fn create_cast(node : &Nodes, cast : &ast::StaticTypes) -> Nodes { let mut cast_node = ast::CallNode::new( ast::CallNode::new( - ast::IdentNode::new("cast", node.location()), + ast::IdentNode::new("cast", node.site()), vec![node.clone()], - node.location()), - vec![ast::SymNode::new(to_type, node.location())], - node.location()); + node.site()), + vec![ast::SymNode::new(to_type, node.site())], + node.site()); if let Nodes::Call(ref mut call) = cast_node { call.set_return_type(cast.clone()) } @@ -61,15 +61,15 @@ fn balance_types(node : &Nodes) -> Nodes { ast::CallNode::new( *call.callee.clone(), vec![create_cast(&right, &cast_to)], - call.callee.location()) + call.callee.site()) } else { ast::CallNode::new( ast::CallNode::new( *call.callee.call().unwrap().callee.clone(), vec![create_cast(&left, &cast_to)], - call.callee.location()), + call.callee.site()), vec![right], - call.location) + call.site.clone()) }; if let Nodes::Call(ref mut c) = new_call { c.set_return_type(cast_to); @@ -96,7 +96,7 @@ fn balance_types(node : &Nodes) -> Nodes { let mut new_call = ast::CallNode::new( *call.callee.clone(), vec![create_cast(&right, &left_yield)], - call.callee.location()); + call.callee.site()); if let Nodes::Call(ref mut c) = new_call { c.set_return_type(left_yield); } @@ -106,7 +106,7 @@ fn balance_types(node : &Nodes) -> Nodes { let mut non_bi = ast::CallNode::new( balance_types(&*call.callee), vec![balance_types(&call.operands[0])], - call.callee.location()); + call.callee.site()); if let Nodes::Call(ref mut c) = non_bi { c.set_return_type(call.return_type.clone()); } diff --git a/src/syntax/analysis/type_checker.rs b/src/syntax/analysis/type_checker.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::err; +use crate::issue; use super::ast; use ast::Nodes; @@ -23,7 +23,7 @@ impl TypeChecker { pub fn type_branch(&mut self, node : &Nodes) -> Nodes { let mut clone = node.to_owned(); - self.source_line = clone.location().line as usize; + self.source_line = clone.location().line.unwrap(); match clone { Nodes::File(f) => self.source_file = f.filename, Nodes::Ident(ref mut i) => { @@ -59,12 +59,12 @@ impl TypeChecker { } else { // Error: We need the left to be an ident. issue!(ParseError, - self.source_file.as_str(), - err::LINE, self.source_line, + callee.operands[0].site().with_filename(&self.source_file), "The left side of the member-of operator (`:`), must be an identifier. You supplied a type of `{}'. Only variable names can be declared as being members of sets.", - callee.operands[0].node_type()); + callee.operands[0].node_type()) + .print(); } }, "=" => { @@ -90,10 +90,11 @@ impl TypeChecker { let base_node = operands.remove(0); if base_node.ident().is_none() { issue!(ParseError, - &self.source_file, err::LINE, self.source_line, + base_node.site().with_filename(&self.source_file), "Function definitions must have the defining function's base caller be an identifier! You're trying to define a function that has - `{}' as base caller...", base_node.node_type()); + `{}' as base caller...", base_node.node_type()) + .print(); } let maybe_type = self.ident_map.get(&base_node.ident().unwrap().value); @@ -103,11 +104,11 @@ impl TypeChecker { println!("{:?}", self.ident_map); } issue!(TypeError, - self.source_file.as_str(), - err::LINE, self.source_line, + base_node.site().with_filename(&self.source_file), "Cannot find type annotation for the function definition of `{}'.", - base_node.ident().unwrap().value); + base_node.ident().unwrap().value) + .print(); } let mut t = maybe_type.unwrap().clone(); diff --git a/src/syntax/analysis/type_resolver.rs b/src/syntax/analysis/type_resolver.rs @@ -9,7 +9,7 @@ use super::type_balancer; use lazy_static::lazy_static; use std::collections::HashSet; -use crate::err; +use crate::issue; #[derive(Debug, Clone, PartialEq, Eq, Hash)] struct SymbolEntry { @@ -162,10 +162,11 @@ pub fn resolve_branch(&mut self, branch : &Nodes) -> Nodes { let signatures = table.collect_signatures(&ident.value); if signatures.len() > 1 { // TODO: Partial application not considered. - issue!(ParseError, &self.filename, - err::LOC, &ident.location, + issue!(ParseError, + ident.site.with_filename(&self.filename), "Variable has multiple type signatures. Overloading \ - types is only possible with functions."); + types is only possible with functions.") + .print(); } // We can unwrap this because we know it contains exactly // one (1) element. @@ -173,7 +174,8 @@ pub fn resolve_branch(&mut self, branch : &Nodes) -> Nodes { // Give the identifier it's signature. ident.static_type = signature.clone(); } else { // Variable has not been declared. - issue!(ParseError, &self.filename, err::LOC, &ident.location, + issue!(ParseError, + ident.site.with_filename(&self.filename), "Variable `{}' is used, but has not been declared.", &ident.value); } @@ -190,7 +192,7 @@ pub fn resolve_branch(&mut self, branch : &Nodes) -> Nodes { ":" => { self.resolve_annotation(appl_0_clone, appl_1.clone()); // FIXME: Should we really replace the annotation with a nil? - // I know it isn't useful anymore, but maybe we should keep it + // I know it isn't useful any more, but maybe we should keep it // and just ignore it when compiling. Returning nil might // add complexity to the rest of the type checking. @@ -229,7 +231,7 @@ pub fn resolve_branch(&mut self, branch : &Nodes) -> Nodes { }} // Any call should resolve its callee type, and check if it is legal // to apply an operand of such a (resolved) type. - // This entier call expression must thus also be typed, unrolling + // This entire call expression must thus also be typed, unrolling // the type from the callee. if skip_type_check { return node; @@ -249,9 +251,10 @@ pub fn resolve_branch(&mut self, branch : &Nodes) -> Nodes { if maybe_op_inner_type.is_none() { // Fatal, we should really never get here, // because we _should_ check for this earlier. - issue!(TypeError, &self.filename, - err::LOC, &(*appl_0.callee).location(), - "Function should map from a set, it does not."); + issue!(TypeError, + (*appl_0.callee).site().with_filename(&self.filename), + "Function should map from a set, it does not.") + .print(); } // Safe to unwrap, we've checked for none. @@ -264,14 +267,15 @@ pub fn resolve_branch(&mut self, branch : &Nodes) -> Nodes { // This should be done on 'Int' cast up to 'Real' // (if a Real was expected, and got an Int), for // example. - // We should alos emit a warning (always?) when + // We should also emit a warning (always?) when // an implicit cast has taken place. - issue!(TypeError, &self.filename, - err::LOC, &appl_0.operands[0].location(), - "Mismatching type in function call. - Expected argument of element \ - of `{}', instead got a `{}'.", - op_inner_type, op_0_st); + issue!(TypeError, + appl_0.operands[0].site().with_filename(&self.filename), + "Mismatching type in function call. + Expected argument of element \ + of `{}', instead got a `{}'.", + op_inner_type, op_0_st) + .print(); } // If so, we can continue to unroll the type and // assign it to this expression. @@ -280,18 +284,19 @@ pub fn resolve_branch(&mut self, branch : &Nodes) -> Nodes { // a type of element of box_ret_t. let return_type = (*box_ret_t).set_inner(); if return_type.is_none() { - // Fatal, see simlar comment above. - issue!(TypeError, &self.filename, - err::LOC, &(*appl_0.callee).location(), - "Function should map to a set, it does not."); + // Fatal, see similar comment above. + issue!(TypeError, + (*appl_0.callee).site().with_filename(&self.filename), + "Function should map to a set, it does not.") + .print(); } appl_0.return_type = return_type.unwrap().clone(); } else { - issue!(TypeError, &self.filename, - err::LOC, &appl_0.callee.location(), - "Function-application / juxtaposition is not \ - defined on type of `{}'.", - appl_0_st); + issue!(TypeError, + appl_0.callee.site().with_filename(&self.filename), + "Function-application / juxtaposition is not \ + defined on type of `{}'.", appl_0_st) + .print(); } } @@ -318,7 +323,7 @@ fn resolve_assignment(&mut self, let filename = &self.filename.to_owned(); let lhs = &appl_1.operands[0]; - // Handle variable (identifier) assignemnt: + // Handle variable (identifier) assignment: if let Nodes::Ident(ident_op_1) = lhs { // Recursively resolve RHS of assignment. appl_0.operands[0] = self.resolve_branch(&appl_0.operands[0]); @@ -344,19 +349,20 @@ fn resolve_assignment(&mut self, if entries.len() == 1 { // Not overloaded. let ref mut entry = entries[0]; // Check entry matches type of RHS - // of assigment. + // of assignment. // TODO: Check if types can be coerced. let rhs_type = appl_0.operands[0].yield_type(); if rhs_type != entry.signature { // TODO: Can cast? if so, do // and don't throw an error. - issue!(TypeError, filename, - err::LOC, &appl_0.operands[0].location(), - "Signature does not match \ - right-hand-side of assignemnt. - Expected `{}', got `{}'.", - entry.signature, rhs_type); + issue!(TypeError, + appl_0.operands[0].site().with_filename(filename), + "Signature does not match \ + right-hand-side of assignment. + Expected `{}', got `{}'.", + entry.signature, rhs_type) + .print(); } // Otherwise, all is fine, // and we can update whether it has @@ -375,23 +381,26 @@ fn resolve_assignment(&mut self, let base_call = call_op_1.base_call(); if !base_call.is_ident() { // Fatal, we must define the call on some sort of ident. - issue!(ParseError, &self.filename, - err::LOC, &base_call.location(), - "You have to assign to a call on an identifer, - this identifier is the function you are defining. - Expected an `identifier', found `{}'!", - base_call.node_type()); + issue!(ParseError, + base_call.site().with_filename(&self.filename), + "You have to assign to a call on an identifier, + this identifier is the function you are defining. + Expected an `identifier', found `{}'!", + base_call.node_type()) + .print(); } // We've checked, and we may unwrap it. let base_call = base_call.ident().unwrap(); // TODO Continue, collect the arguments too!!! } else { - // TODO: Assigment to functions. + // TODO: Assignment to functions. // TODO: Pattern matching etc. + issue!(ParseError, - &self.filename, err::LOC, &appl_1.operands[0].location(), - "Cannot assign to `{}' structure.", - appl_1.operands[0].node_type()); + appl_1.operands[0].site().with_filename(&self.filename), + "Cannot assign to `{}' structure.", + appl_1.operands[0].node_type()) + .print(); } return appl_0; @@ -407,21 +416,24 @@ fn resolve_annotation(&mut self, appl_0 : ast::CallNode, appl_1 : ast::CallNode) self.current_table().push( &op_id_1.value, *signature, false); } else { - issue!(TypeError, &self.filename, err::LOC, &op_0.location(), - "Right of type annotation must be a set. \ - Instead got `{}`.", set_signature); + issue!(TypeError, + op_0.site().with_filename(&self.filename), + "Right of type annotation must be a set; \ + instead got type of `{}'.", set_signature) + .print(); } } else { - issue!(ParseError, &self.filename, - err::LOC, &op_1.location(), + issue!(ParseError, + op_1.site().with_filename(&self.filename), "Left of `:` type annotator must be \ - an identifier; found `{}'.", op_1.node_type()); + an identifier; found `{}'.", op_1.node_type()) + .print(); } } else { issue!(ParseError, - &self.filename, err::LOC, - &appl_1.location, - "No expression found left of `:`."); + appl_1.site.with_filename(&self.filename), + "No expression found left of `:`.") + .print(); } } } diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs @@ -1,8 +1,7 @@ use std::{fmt, ops}; use std::collections::VecDeque; -use super::location; -use location::Loc; +use crate::site::{Site, Location}; /// Identifiers, node representing a name that /// will represent a value stored. @@ -15,7 +14,7 @@ pub struct IdentNode { pub static_type : StaticTypes, /// Source location. - pub location : Loc, + pub site : Site, } /// Different types of possible number types in the language. @@ -198,7 +197,7 @@ pub struct NumNode { pub value : Numerics, /// Source location. - pub location : Loc, + pub site : Site, } @@ -209,7 +208,7 @@ pub struct StrNode { pub value : String, /// Source location. - pub location : Loc, + pub site : Site, } /// Symbol Node. @@ -220,7 +219,7 @@ pub struct SymNode { pub value : String, /// Source location. - pub location : Loc, + pub site : Site, } /// Call Node has a pointer to the callee node @@ -236,7 +235,7 @@ pub struct CallNode { pub return_type : StaticTypes, /// Source location. - pub location : Loc, + pub site : Site, } /// Represents a block of code / compound statements @@ -247,20 +246,20 @@ pub struct BlockNode { pub statements : Vec<Nodes>, /// Source location. - pub location : Loc, + pub site : Site, } #[derive(Clone)] pub struct FileNode { pub filename : String, /// Source location. - pub location : Loc, + pub site : Site, } #[derive(Clone)] pub struct NilNode { /// Source location. - pub location : Loc, + pub site : Site, } /// All base types, determined at compile time. @@ -328,7 +327,7 @@ impl fmt::Display for StaticTypes { ss.as_str() }, StaticTypes::TNil => "nothing", - StaticTypes::TUnknown => "anything", + StaticTypes::TUnknown => "unknown", }; write!(f, "{}", s) } @@ -383,18 +382,23 @@ macro_rules! unwrap_enum { impl Nodes { - pub fn location(&self) -> Loc { + pub fn site(&self) -> Site { match self { - Nodes::Ident(n) => n.location, - Nodes::Call(n) => n.location, - Nodes::Num(n) => n.location, - Nodes::Str(n) => n.location, - Nodes::Sym(n) => n.location, - Nodes::Nil(n) => n.location, - Nodes::Block(n) => n.location, - Nodes::File(n) => n.location, + Nodes::Ident(n) => n.site.to_owned(), + Nodes::Call(n) => n.site.to_owned(), + Nodes::Num(n) => n.site.to_owned(), + Nodes::Str(n) => n.site.to_owned(), + Nodes::Sym(n) => n.site.to_owned(), + Nodes::Nil(n) => n.site.to_owned(), + Nodes::Block(n) => n.site.to_owned(), + Nodes::File(n) => n.site.to_owned(), } } + + pub fn location(&self) -> Location { + self.site().location + } + /// Function that returns the statically known type /// of any syntactic node generated. pub fn yield_type(&self) -> StaticTypes { @@ -466,7 +470,7 @@ impl Nodes { Nodes::Str(_) => "string literal", Nodes::Sym(_) => "symbol", Nodes::Nil(_) => "nothing", - Nodes::Call(_) => "function call", + Nodes::Call(_) => "application", Nodes::Block(_) => "code block", _ => "ungrammatical meta node" } @@ -499,7 +503,7 @@ impl Nodes { pub fn is_file(&self) -> bool { self.file().is_some() } pub fn is_nil(&self) -> bool { self.nil().is_some() } - + pub fn is_atomic(&self) -> bool { match self { @@ -521,39 +525,39 @@ impl Nodes { } impl IdentNode { - pub fn new(value : &str, location : Loc) -> Nodes { + pub fn new(value : &str, site : Site) -> Nodes { Nodes::Ident(IdentNode { value: value.to_string(), static_type: StaticTypes::TUnknown, - location + site }) } } impl NumNode { - pub fn new<Num : ToNumeric>(number : Num, location : Loc) -> Nodes { + pub fn new<Num : ToNumeric>(number : Num, site : Site) -> Nodes { let value = number.to_numeric(); - Nodes::Num(NumNode { value, location }) + Nodes::Num(NumNode { value, site }) } } impl StrNode { - pub fn new(value : &str, location : Loc) -> Nodes - { Nodes::Str(StrNode { value: value.to_string(), location }) } + pub fn new(value : &str, site : Site) -> Nodes + { Nodes::Str(StrNode { value: value.to_string(), site }) } } impl SymNode { - pub fn new(value : &str, location : Loc) -> Nodes - { Nodes::Sym(SymNode { value: value[1..].to_string(), location }) } + pub fn new(value : &str, site : Site) -> Nodes + { Nodes::Sym(SymNode { value: value[1..].to_string(), site }) } } impl CallNode { - pub fn new(callee : Nodes, operands : Vec<Nodes>, location : Loc) -> Nodes { + pub fn new(callee : Nodes, operands : Vec<Nodes>, site : Site) -> Nodes { Nodes::Call(CallNode { callee: Box::new(callee), operands, return_type: StaticTypes::TUnknown, - location + site }) } @@ -613,12 +617,12 @@ impl CallNode { } impl FileNode { - pub fn new(filename : String, location : Loc) -> Nodes - { Nodes::File(FileNode { filename, location }) } + pub fn new(filename : String, site : Site) -> Nodes + { Nodes::File(FileNode { filename, site }) } } impl NilNode { - pub fn new(location : Loc) -> Nodes { Nodes::Nil(NilNode { location }) } + pub fn new(site : Site) -> Nodes { Nodes::Nil(NilNode { site }) } } /// Root branch of the AST. diff --git a/src/syntax/lexer.rs b/src/syntax/lexer.rs @@ -1,10 +1,8 @@ -use crate::err; +use crate::{issue, site::Site}; use super::token; use token::{Token, TokenType}; -use super::location; - use std::collections::VecDeque; use lazy_static::lazy_static; use regex::Regex; @@ -39,7 +37,7 @@ impl RegexExt for Regex { } } -/// All chars that may constitue an ident. +/// All chars that may constitute an ident. const IDENT_CHARS : &str = r"\p{L}\?!'\-_"; // TODO: Parse symbols with spaces? `:"..."` syntax. @@ -55,12 +53,14 @@ macro_rules! try_match { $reg:expr, $token_type:expr, $current_char_ptr:expr, $line:expr, $col:expr) => { if let Some(matched) = $reg.first_match($partial) { - let span = matched.width() as u32; + let width = matched.width(); + let bytes = matched.len(); $stream.push_back(Token::new( $token_type, &matched, - location::new($line, $col, span))); - $current_char_ptr += matched.len(); - $col += span; + Site::single_line($line, $col, + width, bytes, $current_char_ptr))); + $current_char_ptr += bytes; + $col += width; $stream.back() } else { None @@ -77,8 +77,8 @@ pub fn lex(string : &str, filename : &str) -> VecDeque<Token> { let string_size = string.bytes().count(); let mut partial : &str; - let mut line = 1; - let mut col = 1; + let mut line : usize = 1; + let mut col : usize = 1; // Step through while current_char_ptr < string_size { @@ -90,7 +90,6 @@ pub fn lex(string : &str, filename : &str) -> VecDeque<Token> { continue; } - let two_chars = partial.get(0..2).unwrap_or("\0\0"); // Consume EON comment: @@ -98,7 +97,9 @@ pub fn lex(string : &str, filename : &str) -> VecDeque<Token> { let old_char_ptr = current_char_ptr; current_char_ptr += if two_chars == "--" { 2 } else { 1 }; loop { - let current_char = string.bytes().nth(current_char_ptr).unwrap_or(b'\0'); + let current_char = string.bytes() + .nth(current_char_ptr) + .unwrap_or(b'\0'); if current_char == b'\n' || current_char == b'\0' { break; } @@ -106,7 +107,7 @@ pub fn lex(string : &str, filename : &str) -> VecDeque<Token> { } col += string.get(old_char_ptr..current_char_ptr) .expect("Comment ended or started not on char boundary.") - .width() as u32; + .width(); continue; } @@ -119,7 +120,8 @@ pub fn lex(string : &str, filename : &str) -> VecDeque<Token> { if let Some(tt) = vec_brack { token_stream.push_back(Token::new( tt, two_chars, - location::new(line, col, 2))); + Site::single_line(line, col, + 2, 2, current_char_ptr))); col += 2; current_char_ptr += 2; continue; @@ -128,14 +130,15 @@ pub fn lex(string : &str, filename : &str) -> VecDeque<Token> { if two_chars == ": " { token_stream.push_back(Token::new( TokenType::Op, ":", - location::new(line, col, 1))); + Site::single_line(line, col, + 1, 2, current_char_ptr))); col += 2; current_char_ptr += 2; continue; } let first_char = partial.chars().nth(0) - .expect("Empty program was trying to be lexed."); // This should't happen. + .expect("Empty program was trying to be lexed."); // This shouldn't happen. let single_char_token = match first_char { '(' => Some(TokenType::LParen), @@ -151,7 +154,8 @@ pub fn lex(string : &str, filename : &str) -> VecDeque<Token> { if let Some(tt) = single_char_token { token_stream.push_back(Token::new( tt, &first_char.to_string(), - location::new(line, col, 1))); + Site::single_line(line, col, + 1, 1, current_char_ptr))); if first_char == '\n' { line += 1; col = 1; @@ -168,7 +172,8 @@ pub fn lex(string : &str, filename : &str) -> VecDeque<Token> { let mut eos = false; let mut i = 1; let old_col = col; - while !eos { // Spaghet + let old_char_ptr = current_char_ptr; + while !eos { // Spaghetti if let Some(character) = partial.chars().nth(i) { if character == '"' { current_char_ptr += 1; @@ -184,11 +189,15 @@ pub fn lex(string : &str, filename : &str) -> VecDeque<Token> { 'b' => String::from("\x08"), '0' => String::from("\0"), 'x' => { - if let Some(code) = partial.get((current_char_ptr + 2)..(current_char_ptr + 4)) { + if let Some(code) = partial + .get((current_char_ptr + 2) + ..(current_char_ptr + 4)) { i += 2; col += 2; current_char_ptr += 2; - (u8::from_str_radix(code, 16).expect("Malformed hex.") as char).to_string() + (u8::from_str_radix(code, 16) + .expect("Malformed hex.") as char) + .to_string() } else { String::new() } } c => c.to_string() @@ -200,18 +209,18 @@ pub fn lex(string : &str, filename : &str) -> VecDeque<Token> { continue; } else { eos = true; - // Error: Unexpected EOS! + // TODO Error: Unexpected EOS! } } else { contents.push(character); i += 1; - col += character.width().unwrap_or(2) as u32; + col += character.width().unwrap_or(2); current_char_ptr += character.len_utf8(); continue; } } else { eos = true; - // Error: Unexpected EOS! + // TODO Error: Unexpected EOS! } i += 1; current_char_ptr += 1; @@ -219,7 +228,10 @@ pub fn lex(string : &str, filename : &str) -> VecDeque<Token> { } token_stream.push_back(Token::new( TokenType::Str, &contents, - location::new(line, old_col, col - old_col))); + Site::single_line(line, old_col, + col - old_col, + current_char_ptr - old_char_ptr, + old_char_ptr))); continue; } @@ -243,9 +255,10 @@ pub fn lex(string : &str, filename : &str) -> VecDeque<Token> { current_char_ptr, line, col); if let Some(token) = matched { if two_chars == ":)" { - warn!(LexWarn, filename, token, + issue!(LexWarn, token.location.with_filename(filename), "Nice smiley-face, but are you sure you wanted to \ - use a `Symbol' here? Use `:\")\"` to be more explicit."); + use a `Symbol' here? Use `:\")\"` to be more explicit.") + .print(); } continue; } @@ -254,9 +267,9 @@ pub fn lex(string : &str, filename : &str) -> VecDeque<Token> { if partial.is_char_boundary(0) { col += 1 } } - let mut last_location = location::new(0, 0, 1); + let mut last_location = Site::new(); if let Some(last_token) = token_stream.back() { - last_location = last_token.location; + last_location = last_token.location.to_owned(); } token_stream.push_back(Token::new( diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs @@ -1,29 +1,36 @@ use std::collections::VecDeque; -use super::location; use super::token; use super::ast; use super::operators; -use super::super::err; +use crate::{issue, site}; +use site::{Site, Location}; -use location::Loc; use token::{Token, TokenType}; use ast::Nodes; -fn location_range(loc_begin : &Loc, loc_end : &Loc) -> Loc { +fn location_range(loc_begin : &Location, loc_end : &Location) -> Location { let mut loc_final = loc_end.clone(); - loc_final.lines += loc_end.line - loc_begin.line; - loc_final.line = loc_begin.line; - - // TODO: Location should record character count from the - // beginning of the file, this way we can give a proper multi line - // span length. :) - if loc_final.lines == 1 { - loc_final.span += loc_end.col - loc_begin.col; - loc_final.col = loc_begin.col; + if let Location { + line: Some(ref mut line), + lines: Some(ref mut lines), + column: Some(ref mut column), + columns: Some(ref mut columns), + span: Some(ref mut span), + byte_offset: Some(ref mut byte_offset), + .. + } = loc_final { + *lines += loc_end.line.unwrap() - loc_begin.line.unwrap(); + *line = loc_begin.line.unwrap(); + *span = loc_end.eos().unwrap() + - loc_begin.byte_offset.unwrap(); + *columns = loc_end.column.unwrap() + loc_end.columns.unwrap() + - loc_begin.column.unwrap(); + *column = loc_begin.column.unwrap(); + *byte_offset = loc_begin.byte_offset.unwrap(); } loc_final @@ -42,7 +49,7 @@ struct ParseEnvironment<'a> { pub file : &'a str, ignore_newline : bool, - location: Loc, + site : Site, eof_token : Token } @@ -56,12 +63,13 @@ impl<'a> ParseEnvironment<'a> { file, ignore_newline: false, - location: location::new(1, 1, 1), + site: Site::single_line(1, 1, 1, 1, 0), } } pub fn start(&mut self) { - self.root.branches.push(ast::FileNode::new(self.file.to_owned(), self.location)); + self.root.branches.push(ast::FileNode::new( + self.file.to_owned(), self.site.clone())); let mut current = self.stream.get(0); while current.is_some() && current.unwrap().class != TokenType::EOF { @@ -82,7 +90,7 @@ impl<'a> ParseEnvironment<'a> { self.stream.push_back(self.eof_token.clone()); } let shifted = self.stream.pop_front().unwrap(); - self.location = shifted.location; + self.site = shifted.location.to_owned(); shifted } @@ -96,7 +104,7 @@ impl<'a> ParseEnvironment<'a> { // Currently this is only done in `func_apply`. fn null_den(&mut self, token : &Token) -> Nodes { - let loc = token.location; + let loc = token.location.to_owned(); match token.class { TokenType::Op | TokenType::Ident => { let is_op = self.optable.exists(&token.string); @@ -116,16 +124,16 @@ impl<'a> ParseEnvironment<'a> { if prefix.is_none() { ast::CallNode::new( ast::CallNode::new( - ast::IdentNode::new("flip", loc), + ast::IdentNode::new("flip", loc.to_owned()), vec![ast::IdentNode::new(&token.string, loc)], - self.location), + self.site.to_owned()), vec![self.expr(500)], - self.location) + self.site.to_owned()) } else { ast::CallNode::new( ast::IdentNode::new(&token.string, loc), vec![self.expr(500)], - self.location) + self.site.to_owned()) } } }; @@ -133,15 +141,26 @@ impl<'a> ParseEnvironment<'a> { ast::IdentNode::new(&token.string, loc) }, TokenType::Num => ast::NumNode::new(&*token.string, loc), - TokenType::Str => ast::StrNode::new( &token.string, loc), - TokenType::Sym => ast::SymNode::new( &token.string, loc), + TokenType::Str => ast::StrNode::new( &token.string, loc), + TokenType::Sym => ast::SymNode::new( &token.string, loc), TokenType::LParen => { let maybe_current = self.stream.get(0); if let Some(current) = maybe_current { if current.class == TokenType::RParen { self.shift(); let mut nil_loc = loc.clone(); - nil_loc.span += 1; + + if let Location { + span: Some(ref mut span), + columns: Some(ref mut columns), + .. + } = nil_loc.location { + *span += 1; + *columns += 1; + } else { + panic!("All tokens should have width."); + } + return ast::NilNode::new(nil_loc); } else if current.class == TokenType::EOF { self.expect(TokenType::RParen, maybe_current); @@ -159,9 +178,14 @@ impl<'a> ParseEnvironment<'a> { self.shift(); expr } - _ => issue!(ParseError, self.file, token, - "`{}` has no null-denotation (cannot be used as a prefix).", + _ => { + issue!(ParseError, token.location.with_filename(self.file), + "`{}` has no null-denotation.", token.class) + .note("Cannot be used as a prefix / left-of-expression.") + .print(); + ast::NilNode::new(loc) + } } } @@ -206,7 +230,7 @@ impl<'a> ParseEnvironment<'a> { // a correct number of columns (store first and last column). // Also, update `lines` to specify how many lines the function // call spans. - let first_loc = left.location(); + let first_site = left.site().clone(); let mut pushed = false; if let Nodes::Call(ref mut call) = left { @@ -220,18 +244,22 @@ impl<'a> ParseEnvironment<'a> { if pushed { return left; } let operand_node = self.expr(190); - let last_loc = operand_node.location(); - let final_loc = location_range(&first_loc, &last_loc); + let last_site = operand_node.site(); + let mut final_site = first_site.clone(); + final_site.location = location_range( + &first_site.location, + &last_site.location); - ast::CallNode::new(left, vec![operand_node], final_loc) + ast::CallNode::new(left, vec![operand_node], final_site) } fn left_den(&mut self, left : Nodes, op : operators::Operator) -> Nodes { - let left_loc = left.location(); + let left_site = left.site(); let first_apply = ast::CallNode::new( - ast::IdentNode::new(op.name, self.location), + ast::IdentNode::new(op.name, self.site.to_owned()), vec![left], - self.location); + self.site.to_owned()); + if self.stream[0].class == TokenType::RParen { return first_apply; } @@ -239,19 +267,28 @@ impl<'a> ParseEnvironment<'a> { let right = self.expr(op.precedence - (if op.is_right() { 1 } else { 0 })); - let call_loc = location_range(&left_loc, &right.location()); - ast::CallNode::new(first_apply, vec![right], call_loc) + let mut call_site = left_site.clone(); + call_site.location = location_range( + &left_site.location, + &right.location()); + ast::CallNode::new(first_apply, vec![right], call_site) } fn expect(&self, tt : TokenType, maybe_t : Option<&Token>) { if maybe_t.is_none() { - issue!(ParseError, self.file, self.stream.iter().last().unwrap(), - "Unexpected end of stream."); + fatal!(ParseError, + self.stream.iter() + .last().unwrap() + .location + .with_filename(self.file), + "Unexpected end of stream.") + .print(); } let t = maybe_t.unwrap(); if t.class != tt { - issue!(ParseError, self.file, t, - "Unexpected token type: `{}`, expected: `{}`.", t.class, tt); + fatal!(ParseError, t.location.with_filename(self.file), + "Unexpected token type: `{}`, expected: `{}`.", t.class, tt) + .print(); } } } @@ -261,45 +298,54 @@ mod test { use super::*; use ast::Numerics; + macro_rules! num_test { + ($num:expr, $site:expr, $res:expr) => { + assert_eq!( + ast::NumNode::new($num, $site.clone()).num().unwrap().value, + $res); + }; + } + #[test] fn numeric_parsing() { - let l = location::new(1, 1, 1); - assert_eq!(ast::NumNode::new(2, l).num().unwrap().value, Numerics::Natural(2usize)); - assert_eq!(ast::NumNode::new(2usize, l).num().unwrap().value, Numerics::Natural(2usize)); - assert_eq!(ast::NumNode::new(2u32, l).num().unwrap().value, Numerics::Natural(2usize)); - assert_eq!(ast::NumNode::new(2i32, l).num().unwrap().value, Numerics::Natural(2usize)); - - assert_eq!(ast::NumNode::new(-2, l).num().unwrap().value, Numerics::Integer(-2isize)); - assert_eq!(ast::NumNode::new(-2i32, l).num().unwrap().value, Numerics::Integer(-2isize)); - assert_eq!(ast::NumNode::new(-2isize, l).num().unwrap().value, Numerics::Integer(-2isize)); - - assert_eq!(ast::NumNode::new(-2.62, l).num().unwrap().value, Numerics::Real(-2.62f64)); - assert_eq!(ast::NumNode::new(2.62, l).num().unwrap().value, Numerics::Real(2.62f64)); - - assert_eq!(ast::NumNode::new("2", l).num().unwrap().value, Numerics::Natural(2)); - assert_eq!(ast::NumNode::new("325", l).num().unwrap().value, Numerics::Natural(325)); - assert_eq!(ast::NumNode::new("0b01010110", l).num().unwrap().value, Numerics::Natural(0b01010110)); - assert_eq!(ast::NumNode::new("0o721", l).num().unwrap().value, Numerics::Natural(0o721)); - assert_eq!(ast::NumNode::new("0xfa", l).num().unwrap().value, Numerics::Natural(0xfa)); - assert_eq!(ast::NumNode::new("0xf", l).num().unwrap().value, Numerics::Natural(0xf)); - assert_eq!(ast::NumNode::new("2.672", l).num().unwrap().value, Numerics::Real(2.672)); - assert_eq!(ast::NumNode::new("2.672e3", l).num().unwrap().value, Numerics::Real(2672.0)); - assert_eq!(ast::NumNode::new("2.672e+16", l).num().unwrap().value, Numerics::Real(2.672 * 10f64.powf(16f64))); - assert_eq!(ast::NumNode::new("2.672e-10", l).num().unwrap().value, Numerics::Real(2.672 * 10f64.powf(-10f64))); - assert_eq!(ast::NumNode::new("67e-4", l).num().unwrap().value, Numerics::Real(0.0067)); - assert_eq!(ast::NumNode::new("67e+10", l).num().unwrap().value, Numerics::Natural(670000000000)); - assert_eq!(ast::NumNode::new("-2", l).num().unwrap().value, Numerics::Integer(-2)); - assert_eq!(ast::NumNode::new("-325", l).num().unwrap().value, Numerics::Integer(-325)); - assert_eq!(ast::NumNode::new("-0b01010110", l).num().unwrap().value, Numerics::Integer(-0b01010110)); - assert_eq!(ast::NumNode::new("-0o721", l).num().unwrap().value, Numerics::Integer(-0o721)); - assert_eq!(ast::NumNode::new("-0xfa", l).num().unwrap().value, Numerics::Integer(-250)); - assert_eq!(ast::NumNode::new("-0xf", l).num().unwrap().value, Numerics::Integer(-15)); - assert_eq!(ast::NumNode::new("-2.672", l).num().unwrap().value, Numerics::Real(-2.672)); - assert_eq!(ast::NumNode::new("-2.672e3", l).num().unwrap().value, Numerics::Real(-2672.0)); - assert_eq!(ast::NumNode::new("-2.672e+16", l).num().unwrap().value, Numerics::Real(-26720000000000000.0)); - assert_eq!(ast::NumNode::new("-2.672e-10", l).num().unwrap().value, Numerics::Real(-0.0000000002672)); - assert_eq!(ast::NumNode::new("-67e-4", l).num().unwrap().value, Numerics::Real(-0.0067)); - assert_eq!(ast::NumNode::new("-67e+10", l).num().unwrap().value, Numerics::Integer(-670000000000)); + let l = Site::new(); + num_test!(2, l, Numerics::Natural(2usize)); + num_test!(2, l, Numerics::Natural(2usize)); + num_test!(2usize, l, Numerics::Natural(2usize)); + num_test!(2u32, l, Numerics::Natural(2usize)); + num_test!(2i32, l, Numerics::Natural(2usize)); + + num_test!(-2, l, Numerics::Integer(-2isize)); + num_test!(-2i32, l, Numerics::Integer(-2isize)); + num_test!(-2isize, l, Numerics::Integer(-2isize)); + + num_test!(-2.62, l, Numerics::Real(-2.62f64)); + num_test!(2.62, l, Numerics::Real(2.62f64)); + + num_test!("2", l, Numerics::Natural(2)); + num_test!("325", l, Numerics::Natural(325)); + num_test!("0b01010110", l, Numerics::Natural(0b01010110)); + num_test!("0o721", l, Numerics::Natural(0o721)); + num_test!("0xfa", l, Numerics::Natural(0xfa)); + num_test!("0xf", l, Numerics::Natural(0xf)); + num_test!("2.672", l, Numerics::Real(2.672)); + num_test!("2.672e3", l, Numerics::Real(2672.0)); + num_test!("2.672e+16", l, Numerics::Real(2.672 * 10f64.powf(16f64))); + num_test!("2.672e-10", l, Numerics::Real(2.672 * 10f64.powf(-10f64))); + num_test!("67e-4", l, Numerics::Real(0.0067)); + num_test!("67e+10", l, Numerics::Natural(670000000000)); + num_test!("-2", l, Numerics::Integer(-2)); + num_test!("-325", l, Numerics::Integer(-325)); + num_test!("-0b01010110", l, Numerics::Integer(-0b01010110)); + num_test!("-0o721", l, Numerics::Integer(-0o721)); + num_test!("-0xfa", l, Numerics::Integer(-250)); + num_test!("-0xf", l, Numerics::Integer(-15)); + num_test!("-2.672", l, Numerics::Real(-2.672)); + num_test!("-2.672e3", l, Numerics::Real(-2672.0)); + num_test!("-2.672e+16", l, Numerics::Real(-26720000000000000.0)); + num_test!("-2.672e-10", l, Numerics::Real(-0.0000000002672)); + num_test!("-67e-4", l, Numerics::Real(-0.0067)); + num_test!("-67e+10", l, Numerics::Integer(-670000000000)); let s : String = String::from("-6e12"); let num = ast::NumNode::new(&*s, l); diff --git a/src/syntax/token.rs b/src/syntax/token.rs @@ -1,5 +1,5 @@ use std::fmt; -use super::location; +use crate::site::Site; #[cfg(feature="debug")] use { @@ -26,7 +26,7 @@ pub enum TokenType { Num, /// Any operators, simular to idents but are lexed differently. Op, - /// Symbols, they are like elements of enums, they begin with a colon. + /// Symbols, they are like elements of C enums, they begin with a colon. Sym, /// Strings, enclosed by double quotes ("..."). Str, @@ -76,7 +76,7 @@ impl fmt::Display for TokenType { } } -/// Token structure, an individual lexiacal token, +/// Token structure, an individual lexical token, /// represented by its type/class, what it was written as /// in the program, and its location in the code. #[derive(Clone)] @@ -86,13 +86,13 @@ pub struct Token { /// What string the token matched with. pub string : String, /// Where the token is in the code. - pub location : location::Loc, + pub location : Site, } impl Token { /// Constructs a new Token structure. - pub fn new(class : TokenType, string : &str, loc : location::Loc) -> Token { - Token { class, string: String::from(string), location: loc } + pub fn new(class : TokenType, string : &str, site : Site) -> Token { + Token { class, string: String::from(string), location: site } } /// Checks if the token represents an atomic datum. diff --git a/static/build.rs b/static/build.rs @@ -0,0 +1,7 @@ +mod read_version; + +fn main() -> Result<(), Box<dyn std::error::Error>> { + read_version::out()?; + + Ok(()) +} diff --git a/static/read_version.rs b/static/read_version.rs @@ -0,0 +1,42 @@ +use std::env; +use std::fs::File; +use std::io::{Read, Write}; +use std::path::Path; +use toml; + +pub fn out() -> Result<(), Box<dyn std::error::Error>> { + let out_dir = env::var("OUT_DIR")?; + let dest_path = Path::new(&out_dir).join("version.rs"); + let mut f = File::create(&dest_path).unwrap(); + + let mut config_f = File::open("Cargo.toml")?; + let mut config_str = String::new(); + config_f.read_to_string(&mut config_str)?; + + let config: toml::Value = toml::from_str(&config_str) + .unwrap(); + + println!("{:#?}", config); + + match &config["package"]["version"] { + toml::Value::String(version) => { + if let &[major, minor, tiny] = version + .split(".") + .map(|s| s.parse::<u8>().unwrap()) + .collect::<Vec<_>>().as_slice() { + + f.write_all(format!(" + const fn read_version() -> (u8, u8, u8) {{ + return ({}, {}, {}); + }} + ", major, minor, tiny).as_bytes())?; + } else { + panic!("Version string should be three numbers \ + separated by two dots."); + } + } + _ => panic!("Version in `Config.toml' should be a string!") + } + + Ok(()) +} diff --git a/test_source.vh b/test_source.vh @@ -14,3 +14,12 @@ b = 1 - 8 -- -7 c : Real c = f (a + 1.0) (b - 0.0) +--* This should yield three errors *-- + +w : Beep -- Beep doesn't exits yet. +-- This is an error, it should be `<-` not `:`. +w = a + b : Int + +2 = 3 -- Also an error. + + diff --git a/tests/expect_fail/invalid_operations_1.vh b/tests/expect_fail/invalid_operations_1.vh @@ -0,0 +1,25 @@ +-- TODO: Test overloading with `f`. +-- TODO: Test casting from subsets of Real, upto Real. + +f : Real -> Real -> Real + +f a b = (1 + 1) * a + b -- 2a + b + +a : Nat +a = 3 + +( : ) b Int +b = 1 - 8 -- -7 + +c : Real +c = f (a + 1.0) (b - 0.0) + +--* This should yield three errors *-- + +w : Beep -- Beep doesn't exits yet. +-- This is an error, it should be `<-` not `:`. +w = a + b : Int + +2 = 3 -- Also an error. + +