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:
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.
+
+