diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml index 5b1558b..8e5c4cb 100644 --- a/compiler/Cargo.toml +++ b/compiler/Cargo.toml @@ -6,3 +6,4 @@ authors = ["James Harton "] [dependencies] huia-parser = { path = "../parser" } huia-linter = { path = "../linter" } + diff --git a/compiler/src/compiler.rs b/compiler/src/compiler/compiler.rs similarity index 65% rename from compiler/src/compiler.rs rename to compiler/src/compiler/compiler.rs index 5922d50..bb54a0d 100644 --- a/compiler/src/compiler.rs +++ b/compiler/src/compiler/compiler.rs @@ -1,12 +1,5 @@ use huia_linter::Violation; -use huia_parser::{File, ParseError}; -use std::{error::Error, fmt}; - -#[derive(Debug, Clone)] -pub enum CompileError { - ParseError(ParseError), - LintError(Vec), -} +use huia_parser::File; #[derive(Debug, Clone)] pub struct Compiler { @@ -16,8 +9,6 @@ pub struct Compiler { violations: Vec, } -pub type CompilerResult = Result; - impl Compiler { pub fn new(source_code: String, source_name: String, node: File) -> Compiler { let violations = vec![]; @@ -45,11 +36,3 @@ impl Compiler { &self.violations } } - -impl Error for CompileError {} - -impl fmt::Display for CompileError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self) - } -} diff --git a/compiler/src/compiler/error.rs b/compiler/src/compiler/error.rs new file mode 100644 index 0000000..534f780 --- /dev/null +++ b/compiler/src/compiler/error.rs @@ -0,0 +1,113 @@ +use huia_linter; +use huia_linter::Violation; +use huia_parser; +use huia_parser::IntoSpan; +use span::Span; +use std::{error::Error, fmt}; + +#[derive(Debug, Clone)] +pub struct CompileError { + messages: Vec, +} + +#[derive(Debug, Clone)] +pub struct Message { + span: Span, + message: String, + description: Option, + suggestions: Option, + severity: Severity, +} + +#[derive(Debug, Clone)] +pub enum Severity { + Error = 0, + Info, + Warn, +} + +impl From for Severity { + fn from(sev: huia_linter::Severity) -> Severity { + if sev.is_info() { + Severity::Info + } else if sev.is_warn() { + Severity::Warn + } else if sev.is_error() { + Severity::Error + } else { + unreachable!() + } + } +} + +impl From> for CompileError { + fn from(violations: Vec) -> Self { + let mut messages: Vec = vec![]; + + for violation in violations { + messages.push(Message { + span: Span::from(violation.into_span().clone()), + message: violation.message().to_string(), + description: match violation.description() { + Some(value) => Some(value.to_string()), + None => None, + }, + suggestions: match violation.suggestion() { + Some(value) => Some(value.to_string()), + None => None, + }, + severity: Severity::from(violation.severity()), + }) + } + + CompileError { messages: messages } + } +} + +impl From for CompileError { + fn from(parse_error: huia_parser::ParseError) -> Self { + let positives = fmt_rule_list(parse_error.positives()); + let negatives = fmt_rule_list(parse_error.negatives()); + let suggestions = match (negatives.is_empty(), positives.is_empty()) { + (false, false) => Some(format!("unexpected {}; expected {}", positives, negatives)), + (false, true) => Some(format!("unexpected {}", negatives)), + (true, false) => Some(format!("expected {}", positives)), + (true, true) => None, + }; + + let message = Message { + span: Span::from(parse_error.span()), + message: "Unable to parse input".to_string(), + description: None, + suggestions: suggestions, + severity: Severity::Error, + }; + CompileError { + messages: vec![message], + } + } +} + +fn fmt_rule_list(rules: Vec) -> String { + let mut value = String::new(); + let mut count = 1; + let len = rules.len(); + for rule in rules { + value.push_str(&format!("{}", rule)); + count = count + 1; + if count < len { + value.push_str(", "); + } else if count == len { + value.push_str(" or "); + } + } + value +} + +impl Error for CompileError {} + +impl fmt::Display for CompileError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} diff --git a/compiler/src/compiler/mod.rs b/compiler/src/compiler/mod.rs new file mode 100644 index 0000000..7eebbd4 --- /dev/null +++ b/compiler/src/compiler/mod.rs @@ -0,0 +1,7 @@ +mod compiler; +mod error; +mod result; + +pub use self::compiler::Compiler; +pub use self::error::CompileError; +pub use self::result::CompilerResult; diff --git a/compiler/src/compiler/result.rs b/compiler/src/compiler/result.rs new file mode 100644 index 0000000..b17b225 --- /dev/null +++ b/compiler/src/compiler/result.rs @@ -0,0 +1,3 @@ +use super::{CompileError, Compiler}; + +pub type CompilerResult = Result; diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs index 5653276..c9e184f 100644 --- a/compiler/src/lib.rs +++ b/compiler/src/lib.rs @@ -4,14 +4,21 @@ extern crate huia_linter; extern crate huia_parser; mod compiler; +mod position; mod source; mod source_buffer; mod source_file; +mod span; mod stages; use source_buffer::SourceBuffer; use source_file::SourceFile; +/// Version number of the huia-compiler crate. +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub use compiler::{CompileError, Compiler, CompilerResult}; + pub fn compile_file(path: &str) -> compiler::CompilerResult { let input = SourceFile::new(path); stages::compile(input) diff --git a/compiler/src/position.rs b/compiler/src/position.rs new file mode 100644 index 0000000..665e9f3 --- /dev/null +++ b/compiler/src/position.rs @@ -0,0 +1,18 @@ +use huia_parser; + +#[derive(Debug, Clone)] +pub struct Position { + byte: usize, + line: usize, + column: usize, +} + +impl From for Position { + fn from(pos: huia_parser::Position) -> Position { + Position { + byte: pos.byte(), + line: pos.line(), + column: pos.column(), + } + } +} diff --git a/compiler/src/span.rs b/compiler/src/span.rs new file mode 100644 index 0000000..7d46f59 --- /dev/null +++ b/compiler/src/span.rs @@ -0,0 +1,18 @@ +use huia_parser; +use position::Position; + +/// A range within the input source. +#[derive(Debug, Clone)] +pub struct Span { + start: Position, + end: Position, +} + +impl From for Span { + fn from(span: huia_parser::Span) -> Span { + Span { + start: Position::from(span.start().clone()), + end: Position::from(span.end().clone()), + } + } +} diff --git a/compiler/src/stages/lint_ast.rs b/compiler/src/stages/lint_ast.rs index b8a7ce4..daf3330 100644 --- a/compiler/src/stages/lint_ast.rs +++ b/compiler/src/stages/lint_ast.rs @@ -12,7 +12,7 @@ pub fn lint_ast<'a>(mut compiler: Compiler) -> CompilerResult { compiler.push_violations(&mut violations); Ok(compiler) } else { - Err(CompileError::LintError(errors)) + Err(CompileError::from(errors)) } } diff --git a/compiler/src/stages/parse.rs b/compiler/src/stages/parse.rs index 1e093fb..5b6163d 100644 --- a/compiler/src/stages/parse.rs +++ b/compiler/src/stages/parse.rs @@ -8,7 +8,7 @@ pub fn parse(input: impl Source) -> CompilerResult { let result = File::parse(&source_code); match result { Ok(node) => Ok(Compiler::new(source_code, source_name, node)), - Err(err) => Err(CompileError::ParseError(err)), + Err(err) => Err(CompileError::from(err)), } } diff --git a/huiac/Cargo.toml b/huiac/Cargo.toml index c149dab..3d09c5a 100644 --- a/huiac/Cargo.toml +++ b/huiac/Cargo.toml @@ -6,3 +6,5 @@ authors = ["James Harton "] [dependencies] clap = "2.32.0" huia-compiler = { path = "../compiler" } +ansi_term = "0.11" +term_size = "0.3.1" diff --git a/huiac/src/error.rs b/huiac/src/error.rs new file mode 100644 index 0000000..ffd37d1 --- /dev/null +++ b/huiac/src/error.rs @@ -0,0 +1,18 @@ +use ansi_term::{Color::White, Style}; +use huia_compiler::CompileError; +use std::fmt; + +#[derive(Debug)] +pub struct Error(CompileError); + +impl From for Error { + fn from(e: CompileError) -> Error { + Error(e) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} diff --git a/huiac/src/main.rs b/huiac/src/main.rs index 5ed97e1..8b5ae05 100644 --- a/huiac/src/main.rs +++ b/huiac/src/main.rs @@ -1,18 +1,25 @@ #![forbid(unsafe_code)] +extern crate ansi_term; extern crate clap; extern crate huia_compiler; +extern crate term_size; + +mod error; +mod terminal; use clap::{App, Arg}; +use error::Error; use std::process; -const VERSION: &'static str = env!("CARGO_PKG_VERSION"); +/// Version number of the huiac crate. +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); fn main() { let matches = App::new("huiac") .version(VERSION) .author("James Harton ") - .about("The compiler for the Huia programming language.") + .about("The CLI for the Huia programming language compiler.") .arg( Arg::with_name("INPUT") .help("The source file to compile") @@ -27,7 +34,7 @@ fn main() { println!("{:#?}", result.unwrap()); process::exit(0); } else { - println!("{}", result.err().unwrap()); + println!("{}", Error::from(result.err().unwrap())); process::exit(1); } } diff --git a/linter/src/ast.rs b/linter/src/ast.rs index 774a9a5..10b06ea 100644 --- a/linter/src/ast.rs +++ b/linter/src/ast.rs @@ -1,6 +1,8 @@ use huia_parser::*; +use lint::Lint; use rule::*; use rules::*; +use violation::Violation; impl Lint for ArgumentName { fn lint(&self) -> Vec> { diff --git a/linter/src/lib.rs b/linter/src/lib.rs index 33da152..06cbb3d 100644 --- a/linter/src/lib.rs +++ b/linter/src/lib.rs @@ -3,11 +3,15 @@ extern crate heck; extern crate huia_parser; mod ast; +mod lint; mod rule; mod rules; mod severity; +mod violation; -pub use rule::{Lint, Violation}; +pub use lint::Lint; +pub use severity::Severity; +pub use violation::Violation; /// Version number of the huia-liter crate. pub const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/linter/src/lint.rs b/linter/src/lint.rs new file mode 100644 index 0000000..3694779 --- /dev/null +++ b/linter/src/lint.rs @@ -0,0 +1,45 @@ +use rule::RuleResult; +use violation::Violation; + +/// The ability to lint oneself +/// +/// This trait is implemented by all the ast nodes so that we know which rules +/// to apply to any given node. See `huia_linter::ast` for details. +pub trait Lint { + /// Return the result of running all rules applicable to `Self`. + fn lint(&self) -> Vec { + vec![] + } + + /// Return all violations. + fn all_violations(&self) -> Vec { + self.lint().iter().filter_map(|r| r.clone().err()).collect() + } + + /// Return only error violations. + fn error_violations(&self) -> Vec { + self.lint() + .iter() + .filter_map(|r| r.clone().err()) + .filter(|r| r.is_error()) + .collect() + } + + /// Return only info violations. + fn info_violations(&self) -> Vec { + self.lint() + .iter() + .filter_map(|r| r.clone().err()) + .filter(|r| r.is_info()) + .collect() + } + + /// Return only warn violations + fn warn_violations(&self) -> Vec { + self.lint() + .iter() + .filter_map(|r| r.clone().err()) + .filter(|r| r.is_warn()) + .collect() + } +} diff --git a/linter/src/rule.rs b/linter/src/rule.rs index bc4b96b..c783f9b 100644 --- a/linter/src/rule.rs +++ b/linter/src/rule.rs @@ -1,62 +1,16 @@ +use huia_parser::IntoSpan; use severity::Severity; - -/// A rule violation -#[derive(Debug, Clone)] -pub struct Violation { - message: String, - description: Option, - suggestion: Option, - severity: Severity, -} - -impl Violation { - /// Is the severity `Info`? - pub fn is_info(&self) -> bool { - match self.severity { - Severity::Info => true, - _ => false, - } - } - - /// Is the severity `Warn`? - pub fn is_warn(&self) -> bool { - match self.severity { - Severity::Warn => true, - _ => false, - } - } - - /// Is the severity `Error`? - pub fn is_error(&self) -> bool { - match self.severity { - Severity::Error => true, - _ => false, - } - } - - /// The description of this violation - pub fn description(&self) -> Option<&str> { - match self.description { - Some(ref s) => Some(s), - None => None, - } - } - - /// A suggested fix for this violation - pub fn suggestion(&self) -> Option<&str> { - match self.suggestion { - Some(ref s) => Some(s), - None => None, - } - } -} +use violation::Violation; pub type RuleResult = Result<(), Violation>; /// A linting rule /// /// Your rule must implement this trait to be used by the linter. -pub trait Rule { +pub trait Rule +where + Self: IntoSpan, +{ /// Is the node valid? fn is_valid(&self) -> bool; @@ -83,6 +37,7 @@ pub trait Rule { Ok(()) } else { Err(Violation { + span: self.into_span().clone(), message: self.message(), description: self.description(), suggestion: self.suggestion(), @@ -91,46 +46,3 @@ pub trait Rule { } } } - -/// The ability to lint oneself -/// -/// This trait is implemented by all the ast nodes so that we know which rules -/// to apply to any given node. See `huia_linter::ast` for details. -pub trait Lint { - /// Return the result of running all rules applicable to `Self`. - fn lint(&self) -> Vec { - vec![] - } - - /// Return all violations. - fn all_violations(&self) -> Vec { - self.lint().iter().filter_map(|r| r.clone().err()).collect() - } - - /// Return only error violations. - fn error_violations(&self) -> Vec { - self.lint() - .iter() - .filter_map(|r| r.clone().err()) - .filter(|r| r.is_error()) - .collect() - } - - /// Return only info violations. - fn info_violations(&self) -> Vec { - self.lint() - .iter() - .filter_map(|r| r.clone().err()) - .filter(|r| r.is_info()) - .collect() - } - - /// Return only warn violations - fn warn_violations(&self) -> Vec { - self.lint() - .iter() - .filter_map(|r| r.clone().err()) - .filter(|r| r.is_warn()) - .collect() - } -} diff --git a/linter/src/rules/argument_name_should_be_snake_case.rs b/linter/src/rules/argument_name_should_be_snake_case.rs index 33a67ef..f16a66e 100644 --- a/linter/src/rules/argument_name_should_be_snake_case.rs +++ b/linter/src/rules/argument_name_should_be_snake_case.rs @@ -1,5 +1,5 @@ use heck::SnakeCase; -use huia_parser::ArgumentName; +use huia_parser::{ArgumentName, IntoSpan, Span}; use rule::Rule; use severity::Severity; @@ -38,6 +38,12 @@ impl<'a> From<&'a ArgumentName> for ArgumentNameShouldBeSnakeCase<'a> { } } +impl<'a> IntoSpan for ArgumentNameShouldBeSnakeCase<'a> { + fn into_span(&self) -> &Span { + self.0.into_span() + } +} + #[cfg(test)] mod test { use super::*; diff --git a/linter/src/rules/atom_should_be_snake_case.rs b/linter/src/rules/atom_should_be_snake_case.rs index ec62f3d..e36ec2a 100644 --- a/linter/src/rules/atom_should_be_snake_case.rs +++ b/linter/src/rules/atom_should_be_snake_case.rs @@ -1,5 +1,5 @@ use heck::SnakeCase; -use huia_parser::Atom; +use huia_parser::{Atom, IntoSpan, Span}; use rule::Rule; use severity::Severity; @@ -38,6 +38,12 @@ impl<'a> From<&'a Atom> for AtomShouldBeSnakeCase<'a> { } } +impl<'a> IntoSpan for AtomShouldBeSnakeCase<'a> { + fn into_span(&self) -> &Span { + self.0.into_span() + } +} + #[cfg(test)] mod test { use super::*; diff --git a/linter/src/rules/function_name_should_be_snake_case.rs b/linter/src/rules/function_name_should_be_snake_case.rs index f1a104a..2f6af63 100644 --- a/linter/src/rules/function_name_should_be_snake_case.rs +++ b/linter/src/rules/function_name_should_be_snake_case.rs @@ -1,5 +1,5 @@ use heck::SnakeCase; -use huia_parser::DefineFunction; +use huia_parser::{DefineFunction, IntoSpan, Span}; use rule::Rule; use severity::Severity; @@ -38,6 +38,12 @@ impl<'a> From<&'a DefineFunction> for FunctionNameShouldBeSnakeCase<'a> { } } +impl<'a> IntoSpan for FunctionNameShouldBeSnakeCase<'a> { + fn into_span(&self) -> &Span { + &self.0.into_span() + } +} + #[cfg(test)] mod test { use super::*; diff --git a/linter/src/rules/property_name_should_be_snake_case.rs b/linter/src/rules/property_name_should_be_snake_case.rs index cbaa037..97fceea 100644 --- a/linter/src/rules/property_name_should_be_snake_case.rs +++ b/linter/src/rules/property_name_should_be_snake_case.rs @@ -1,5 +1,5 @@ use heck::SnakeCase; -use huia_parser::Property; +use huia_parser::{IntoSpan, Property, Span}; use rule::Rule; use severity::Severity; @@ -38,6 +38,12 @@ impl<'a> From<&'a Property> for PropertyNameShouldBeSnakeCase<'a> { } } +impl<'a> IntoSpan for PropertyNameShouldBeSnakeCase<'a> { + fn into_span(&self) -> &Span { + &self.0.into_span() + } +} + #[cfg(test)] mod test { use super::*; diff --git a/linter/src/rules/type_name_should_be_camel_case.rs b/linter/src/rules/type_name_should_be_camel_case.rs index 3842376..4bf5dd6 100644 --- a/linter/src/rules/type_name_should_be_camel_case.rs +++ b/linter/src/rules/type_name_should_be_camel_case.rs @@ -1,5 +1,5 @@ use heck::CamelCase; -use huia_parser::TypeName; +use huia_parser::{IntoSpan, Span, TypeName}; use rule::Rule; use severity::Severity; @@ -37,6 +37,12 @@ impl<'a> From<&'a TypeName> for TypeNameShouldBeCamelCase<'a> { } } +impl<'a> IntoSpan for TypeNameShouldBeCamelCase<'a> { + fn into_span(&self) -> &Span { + self.0.into_span() + } +} + #[cfg(test)] mod test { use super::*; diff --git a/linter/src/rules/variable_name_should_be_snake_case.rs b/linter/src/rules/variable_name_should_be_snake_case.rs index 4bdc519..9e7901f 100644 --- a/linter/src/rules/variable_name_should_be_snake_case.rs +++ b/linter/src/rules/variable_name_should_be_snake_case.rs @@ -1,5 +1,5 @@ use heck::SnakeCase; -use huia_parser::Variable; +use huia_parser::{IntoSpan, Span, Variable}; use rule::Rule; use severity::Severity; @@ -38,6 +38,12 @@ impl<'a> From<&'a Variable> for VariableNameShouldBeSnakeCase<'a> { } } +impl<'a> IntoSpan for VariableNameShouldBeSnakeCase<'a> { + fn into_span(&self) -> &Span { + self.0.into_span() + } +} + #[cfg(test)] mod test { use super::*; diff --git a/linter/src/violation.rs b/linter/src/violation.rs new file mode 100644 index 0000000..43f40ba --- /dev/null +++ b/linter/src/violation.rs @@ -0,0 +1,68 @@ +use huia_parser::{IntoSpan, Span}; +use severity::Severity; + +/// A rule violation +#[derive(Debug, Clone)] +pub struct Violation { + pub span: Span, + pub message: String, + pub description: Option, + pub suggestion: Option, + pub severity: Severity, +} + +impl Violation { + /// Is the severity `Info`? + pub fn is_info(&self) -> bool { + match self.severity { + Severity::Info => true, + _ => false, + } + } + + /// Is the severity `Warn`? + pub fn is_warn(&self) -> bool { + match self.severity { + Severity::Warn => true, + _ => false, + } + } + + /// Is the severity `Error`? + pub fn is_error(&self) -> bool { + match self.severity { + Severity::Error => true, + _ => false, + } + } + + pub fn message(&self) -> &str { + &self.message + } + + /// The description of this violation + pub fn description(&self) -> Option<&str> { + match self.description { + Some(ref s) => Some(s), + None => None, + } + } + + /// A suggested fix for this violation + pub fn suggestion(&self) -> Option<&str> { + match self.suggestion { + Some(ref s) => Some(s), + None => None, + } + } + + pub fn severity(&self) -> Severity { + self.severity.clone() + } +} + +impl IntoSpan for Violation { + fn into_span(&self) -> &Span { + &self.span + } +} diff --git a/parser/src/ast/argument_name.rs b/parser/src/ast/argument_name.rs index 3a02a33..dbe37f6 100644 --- a/parser/src/ast/argument_name.rs +++ b/parser/src/ast/argument_name.rs @@ -1,4 +1,4 @@ -use ast::span::Span; +use ast::{into_span::IntoSpan, span::Span}; use grammar::Rule; use parse::Parse; use pest::iterators::Pair; @@ -35,6 +35,12 @@ impl ArgumentName { } } +impl IntoSpan for ArgumentName { + fn into_span(&self) -> &Span { + &self.span + } +} + #[cfg(test)] mod test { use super::*; diff --git a/parser/src/ast/define_function.rs b/parser/src/ast/define_function.rs index 97d852a..68f5e43 100644 --- a/parser/src/ast/define_function.rs +++ b/parser/src/ast/define_function.rs @@ -1,4 +1,7 @@ -use ast::{argument_name::ArgumentName, expression::Expression, span::Span, type_name::TypeName}; +use ast::{ + argument_name::ArgumentName, expression::Expression, into_span::IntoSpan, span::Span, + type_name::TypeName, +}; use grammar::Rule; use parse::Parse; use pest::iterators::{Pair, Pairs}; @@ -156,6 +159,12 @@ impl<'a> From> for FunctionType { } } +impl IntoSpan for DefineFunction { + fn into_span(&self) -> &Span { + &self.span + } +} + #[cfg(test)] mod test { use super::*; diff --git a/parser/src/ast/mod.rs b/parser/src/ast/mod.rs index 91545a0..b439615 100644 --- a/parser/src/ast/mod.rs +++ b/parser/src/ast/mod.rs @@ -46,7 +46,7 @@ pub use self::{ map::{Map, MapPair}, operator::Operator, property::Property, - span::Span, + span::{Position, Span}, string::StringLiteral, type_name::TypeName, unary::Unary, diff --git a/parser/src/ast/property.rs b/parser/src/ast/property.rs index 90db6d7..ee8754d 100644 --- a/parser/src/ast/property.rs +++ b/parser/src/ast/property.rs @@ -1,4 +1,4 @@ -use ast::span::Span; +use ast::{into_span::IntoSpan, span::Span}; use grammar::Rule; use parse::Parse; use pest::iterators::Pair; @@ -41,6 +41,12 @@ impl Property { } } +impl IntoSpan for Property { + fn into_span(&self) -> &Span { + &self.span + } +} + #[cfg(test)] mod test { use super::*; diff --git a/parser/src/ast/span.rs b/parser/src/ast/span.rs index 6507977..01e4fc8 100644 --- a/parser/src/ast/span.rs +++ b/parser/src/ast/span.rs @@ -1,29 +1,70 @@ use pest; -/// A location within the input source. +/// A range within the input source. #[derive(Debug, Clone)] pub struct Span { - start: usize, - end: usize, + start: Position, + end: Position, } impl Span { /// The start position of the current node. - pub fn start(&self) -> usize { - self.start + pub fn start(&self) -> &Position { + &self.start } /// The end position of the current node. - pub fn end(&self) -> usize { - self.end + pub fn end(&self) -> &Position { + &self.end + } + + /// Make a new span where the start and end positions are the same. + pub fn point(point: Position) -> Span { + Span { + start: point.clone(), + end: point, + } } } impl<'a> From> for Span { fn from(pest: pest::Span<'a>) -> Span { Span { - start: pest.start(), - end: pest.end(), + start: Position::from(pest.start_pos()), + end: Position::from(pest.end_pos()), } } } + +/// A location within the input source. +#[derive(Debug, Clone)] +pub struct Position { + byte: usize, + line: usize, + column: usize, +} + +impl<'a> From> for Position { + fn from(pest: pest::Position<'a>) -> Position { + let byte = pest.pos(); + let (line, column) = pest.line_col(); + Position { byte, line, column } + } +} + +impl Position { + /// A byte offset of the position in the input + pub fn byte(&self) -> usize { + self.byte + } + + /// The line number of this position in the input + pub fn line(&self) -> usize { + self.line + } + + /// The column number of this position in the input + pub fn column(&self) -> usize { + self.column + } +} diff --git a/parser/src/ast/type_name.rs b/parser/src/ast/type_name.rs index 8a8b0ee..7d6bf88 100644 --- a/parser/src/ast/type_name.rs +++ b/parser/src/ast/type_name.rs @@ -1,4 +1,4 @@ -use ast::span::Span; +use ast::{into_span::IntoSpan, span::Span}; use grammar::Rule; use parse::Parse; use pest::iterators::Pair; @@ -34,6 +34,12 @@ impl TypeName { } } +impl IntoSpan for TypeName { + fn into_span(&self) -> &Span { + &self.span + } +} + #[cfg(test)] mod test { use super::*; diff --git a/parser/src/ast/variable.rs b/parser/src/ast/variable.rs index 9ec89d6..851a25c 100644 --- a/parser/src/ast/variable.rs +++ b/parser/src/ast/variable.rs @@ -1,4 +1,4 @@ -use ast::span::Span; +use ast::{into_span::IntoSpan, span::Span}; use grammar::Rule; use parse::Parse; use pest::iterators::Pair; @@ -36,6 +36,12 @@ impl Variable { } } +impl IntoSpan for Variable { + fn into_span(&self) -> &Span { + &self.span + } +} + #[cfg(test)] mod test { use super::*; diff --git a/parser/src/error.rs b/parser/src/error.rs index 4a70eee..9b5e88b 100644 --- a/parser/src/error.rs +++ b/parser/src/error.rs @@ -1,4 +1,4 @@ -use ast::Span; +use ast::{Position, Span}; use grammar::Rule; use pest; use std::error::Error; @@ -18,24 +18,51 @@ pub enum ParseError { PestError { positives: Vec, negatives: Vec, - position: usize, + position: Position, }, /// There was an error converting the parse tree into AST. AstGeneration { rule: Rule, span: Span }, } +impl ParseError { + /// Returns a Span where the error took place. + /// + /// In the case of a parse error it returns a span where the start and end + /// positions are the same. + pub fn span(&self) -> Span { + match self { + ParseError::PestError { position, .. } => Span::point(position.clone()), + ParseError::AstGeneration { span, .. } => span.clone(), + } + } + + pub fn positives(&self) -> Vec { + match self { + ParseError::PestError { positives, .. } => positives.clone(), + _ => vec![], + } + } + + pub fn negatives(&self) -> Vec { + match self { + ParseError::PestError { negatives, .. } => negatives.clone(), + _ => vec![], + } + } +} + impl<'a> From> for ParseError { fn from(pest: pest::Error<'a, Rule>) -> Self { match pest { pest::Error::ParsingError { - ref positives, - ref negatives, - ref pos, + positives, + negatives, + pos, } => ParseError::PestError { - positives: positives.clone(), - negatives: negatives.clone(), - position: pos.pos(), + positives: positives, + negatives: negatives, + position: Position::from(pos), }, _ => unreachable!(), } diff --git a/parser/src/grammar/mod.rs b/parser/src/grammar/mod.rs index a7f6ecb..1725a9c 100644 --- a/parser/src/grammar/mod.rs +++ b/parser/src/grammar/mod.rs @@ -1,3 +1,5 @@ +use std::fmt; + const _GRAMMAR: &'static str = include_str!("../grammar.pest"); /// The Grammar derived from `grammar.pest`. @@ -5,5 +7,11 @@ const _GRAMMAR: &'static str = include_str!("../grammar.pest"); #[grammar = "grammar.pest"] pub struct Grammar; +impl fmt::Display for Rule { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + #[cfg(test)] mod test;