I don't remember what I was doing.

This commit is contained in:
James Harton 2019-02-04 14:18:44 +13:00
parent 6785e01df7
commit 8b464e68bd
33 changed files with 501 additions and 148 deletions

View file

@ -6,3 +6,4 @@ authors = ["James Harton <james@automat.nz>"]
[dependencies]
huia-parser = { path = "../parser" }
huia-linter = { path = "../linter" }

View file

@ -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<Violation>),
}
use huia_parser::File;
#[derive(Debug, Clone)]
pub struct Compiler {
@ -16,8 +9,6 @@ pub struct Compiler {
violations: Vec<Violation>,
}
pub type CompilerResult = Result<Compiler, CompileError>;
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)
}
}

View file

@ -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<Message>,
}
#[derive(Debug, Clone)]
pub struct Message {
span: Span,
message: String,
description: Option<String>,
suggestions: Option<String>,
severity: Severity,
}
#[derive(Debug, Clone)]
pub enum Severity {
Error = 0,
Info,
Warn,
}
impl From<huia_linter::Severity> 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<Vec<Violation>> for CompileError {
fn from(violations: Vec<Violation>) -> Self {
let mut messages: Vec<Message> = 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<huia_parser::ParseError> 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<huia_parser::grammar::Rule>) -> 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)
}
}

View file

@ -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;

View file

@ -0,0 +1,3 @@
use super::{CompileError, Compiler};
pub type CompilerResult = Result<Compiler, CompileError>;

View file

@ -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)

18
compiler/src/position.rs Normal file
View file

@ -0,0 +1,18 @@
use huia_parser;
#[derive(Debug, Clone)]
pub struct Position {
byte: usize,
line: usize,
column: usize,
}
impl From<huia_parser::Position> for Position {
fn from(pos: huia_parser::Position) -> Position {
Position {
byte: pos.byte(),
line: pos.line(),
column: pos.column(),
}
}
}

18
compiler/src/span.rs Normal file
View file

@ -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<huia_parser::Span> for Span {
fn from(span: huia_parser::Span) -> Span {
Span {
start: Position::from(span.start().clone()),
end: Position::from(span.end().clone()),
}
}
}

View file

@ -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))
}
}

View file

@ -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)),
}
}

View file

@ -6,3 +6,5 @@ authors = ["James Harton <james@automat.nz>"]
[dependencies]
clap = "2.32.0"
huia-compiler = { path = "../compiler" }
ansi_term = "0.11"
term_size = "0.3.1"

18
huiac/src/error.rs Normal file
View file

@ -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<CompileError> 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)
}
}

View file

@ -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 <james@automat.nz>")
.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);
}
}

View file

@ -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<Result<(), Violation>> {

View file

@ -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");

45
linter/src/lint.rs Normal file
View file

@ -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<RuleResult> {
vec![]
}
/// Return all violations.
fn all_violations(&self) -> Vec<Violation> {
self.lint().iter().filter_map(|r| r.clone().err()).collect()
}
/// Return only error violations.
fn error_violations(&self) -> Vec<Violation> {
self.lint()
.iter()
.filter_map(|r| r.clone().err())
.filter(|r| r.is_error())
.collect()
}
/// Return only info violations.
fn info_violations(&self) -> Vec<Violation> {
self.lint()
.iter()
.filter_map(|r| r.clone().err())
.filter(|r| r.is_info())
.collect()
}
/// Return only warn violations
fn warn_violations(&self) -> Vec<Violation> {
self.lint()
.iter()
.filter_map(|r| r.clone().err())
.filter(|r| r.is_warn())
.collect()
}
}

View file

@ -1,62 +1,16 @@
use huia_parser::IntoSpan;
use severity::Severity;
/// A rule violation
#[derive(Debug, Clone)]
pub struct Violation {
message: String,
description: Option<String>,
suggestion: Option<String>,
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<RuleResult> {
vec![]
}
/// Return all violations.
fn all_violations(&self) -> Vec<Violation> {
self.lint().iter().filter_map(|r| r.clone().err()).collect()
}
/// Return only error violations.
fn error_violations(&self) -> Vec<Violation> {
self.lint()
.iter()
.filter_map(|r| r.clone().err())
.filter(|r| r.is_error())
.collect()
}
/// Return only info violations.
fn info_violations(&self) -> Vec<Violation> {
self.lint()
.iter()
.filter_map(|r| r.clone().err())
.filter(|r| r.is_info())
.collect()
}
/// Return only warn violations
fn warn_violations(&self) -> Vec<Violation> {
self.lint()
.iter()
.filter_map(|r| r.clone().err())
.filter(|r| r.is_warn())
.collect()
}
}

View file

@ -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::*;

View file

@ -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::*;

View file

@ -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::*;

View file

@ -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::*;

View file

@ -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::*;

View file

@ -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::*;

68
linter/src/violation.rs Normal file
View file

@ -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<String>,
pub suggestion: Option<String>,
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
}
}

View file

@ -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::*;

View file

@ -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<Pair<'a, Rule>> for FunctionType {
}
}
impl IntoSpan for DefineFunction {
fn into_span(&self) -> &Span {
&self.span
}
}
#[cfg(test)]
mod test {
use super::*;

View file

@ -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,

View file

@ -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::*;

View file

@ -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<pest::Span<'a>> 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<pest::Position<'a>> 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
}
}

View file

@ -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::*;

View file

@ -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::*;

View file

@ -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<Rule>,
negatives: Vec<Rule>,
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<Rule> {
match self {
ParseError::PestError { positives, .. } => positives.clone(),
_ => vec![],
}
}
pub fn negatives(&self) -> Vec<Rule> {
match self {
ParseError::PestError { negatives, .. } => negatives.clone(),
_ => vec![],
}
}
}
impl<'a> From<pest::Error<'a, Rule>> 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!(),
}

View file

@ -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;