wip: use miette for compiler errors.

This commit is contained in:
James Harton 2022-08-22 11:30:39 +12:00
parent 336b2574b8
commit 272be0d249
61 changed files with 974 additions and 289 deletions

View file

@ -19,9 +19,13 @@
"indoc",
"Jiggawatts",
"lexer",
"miette",
"prec",
"Scandroid",
"struct",
"typeblock"
"textwrap",
"thiserror",
"typeblock",
"typeunion"
]
}

260
Cargo.lock generated
View file

@ -2,12 +2,62 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "assert_matches"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "backtrace"
version = "0.3.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "block-buffer"
version = "0.10.2"
@ -17,6 +67,12 @@ dependencies = [
"generic-array",
]
[[package]]
name = "cc"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]]
name = "cfg-if"
version = "1.0.0"
@ -62,6 +118,27 @@ dependencies = [
"version_check",
]
[[package]]
name = "gimli"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "is_ci"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb"
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -74,6 +151,61 @@ version = "0.2.132"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "miette"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28d6092d7e94a90bb9ea8e6c26c99d5d112d49dda2afdb4f7ea8cf09e1a5a6d"
dependencies = [
"atty",
"backtrace",
"miette-derive",
"once_cell",
"owo-colors",
"supports-color",
"supports-hyperlinks",
"supports-unicode",
"terminal_size",
"textwrap",
"thiserror",
"unicode-width",
]
[[package]]
name = "miette-derive"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f2485ed7d1fe80704928e3eb86387439609bd0c6bb96db8208daa364cfd1e09"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "miniz_oxide"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
dependencies = [
"adler",
]
[[package]]
name = "object"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.13.1"
@ -90,6 +222,9 @@ dependencies = [
[[package]]
name = "outrun-common"
version = "0.1.0"
dependencies = [
"thiserror",
]
[[package]]
name = "outrun-compiler"
@ -97,18 +232,28 @@ version = "0.1.0"
dependencies = [
"assert_matches",
"lazy_static",
"miette",
"outrun-common",
"pest",
"pest_derive",
"textwrap",
"thiserror",
]
[[package]]
name = "outrun-core"
version = "0.1.0"
dependencies = [
"miette",
"outrun-compiler",
]
[[package]]
name = "owo-colors"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b"
[[package]]
name = "pest"
version = "2.2.1"
@ -171,6 +316,29 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]]
name = "rustc-demangle"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]]
name = "sha-1"
version = "0.10.0"
@ -182,6 +350,40 @@ dependencies = [
"digest",
]
[[package]]
name = "smawk"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
[[package]]
name = "supports-color"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4872ced36b91d47bae8a214a683fe54e7078875b399dfa251df346c9b547d1f9"
dependencies = [
"atty",
"is_ci",
]
[[package]]
name = "supports-hyperlinks"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "590b34f7c5f01ecc9d78dba4b3f445f31df750a67621cf31626f3b7441ce6406"
dependencies = [
"atty",
]
[[package]]
name = "supports-unicode"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8b945e45b417b125a8ec51f1b7df2f8df7920367700d1f98aedd21e5735f8b2"
dependencies = [
"atty",
]
[[package]]
name = "syn"
version = "1.0.99"
@ -193,6 +395,27 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "terminal_size"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "textwrap"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
dependencies = [
"smawk",
"unicode-linebreak",
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.32"
@ -231,8 +454,45 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
[[package]]
name = "unicode-linebreak"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f"
dependencies = [
"regex",
]
[[package]]
name = "unicode-width"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View file

@ -9,3 +9,4 @@ version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
thiserror = "1.0"

View file

@ -1,3 +1,5 @@
extern crate thiserror;
pub mod indexed_vec;
pub mod stack;
pub mod unique_vec;

View file

@ -1,8 +1,11 @@
use std::default::Default;
use thiserror::Error;
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Error)]
pub enum StackError {
#[error("Stack overflow")]
Overflow,
#[error("Stack underflow")]
Underflow,
}

View file

@ -10,9 +10,12 @@ version = "0.1.0"
[dependencies]
lazy_static = "1.4"
miette = {version = "5.3", features = ["fancy"]}
outrun-common = {path = "../outrun-common"}
pest = "2.2"
pest_derive = "2.2"
textwrap = "0.15.0"
thiserror = "1.0"
[dev-dependencies]
assert_matches = "1.5"

View file

@ -1,20 +1,76 @@
use crate::grammar::Error as GrammarError;
use crate::ir::Error as IrError;
use crate::source::Source;
use crate::span::Span;
use miette::Diagnostic;
use std::io::Error as IoError;
use std::num::{ParseFloatError, ParseIntError};
use std::sync::Arc;
use thiserror::Error;
#[derive(Debug)]
#[derive(Debug, Error, Diagnostic)]
pub enum Error {
ParseError(GrammarError),
IrError(IrError),
#[error("Error while parsing")]
#[diagnostic()]
ParseError {
#[source_code]
source: Arc<Source>,
#[source]
error: GrammarError,
#[label]
span: Span,
},
#[error("Error while parsing")]
#[diagnostic()]
IrError {
#[source_code]
source: Arc<Source>,
#[source]
error: IrError,
#[label]
span: Option<Span>,
},
#[error("I/O error")]
#[diagnostic()]
IoErrorWithSource {
#[source_code]
source: Arc<Source>,
#[source]
error: IoError,
},
#[error("I/O error")]
#[diagnostic()]
IoError(
#[from]
#[source]
IoError,
),
}
impl From<GrammarError> for Error {
fn from(error: GrammarError) -> Self {
Error::ParseError(error)
impl From<(Arc<Source>, GrammarError)> for Error {
fn from((source, error): (Arc<Source>, GrammarError)) -> Self {
let span = error.as_span();
Error::ParseError {
source,
error,
span,
}
}
}
impl From<IrError> for Error {
fn from(error: IrError) -> Self {
Error::IrError(error)
impl From<(Arc<Source>, IrError)> for Error {
fn from((source, error): (Arc<Source>, IrError)) -> Self {
let span = error.maybe_span();
Self::IrError {
source,
error,
span,
}
}
}
impl From<(Arc<Source>, IoError)> for Error {
fn from((source, error): (Arc<Source>, IoError)) -> Self {
Self::IoErrorWithSource { source, error }
}
}

View file

@ -155,6 +155,7 @@ pub enum NodeKind {
Float,
GetLocal,
Impl,
Index,
Infix,
Integer,
Let,
@ -192,6 +193,10 @@ pub enum NodeValue {
name: Box<Node>,
props: HashMap<String, Node>,
},
Index {
expr: Box<Node>,
field_name: String,
},
Infix {
op: Operator,
lhs: Box<Node>,

View file

@ -1,43 +1,136 @@
use super::Rule;
use crate::span::Span;
use std::fmt;
use std::num::{ParseFloatError, ParseIntError};
use thiserror::Error;
#[derive(Debug)]
#[derive(Error, Debug)]
pub enum Error {
ParseError(pest::error::Error<Rule>),
ParseIntegerError(ParseIntError),
ParseFloatError(ParseFloatError),
ParseIntError(Span, #[source] ParseIntError),
ParseFloatError(Span, #[source] ParseFloatError),
Unexpected {
received: Rule,
expected: Option<Rule>,
span: Span,
},
UnexpectedExpected {
received: Rule,
expected: Rule,
span: Span,
},
PestError {
positives: Vec<Rule>,
negatives: Vec<Rule>,
span: Span,
},
}
impl Error {
pub fn unexpected<T: Into<Span>>(received: Rule, expected: Option<Rule>, span: T) -> Error {
Error::Unexpected {
expected,
received,
span: span.into(),
if expected.is_some() {
Error::UnexpectedExpected {
received,
expected: expected.unwrap(),
span: span.into(),
}
} else {
Error::Unexpected {
received,
span: span.into(),
}
}
}
pub fn as_span(&self) -> Span {
match self {
Error::ParseIntError(span, _) => *span,
Error::ParseFloatError(span, _) => *span,
Error::Unexpected { span, .. } => *span,
Error::UnexpectedExpected { span, .. } => *span,
Error::PestError { span, .. } => *span,
}
}
}
fn to_list(rules: &[Rule]) -> String {
let mut result = String::new();
let len = rules.len();
for (i, r) in rules.iter().enumerate() {
result.push_str(&format!("`{:?}`", r));
match len - i {
1 => break,
2 => result.push_str(" or "),
_ => result.push_str(", "),
}
}
result
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::ParseIntError(_, error) => write!(f, "Unable to parse integer: {}", error),
Error::ParseFloatError(_, error) => write!(f, "Unable to parse float: {}", error),
Error::Unexpected { received, .. } => {
write!(f, "Unexpectedly received: `{:?}` token", received)
}
Error::UnexpectedExpected {
expected, received, ..
} => write!(
f,
"Expected `{:?}` token, but received `{:?}` token",
expected, received
),
Error::PestError {
positives,
negatives,
..
} => match (positives.len(), negatives.len()) {
(0, 0) => write!(f, "Unknown parsing error"),
(0, 1) => write!(f, "Unexpectedly received `{:?}` token", negatives[0]),
(0, _) => write!(
f,
"Unexpectedly received one of {} tokens",
to_list(negatives)
),
(1, 0) => write!(f, "Expected `{:?}` token", positives[0]),
(_, 0) => write!(f, "Expected one of {} tokens", to_list(positives)),
(1, 1) => write!(
f,
"Expected `{:?}` token, but received `{:?}`",
positives[0], negatives[0]
),
(1, _) => write!(
f,
"Expected `{:?}` token, but received one of {}",
positives[0],
to_list(negatives)
),
(_, _) => write!(
f,
"Expected one of {} tokens, but received one of {}",
to_list(positives),
to_list(negatives)
),
},
}
}
}
impl From<pest::error::Error<Rule>> for Error {
fn from(error: pest::error::Error<Rule>) -> Self {
Error::ParseError(error)
}
}
impl From<ParseIntError> for Error {
fn from(error: ParseIntError) -> Self {
Error::ParseIntegerError(error)
}
}
impl From<ParseFloatError> for Error {
fn from(error: ParseFloatError) -> Self {
Error::ParseFloatError(error)
fn from(err: pest::error::Error<Rule>) -> Self {
match err.variant {
pest::error::ErrorVariant::ParsingError {
positives,
negatives,
} => Error::PestError {
positives,
negatives,
span: err.location.into(),
},
_ => unreachable!("We don't use any custom errors in Pest"),
}
}
}

View file

@ -48,7 +48,8 @@ body_expression = _{ let_binding | expression }
expression = _{ infix | expression_inner }
infix = { expression_inner ~ (binary_operator ~ expression_inner)+ }
expression_inner = _{ unary | literal | call_remote | call_local | local | braced_expression }
expression_inner = _{ unary | literal | index | call_remote | call_local | local | braced_expression }
index = { (local | literal | braced_expression) ~ "." ~ ident }
call_remote = { typename ~ "." ~ ident ~ "(" ~ call_args ~ ")" }
call_local = { local ~ "(" ~ call_args ~ ")" }
call_args = _{ (expression ~ ("," ~ expression)*)? }
@ -57,7 +58,7 @@ let_binding = { "let" ~ ident ~ maybe_type_annotation ~ "=" ~ e
unary = { unary_operator ~ (literal | local | braced_expression) }
literal = _{ string | atom | boolean | float | integer | string | array | map | constructor }
ident = @{ ("_")? ~ LOWERCASE_LETTER ~ (LETTER | NUMBER | "_")* ~ ("?")? }
ident = @{ ("_" | LOWERCASE_LETTER) ~ (LETTER | NUMBER | "_")* ~ ("?")? }
keyword = ${ ident ~ ":" }
maybe_type_annotation = { ( ":" ~ typename )? }

View file

@ -3,24 +3,20 @@ mod error;
mod precedence;
mod visitor;
use crate::source::Source;
pub use ast::Operator;
pub use ast::{Node, NodeKind, NodeValue};
pub use error::Error;
use pest::Parser;
use std::sync::Arc;
#[derive(Parser)]
#[grammar = "grammar/grammar.pest"]
pub struct Grammar;
pub fn parse_file(input: &str) -> Result<Vec<Node>, Error> {
let pairs = Grammar::parse(Rule::file, input)?;
let nodes = visitor::visit_pairs(pairs)?;
Ok(nodes)
}
pub fn parse_input(input: &str) -> Result<Vec<Node>, Error> {
let pairs = Grammar::parse(Rule::input, input)?;
pub fn parse(source: Arc<Source>, rule: Option<Rule>) -> Result<Vec<Node>, Error> {
let rule = rule.unwrap_or(Rule::file);
let pairs = Grammar::parse(rule, source.as_str())?;
let nodes = visitor::visit_pairs(pairs)?;
Ok(nodes)
@ -31,10 +27,14 @@ mod test {
use super::*;
use ast::{NodeKind, NodeValue, Operator};
fn to_source(input: &str) -> Arc<Source> {
Arc::new(input.into())
}
#[test]
fn test_empty_struct() {
let input = "struct Empty";
let ast = parse_file(input).unwrap();
let ast = parse(to_source(input), Some(Rule::file)).unwrap();
let node = ast.first().unwrap();
assert_eq!(node.kind, NodeKind::Struct);
@ -48,7 +48,7 @@ mod test {
#[test]
fn test_struct_with_fields() {
let input = "struct WithFields(first: Boolean, second: Integer)";
let ast = parse_file(input).unwrap();
let ast = parse(to_source(input), Some(Rule::file)).unwrap();
let node = ast.first().unwrap();
assert_eq!(node.kind, NodeKind::Struct);
@ -76,7 +76,7 @@ mod test {
end
end
"#;
let ast = parse_file(input).unwrap();
let ast = parse(to_source(input), Some(Rule::file)).unwrap();
let node = ast.first().unwrap();
assert_eq!(node.kind, NodeKind::Struct);
@ -90,7 +90,7 @@ mod test {
#[test]
fn test_let_without_annotation() {
let input = r#"let soundtrack = "Passing Breeze""#;
let ast = parse_input(input).unwrap();
let ast = parse(to_source(input), Some(Rule::input)).unwrap();
let node = ast.first().unwrap();
assert_eq!(node.kind, NodeKind::Let);
@ -104,7 +104,7 @@ mod test {
#[test]
fn test_infix_binary() {
let input = r#"1 + 2 * 3"#;
let ast = parse_input(input).unwrap();
let ast = parse(to_source(input), Some(Rule::input)).unwrap();
let node = ast.first().unwrap();
assert_matches!(node.kind, NodeKind::Infix);
@ -123,7 +123,7 @@ mod test {
#[test]
fn test_unary() {
let input = "-1";
let ast = parse_input(input).unwrap();
let ast = parse(to_source(input), Some(Rule::input)).unwrap();
let node = ast.first().unwrap();
assert_eq!(node.kind, NodeKind::Unary);
@ -135,7 +135,7 @@ mod test {
#[test]
fn test_call_remote() {
let input = "Outrun.NIF.integer_add(self, other)";
let ast = parse_input(input).unwrap();
let ast = parse(to_source(input), Some(Rule::input)).unwrap();
let node = ast.first().unwrap();
assert_eq!(node.kind, NodeKind::RemoteCall);
@ -153,7 +153,7 @@ mod test {
#[test]
fn test_string() {
let input = r#""this is a \u0008 string\n""#;
let ast = parse_input(input).unwrap();
let ast = parse(to_source(input), Some(Rule::input)).unwrap();
let node = ast.first().unwrap();
assert_is_string(node, "this is a \u{08} string\n");
@ -162,7 +162,7 @@ mod test {
#[test]
fn test_atom() {
let input = r":GUNSHIP";
let ast = parse_input(input).unwrap();
let ast = parse(to_source(input), Some(Rule::input)).unwrap();
let node = ast.first().unwrap();
assert_is_atom(node, "GUNSHIP");
@ -171,7 +171,7 @@ mod test {
#[test]
fn test_boolean_true() {
let input = "true";
let ast = parse_input(input).unwrap();
let ast = parse(to_source(input), Some(Rule::input)).unwrap();
let node = ast.first().unwrap();
assert_is_boolean(node, true);
@ -180,7 +180,7 @@ mod test {
#[test]
fn test_boolean_false() {
let input = "false";
let ast = parse_input(input).unwrap();
let ast = parse(to_source(input), Some(Rule::input)).unwrap();
let node = ast.first().unwrap();
assert_is_boolean(node, false);
@ -189,7 +189,7 @@ mod test {
#[test]
fn test_array() {
let input = "[1, 2, 3]";
let ast = parse_input(input).unwrap();
let ast = parse(to_source(input), Some(Rule::input)).unwrap();
let node = ast.first().unwrap();
assert_eq!(node.kind, NodeKind::Array);
@ -204,7 +204,7 @@ mod test {
#[test]
fn test_map() {
let input = "{a: 1, :b => 2}";
let ast = parse_input(input).unwrap();
let ast = parse(to_source(input), Some(Rule::input)).unwrap();
let node = ast.first().unwrap();
assert_eq!(node.kind, NodeKind::Map);
@ -224,7 +224,7 @@ mod test {
#[test]
fn test_constructor() {
let input = "AB{a: 1, b: 2}";
let ast = parse_input(input).unwrap();
let ast = parse(to_source(input), Some(Rule::input)).unwrap();
let node = ast.first().unwrap();
assert_eq!(node.kind, NodeKind::Constructor);
@ -243,7 +243,7 @@ mod test {
#[test]
fn test_float() {
let input = "1.21"; // Jiggawatts!
let ast = parse_input(input).unwrap();
let ast = parse(to_source(input), Some(Rule::input)).unwrap();
let node = ast.first().unwrap();
assert_is_float(node, 1.21);
@ -252,7 +252,7 @@ mod test {
#[test]
fn test_local() {
let input = "scandroid";
let ast = parse_input(input).unwrap();
let ast = parse(to_source(input), Some(Rule::input)).unwrap();
let node = ast.first().unwrap();
assert_is_get_local(node, "scandroid");
@ -261,7 +261,7 @@ mod test {
#[test]
fn test_call_local() {
let input = "fury_weekend(1, 2, 3)";
let ast = parse_input(input).unwrap();
let ast = parse(to_source(input), Some(Rule::input)).unwrap();
let node = ast.first().unwrap();
assert_eq!(node.kind, NodeKind::LocalCall);

View file

@ -37,12 +37,9 @@ pub fn visit_pair(pair: Pair<'_, Rule>) -> Result<Node, Error> {
Rule::defs => visit_defs(pair),
Rule::defstruct => visit_struct(pair),
Rule::defuse => visit_use(pair),
Rule::documentation => Ok(Node::new(
NodeKind::Documentation,
NodeValue::Documentation(pair.as_str().to_string()),
pair.as_span(),
)),
Rule::documentation => visit_documentation(pair),
Rule::float => visit_float(pair),
Rule::index => visit_index(pair),
Rule::infix => climb(pair),
Rule::integer => visit_integer(pair),
Rule::let_binding => visit_let(pair),
@ -50,6 +47,7 @@ pub fn visit_pair(pair: Pair<'_, Rule>) -> Result<Node, Error> {
Rule::map => visit_map(pair),
Rule::typeblock => visit_typeblock(pair),
Rule::typename => visit_typename(pair),
Rule::typeunion => climb(pair),
Rule::string => visit_string(pair),
Rule::unary => visit_unary(pair),
rule => Err(Error::unexpected(rule, None, pair.as_span())),
@ -164,16 +162,43 @@ fn visit_constructor(pair: Pair<'_, Rule>) -> Result<Node, Error> {
))
}
fn visit_documentation(pair: Pair<'_, Rule>) -> Result<Node, Error> {
let span = pair.as_span();
let docs = textwrap::dedent(pair.as_str());
Ok(Node::new(
NodeKind::Documentation,
NodeValue::Documentation(docs),
span,
))
}
fn visit_float(pair: Pair<'_, Rule>) -> Result<Node, Error> {
assert_is_rule(&pair, Rule::float)?;
let span = pair.as_span();
let value = pair.as_str().replace("_", "");
let value = f64::from_str(&value)?;
let value = f64::from_str(&value).map_err(|e| Error::ParseFloatError(span.into(), e))?;
Ok(Node::new(NodeKind::Float, NodeValue::Float(value), span))
}
fn visit_index(pair: Pair<'_, Rule>) -> Result<Node, Error> {
assert_is_rule(&pair, Rule::index)?;
let span = pair.as_span();
let mut pairs = pair.into_inner();
let expr = visit_pair(pairs.next().unwrap()).map(Box::new)?;
let field_name = pairs.next().unwrap().as_str().to_string();
Ok(Node::new(
NodeKind::Index,
NodeValue::Index { expr, field_name },
span,
))
}
fn visit_struct(pair: Pair<'_, Rule>) -> Result<Node, Error> {
assert_is_rule(&pair, Rule::defstruct)?;
let span = pair.as_span();
@ -523,8 +548,10 @@ fn visit_integer(pair: Pair<'_, Rule>) -> Result<Node, Error> {
Rule::integer_octal => i64::from_str_radix(&str, 8),
Rule::integer_binary => i64::from_str_radix(&str, 2),
Rule::integer_decimal => i64::from_str_radix(&str, 10),
Rule::integer_zero => Ok(0),
_ => unreachable!(),
}?;
}
.map_err(|e| Error::ParseIntError(span.into(), e))?;
Ok(Node::new(
NodeKind::Integer,

View file

@ -1,20 +1,38 @@
use crate::grammar::NodeKind;
use crate::span::Span;
use miette::Diagnostic;
use outrun_common::StackError;
use thiserror::Error;
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Error, Diagnostic)]
pub enum Error {
#[diagnostic()]
#[error("Failed to parse Outrun source code file; unexpectedly received {received:?} node instead of {expected:?}")]
ExpectedReceived {
#[label]
span: Span,
expected: Vec<NodeKind>,
received: NodeKind,
},
#[error("Unknown type {0}")]
UnknownType(String),
MissingProperty(String),
NotAFunction,
StackError(StackError),
#[error("Missing property {property}")]
MissingProperty {
#[label]
span: Span,
property: String,
},
#[error("{0}")]
StackError(#[from] StackError),
}
impl From<StackError> for Error {
fn from(e: StackError) -> Self {
Error::StackError(e)
impl Error {
pub fn maybe_span(&self) -> Option<Span> {
match self {
Error::ExpectedReceived { span, .. } => Some(*span),
Error::UnknownType(_) => None,
Error::MissingProperty { span, .. } => Some(*span),
Error::StackError(_) => None,
}
}
}

View file

@ -1,5 +1,5 @@
use super::Type;
use crate::grammar::{NodeValue, Operator};
use crate::grammar::NodeValue;
use crate::span::Span;
use std::sync::Arc;
@ -18,11 +18,19 @@ pub enum ExpressionValue {
Constructor(Vec<Expression>),
Float(f64),
GetLocal(String),
Index {
expr: Box<Expression>,
field_name: String,
},
Integer(i64),
Let {
name: String,
value: Box<Expression>,
},
LocalCall {
receiver: Box<Expression>,
arguments: Vec<Expression>,
},
Map(Vec<(Expression, Expression)>),
RemoteCall {
associated_type: Arc<Type>,
@ -30,10 +38,6 @@ pub enum ExpressionValue {
arguments: Vec<Expression>,
},
String(String),
Unary {
op: Operator,
rhs: Box<Expression>,
},
}
impl From<NodeValue> for ExpressionValue {

View file

@ -1,5 +1,6 @@
use super::{Expression, Type};
use crate::grammar::NodeKind;
use std::cell::RefCell;
use std::sync::Arc;
#[derive(Debug, Clone)]
@ -14,8 +15,8 @@ pub struct Function {
pub name: String,
pub arguments: Vec<String>,
pub r#type: Arc<Type>,
pub body: Vec<Expression>,
pub guards: Vec<Expression>,
pub body: RefCell<Vec<Expression>>,
pub guards: RefCell<Vec<Expression>>,
pub access: Access,
}
@ -30,11 +31,19 @@ impl Function {
name,
arguments,
r#type,
body: Vec::new(),
guards: Vec::new(),
body: RefCell::new(Vec::new()),
guards: RefCell::new(Vec::new()),
access: access.into(),
}
}
pub fn push_body(&self, expr: Expression) {
self.body.borrow_mut().push(expr);
}
pub fn push_guard(&self, expr: Expression) {
self.guards.borrow_mut().push(expr);
}
}
impl From<NodeKind> for Access {

View file

@ -11,10 +11,10 @@ pub use error::Error;
pub use expression::{Expression, ExpressionValue};
pub use function::Function;
pub use module::Module;
pub use notice::{Notice, NoticeKind, Severity};
pub use notice::{Notice, NoticeKind, NoticeLevel};
pub use r#type::{Type, TypeVariable};
pub use visitor::{visit_node, visit_nodes};
pub fn improve(mut module: Module) -> Result<Module, Error> {
pub fn improve(module: Module) -> Result<Module, Error> {
stages::link_type_references(module)
}

View file

@ -1,29 +1,35 @@
use super::{Error, Function, Notice, NoticeKind, Severity, Type, TypeVariable};
use super::{Error, Function, Notice, NoticeKind, NoticeLevel, Type, TypeVariable};
use crate::source::Source;
use crate::span::Span;
use outrun_common::Stack;
use outrun_common::{Stack, StackError};
use std::cell::RefCell;
use std::path::{Path, PathBuf};
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct Module {
pub path: Option<PathBuf>,
pub types: Stack<Arc<Type>>,
pub functions: Stack<Function>,
pub source: Arc<Source>,
pub types: Vec<Arc<Type>>,
pub functions: Vec<Arc<Function>>,
pub docs: Stack<String>,
pub notices: Vec<Notice>,
pub current_type: Stack<Arc<Type>>,
pub self_type: Stack<Arc<Type>>,
pub current_function: Stack<Arc<Function>>,
type_id: u64,
}
impl Module {
pub fn new(path: &Path) -> Module {
pub fn new(source: Arc<Source>) -> Module {
bootstrap(Module {
path: Some(path.to_owned()),
types: Stack::new(),
functions: Stack::new(),
source,
types: Vec::new(),
functions: Vec::new(),
docs: Stack::new(),
notices: Vec::new(),
type_id: 0,
current_type: Stack::new(),
self_type: Stack::new(),
current_function: Stack::new(),
})
}
@ -47,7 +53,7 @@ impl Module {
name: name.to_string(),
});
let r#type = Arc::new(Type::Variable { r#type, span });
self.types.push(r#type.clone())?;
self.types.push(r#type.clone());
Ok(r#type)
})
}
@ -57,7 +63,7 @@ impl Module {
self.type_id += 1;
let r#type = RefCell::new(TypeVariable::Unbound { id: type_id });
let r#type = Arc::new(Type::Variable { r#type, span: None });
self.types.push(r#type.clone())?;
self.types.push(r#type.clone());
Ok(r#type)
}
@ -74,7 +80,7 @@ impl Module {
name: name.to_string(),
span,
});
self.types.push(ty.clone()).unwrap();
self.types.push(ty.clone());
ty
});
@ -92,17 +98,19 @@ impl Module {
target,
span,
});
self.types.push(ty.clone())?;
self.types.push(ty.clone());
Ok(ty)
}
pub fn current_function(&mut self) -> Result<&mut Function, Error> {
self.functions.peek_mut().ok_or(Error::NotAFunction)
pub fn current_function(&mut self) -> Result<&mut Arc<Function>, Error> {
self.current_function
.peek_mut()
.ok_or(StackError::Underflow.into())
}
pub fn info(&mut self, span: Option<Span>, kind: NoticeKind) {
self.notices.push(Notice {
severity: Severity::Info,
level: NoticeLevel::Info,
kind,
span,
})
@ -110,7 +118,7 @@ impl Module {
pub fn warning(&mut self, span: Option<Span>, kind: NoticeKind) {
self.notices.push(Notice {
severity: Severity::Warning,
level: NoticeLevel::Warning,
kind,
span,
})
@ -118,7 +126,7 @@ impl Module {
pub fn error(&mut self, span: Option<Span>, kind: NoticeKind) {
self.notices.push(Notice {
severity: Severity::Error,
level: NoticeLevel::Error,
kind,
span,
})
@ -128,30 +136,17 @@ impl Module {
where
F: Fn(Arc<Type>) -> bool,
{
self.types.find(|t| predicate((*t).clone())).cloned()
}
}
impl Default for Module {
fn default() -> Self {
bootstrap(Module {
path: None,
types: Stack::new(),
functions: Stack::new(),
docs: Stack::new(),
notices: Vec::new(),
type_id: 0,
})
self.types.iter().find(|t| predicate((*t).clone())).cloned()
}
}
fn bootstrap(mut module: Module) -> Module {
module.types.push(Arc::new(Type::Atom)).unwrap();
module.types.push(Arc::new(Type::Boolean)).unwrap();
module.types.push(Arc::new(Type::Integer)).unwrap();
module.types.push(Arc::new(Type::Float)).unwrap();
module.types.push(Arc::new(Type::String)).unwrap();
module.types.push(Arc::new(Type::Array)).unwrap();
module.types.push(Arc::new(Type::Map)).unwrap();
module.types.push(Arc::new(Type::Atom));
module.types.push(Arc::new(Type::Boolean));
module.types.push(Arc::new(Type::Integer));
module.types.push(Arc::new(Type::Float));
module.types.push(Arc::new(Type::String));
module.types.push(Arc::new(Type::Array));
module.types.push(Arc::new(Type::Map));
module
}

View file

@ -1,7 +1,7 @@
use crate::span::Span;
#[derive(Debug, Clone)]
pub enum Severity {
pub enum NoticeLevel {
Info,
Warning,
Error,
@ -9,7 +9,7 @@ pub enum Severity {
#[derive(Debug, Clone)]
pub struct Notice {
pub severity: Severity,
pub level: NoticeLevel,
pub kind: NoticeKind,
pub span: Option<Span>,
}

View file

@ -64,10 +64,6 @@ pub enum Type {
}
impl Type {
pub fn is_protocol(&self) -> bool {
matches!(self, Type::Protocol { .. })
}
pub fn is_named(&self) -> bool {
match self {
Type::Variable { r#type, .. } => r#type.borrow().is_named(),

View file

@ -35,7 +35,7 @@ fn consume_expression(module: Module, node: Node) -> Result<Module, Error> {
let (mut module, expression) = visit_expression(module, node)?;
module
.current_function()
.map(|fun| fun.body.push(expression))?;
.map(|fun| fun.push_body(expression))?;
Ok(module)
}
@ -51,6 +51,7 @@ fn consume_statement(module: Module, node: Node) -> Result<Module, Error> {
NodeKind::Struct => consume_type_statement(module, node),
NodeKind::Use => visit_use_statement(module, node),
other => Err(Error::ExpectedReceived {
span: node.span,
expected: vec![
NodeKind::Block,
NodeKind::DefPrivate,
@ -60,6 +61,7 @@ fn consume_statement(module: Module, node: Node) -> Result<Module, Error> {
NodeKind::Let,
NodeKind::Protocol,
NodeKind::Struct,
NodeKind::Use,
],
received: other.clone(),
}),
@ -102,7 +104,7 @@ fn eval_type_union(mut module: Module, node: Node) -> Result<(Module, Arc<Type>)
types,
span: node.span,
});
module.types.push(ty.clone())?;
module.types.push(ty.clone());
Ok((module, ty))
}
}
@ -116,6 +118,7 @@ fn eval_type_union_collect(
) -> Result<(Module, Vec<Arc<Type>>), Error> {
if !matches!(node.kind, NodeKind::Infix | NodeKind::TypeName) {
return Err(Error::ExpectedReceived {
span: node.span,
expected: vec![NodeKind::Infix, NodeKind::TypeName],
received: node.kind.clone(),
});
@ -157,14 +160,17 @@ fn visit_expression(module: Module, node: Node) -> Result<(Module, Expression),
NodeKind::Constructor => visit_constructor(module, node),
NodeKind::Float => visit_float(module, node),
NodeKind::GetLocal => visit_get_local(module, node),
NodeKind::Index => visit_index(module, node),
NodeKind::Infix => visit_infix(module, node),
NodeKind::Integer => visit_integer(module, node),
NodeKind::Let => visit_let_expression(module, node),
NodeKind::LocalCall => visit_local_call(module, node),
NodeKind::Map => visit_map(module, node),
NodeKind::RemoteCall => visit_remote_call(module, node),
NodeKind::String => visit_string(module, node),
NodeKind::Unary => visit_unary(module, node),
other => Err(Error::ExpectedReceived {
span: node.span,
expected: vec![
NodeKind::Array,
NodeKind::Atom,
@ -172,11 +178,15 @@ fn visit_expression(module: Module, node: Node) -> Result<(Module, Expression),
NodeKind::Constructor,
NodeKind::Float,
NodeKind::GetLocal,
NodeKind::Index,
NodeKind::Infix,
NodeKind::Integer,
NodeKind::Let,
NodeKind::LocalCall,
NodeKind::Map,
NodeKind::RemoteCall,
NodeKind::String,
NodeKind::Unary,
],
received: other.clone(),
}),
@ -302,6 +312,7 @@ fn visit_function_statement(mut module: Module, node: Node) -> Result<Module, Er
NodeKind::DefPublic | NodeKind::DefPrivate | NodeKind::DefStatic
) {
return Err(Error::ExpectedReceived {
span: node.span,
expected: vec![
NodeKind::DefPublic,
NodeKind::DefPrivate,
@ -318,7 +329,7 @@ fn visit_function_statement(mut module: Module, node: Node) -> Result<Module, Er
props,
result,
} => {
let associated_type = module.types.peek().cloned()?;
let associated_type = module.current_type.peek().cloned()?;
let span = node.span;
let mut argument_names = Vec::new();
@ -351,23 +362,53 @@ fn visit_function_statement(mut module: Module, node: Node) -> Result<Module, Er
documentation,
span,
});
module.types.push(function_type.clone())?;
module.types.push(function_type.clone());
let function = Function::new(name, argument_names, function_type, node.kind);
module.functions.push(function.clone())?;
let function = Arc::new(Function::new(
name,
argument_names,
function_type,
node.kind,
));
module.functions.push(function.clone());
module.current_function.push(function.clone())?;
if let Some(guard) = props.get("when") {
let (new_module, expr) = visit_expression(module, guard.clone())?;
module = new_module;
module.current_function().map(|fun| fun.guards.push(expr))?;
match &guard.value {
NodeValue::Block(nodes) => {
for node in nodes {
let (new_module, expr) = visit_expression(module, node.clone())?;
module = new_module;
module.current_function().map(|fun| fun.push_guard(expr))?;
}
}
_ => {
let (new_module, expr) = visit_expression(module, guard.clone())?;
module = new_module;
module.current_function().map(|fun| fun.push_guard(expr))?;
}
}
}
if let Some(body) = props.get("as") {
let (new_module, expr) = visit_expression(module, body.clone())?;
module = new_module;
module.current_function().map(|fun| fun.body.push(expr))?
match &body.value {
NodeValue::Block(nodes) => {
for node in nodes {
let (new_module, expr) = visit_expression(module, node.clone())?;
module = new_module;
module.current_function().map(|fun| fun.push_body(expr))?;
}
}
_ => {
let (new_module, expr) = visit_expression(module, body.clone())?;
module = new_module;
module.current_function().map(|fun| fun.push_body(expr))?
}
}
}
module.current_function.pop()?;
for key in props.keys() {
if key == "as" {
continue;
@ -417,7 +458,10 @@ fn visit_impl(mut module: Module, node: Node) -> Result<(Module, Arc<Type>), Err
.and_then(|name| module.reference_type(name, Some(node.span)))?;
let augments = props
.get("for")
.ok_or(Error::MissingProperty("for".to_string()))
.ok_or(Error::MissingProperty {
span: node.span,
property: "for".to_string(),
})
.and_then(|node| {
let name = typename_to_string(&node)?;
let ty = module.reference_type(name, Some(node.span))?;
@ -428,17 +472,22 @@ fn visit_impl(mut module: Module, node: Node) -> Result<(Module, Arc<Type>), Err
let ty = Arc::new(Type::Impl {
protocol,
augments,
augments: augments.clone(),
documentation,
span: node.span,
});
module.types.push(ty.clone())?;
module.types.push(ty.clone());
module.current_type.push(ty.clone())?;
module.self_type.push(augments)?;
if let Some(node) = props.get("as") {
let new_module = consume_statement(module, node.clone())?;
module = new_module;
}
module.self_type.pop()?;
module.current_type.pop()?;
for key in props.keys() {
if key == "as" {
continue;
@ -459,6 +508,33 @@ fn visit_impl(mut module: Module, node: Node) -> Result<(Module, Arc<Type>), Err
}
}
fn visit_index(module: Module, node: Node) -> Result<(Module, Expression), Error> {
assert_is_kind(&node, NodeKind::Index)?;
match node.value {
NodeValue::Index { expr, field_name } => {
let (mut module, expr) = visit_expression(module, *expr)?;
let r#type = module.unbound_type()?;
let value = ExpressionValue::Index {
expr: Box::new(expr),
field_name,
};
Ok((
module,
Expression {
r#type,
span: node.span,
value,
},
))
}
_ => unreachable!(),
}
}
fn visit_infix(module: Module, node: Node) -> Result<(Module, Expression), Error> {
assert_is_kind(&node, NodeKind::Infix)?;
@ -550,6 +626,44 @@ fn visit_let_expression(mut module: Module, node: Node) -> Result<(Module, Expre
}
}
fn visit_local_call(module: Module, node: Node) -> Result<(Module, Expression), Error> {
assert_is_kind(&node, NodeKind::LocalCall)?;
match node.value {
NodeValue::LocalCall {
receiver,
arguments,
} => {
let (mut module, receiver) = visit_expression(module, *receiver)?;
let receiver = Box::new(receiver);
let mut args = Vec::new();
for arg in arguments {
let (new_module, arg) = visit_expression(module, arg)?;
module = new_module;
args.push(arg);
}
let value = ExpressionValue::LocalCall {
receiver,
arguments: args,
};
let r#type = module.unbound_type()?;
Ok((
module,
Expression {
r#type,
span: node.span,
value,
},
))
}
_ => unreachable!(),
}
}
fn visit_map(mut module: Module, node: Node) -> Result<(Module, Expression), Error> {
assert_is_kind(&node, NodeKind::Map)?;
@ -606,10 +720,18 @@ fn visit_protocol(mut module: Module, node: Node) -> Result<(Module, Arc<Type>),
span: node.span,
});
module.types.push(ty.clone());
module.current_type.push(ty.clone())?;
let self_type = module.unbound_type()?;
module.self_type.push(self_type)?;
if let Some(node) = props.get("as") {
module = consume_statement(module, node.clone())?;
}
module.self_type.pop()?;
module.current_type.pop()?;
for key in props.keys() {
if key == "as" {
continue;
@ -722,12 +844,17 @@ fn visit_struct(mut module: Module, node: Node) -> Result<(Module, Arc<Type>), E
span: node.span,
});
module.types.push(r#type.clone())?;
module.types.push(r#type.clone());
module.current_type.push(r#type.clone())?;
module.self_type.push(r#type.clone())?;
if let Some(node) = props.get("as") {
module = consume_statement(module, node.clone())?;
}
module.self_type.pop()?;
module.current_type.pop()?;
for key in props.keys() {
if key == "as" {
continue;
@ -754,6 +881,7 @@ fn visit_type_statement(module: Module, node: Node) -> Result<(Module, Arc<Type>
NodeKind::Protocol => visit_protocol(module, node),
NodeKind::Impl => visit_impl(module, node),
other => Err(Error::ExpectedReceived {
span: node.span,
expected: vec![NodeKind::Struct, NodeKind::Protocol, NodeKind::Impl],
received: other.clone(),
}),
@ -832,18 +960,14 @@ fn assert_is_kind(node: &Node, kind: NodeKind) -> Result<(), Error> {
}
Err(Error::ExpectedReceived {
span: node.span,
expected: vec![kind],
received: node.kind.clone(),
})
}
fn self_type(mut module: Module) -> Result<(Module, Arc<Type>), Error> {
let parent = module.types.peek().cloned()?;
fn self_type(module: Module) -> Result<(Module, Arc<Type>), Error> {
let parent = module.self_type.peek().cloned()?;
if parent.is_protocol() {
let ty = module.unbound_type()?;
Ok((module, ty))
} else {
Ok((module, parent))
}
Ok((module, parent))
}

View file

@ -1,4 +1,6 @@
extern crate outrun_common;
extern crate pest;
extern crate thiserror;
#[macro_use]
extern crate pest_derive;
@ -10,66 +12,24 @@ extern crate lazy_static;
#[macro_use]
extern crate assert_matches;
extern crate outrun_common;
mod error;
mod grammar;
mod ir;
mod source;
mod span;
mod types;
use std::sync::Arc;
pub use error::Error;
pub use ir::Module;
pub use source::Source;
pub fn compile_file(input: &str) -> Result<(), Error> {
let untyped_ast = grammar::parse_file(input)?;
// println!("AST: {:#?}", untyped_ast);
pub fn compile(source: Arc<Source>) -> Result<Module, Error> {
let ast = grammar::parse(source.clone(), None).map_err(|e| (source.clone(), e))?;
let module = ir::Module::default();
let module = ir::visit_nodes(module, untyped_ast)?;
let module = ir::improve(module)?;
let module = Module::new(source.clone());
let module = ir::visit_nodes(module, ast).map_err(|e| (source.clone(), e))?;
let module = ir::improve(module).map_err(|e| (source.clone(), e))?;
println!("IR {:#?}", module);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_it_works() {
let input = r#"
use AbsoluteValue
use Option
use Outrun.NIF, as: NIF
```doc
# Outrun.Core.Integer
The native implementation of `Integer` based around Rust's `i64` type.
Has no constructor as it is created internally in the virtual machine by literals.
```
struct Outrun.Core.Integer, as: do
```doc
Computes the absolute value of `self`.
Returns none on overflow.
```
def abs(self: Self): Option, as: NIF.integer_abs(self)
end
impl AbsoluteValue, for: Outrun.Core.Integer, as: do
def abs(self: Self): Self, as: NIF.integer_abs(self)
end
"#;
let result = compile_file(input);
println!("{:#?}", result);
assert!(false);
}
Ok(module)
}

View file

@ -0,0 +1,96 @@
use crate::error::Error;
use miette::{MietteError, MietteSpanContents, SourceCode, SourceSpan, SpanContents};
use std::path::{Path, PathBuf};
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct Source {
pub contents: String,
pub location: Option<PathBuf>,
}
impl Source {
pub fn from_file(path: &Path) -> Result<Source, Error> {
let mut source = Source {
contents: String::new(),
location: Some(path.to_owned()),
};
source.contents =
std::fs::read_to_string(path).map_err(|e| (Arc::new(source.clone()), e))?;
Ok(source)
}
pub fn from_input<T: ToString>(input: T) -> Result<Source, Error> {
Ok(Source {
contents: input.to_string(),
location: None,
})
}
pub fn as_str(&self) -> &str {
&self.contents
}
pub fn as_path(&self) -> Option<&Path> {
match self.location {
Some(ref location) => Some(location),
None => None,
}
}
}
impl SourceCode for Source {
fn read_span<'a>(
&'a self,
span: &SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
let contents = self
.contents
.read_span(span, context_lines_before, context_lines_after)?;
match self.location {
Some(ref location) => Ok(Box::new(MietteSpanContents::new_named(
location.to_string_lossy().to_string(),
contents.data(),
*contents.span(),
contents.line(),
contents.column(),
contents.line_count(),
))),
None => Ok(Box::new(MietteSpanContents::new(
contents.data(),
*contents.span(),
contents.line(),
contents.column(),
contents.line_count(),
))),
}
}
}
impl From<String> for Source {
fn from(contents: String) -> Source {
Source::from_input(contents).unwrap()
}
}
impl From<&str> for Source {
fn from(contents: &str) -> Source {
Source::from_input(contents).unwrap()
}
}
impl From<PathBuf> for Source {
fn from(path: PathBuf) -> Source {
Source::from_file(&path).unwrap()
}
}
impl From<&Path> for Source {
fn from(path: &Path) -> Source {
Source::from_file(path).unwrap()
}
}

View file

@ -1,7 +1,10 @@
use miette::SourceSpan;
use pest::error::InputLocation;
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
pub struct Span {
start: usize,
end: usize,
pub start: usize,
pub end: usize,
}
impl Span {
@ -34,3 +37,18 @@ impl From<pest::Span<'_>> for Span {
Self::new(span.start(), span.end())
}
}
impl From<pest::error::InputLocation> for Span {
fn from(location: pest::error::InputLocation) -> Self {
match location {
InputLocation::Span(span) => span.into(),
InputLocation::Pos(pos) => pos.into(),
}
}
}
impl Into<SourceSpan> for Span {
fn into(self) -> SourceSpan {
SourceSpan::new(self.start.into(), (self.end - self.start).into())
}
}

View file

@ -1,28 +0,0 @@
use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq)]
pub enum Type {
Struct {
name: String,
fields: HashMap<String, Arc<Type>>,
},
Protocol {
name: String,
requires: Vec<Arc<Type>>,
},
Function {
name: String,
associated_type: Arc<Type>,
arguments: HashMap<String, Arc<Type>>,
return_type: Arc<Type>,
},
Variable(Arc<RefCell<TypeVar>>),
}
#[derive(Debug, Clone, PartialEq)]
pub enum TypeVar {
Unbound { id: usize },
Link { r#type: Arc<Type> },
}

View file

@ -9,4 +9,5 @@ version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
miette = {version = "5.3", features = ["fancy"]}
outrun-compiler = {path = "../outrun-compiler"}

View file

@ -3,4 +3,6 @@
Defines a special protocol which applies to all Outrun types. Usually used in
place of a generic type argument.
```
protocol Any
protocol Any, as: do
def type_name(self: Self): String
end

View file

@ -1,4 +1,5 @@
use Outrun.Core.Boolean
use LogicalNot
```doc
# Boolean
@ -16,5 +17,5 @@ protocol Boolean, as: do
```doc
Is the value false?
```
def false?(self: Self): Outrun.Core.Boolean, as: !true?(self)
def false?(self: Self): Outrun.Core.Boolean, as: LogicalNot.not(Boolean.true?(self))
end

View file

@ -1,9 +1,10 @@
use Any
use Outrun.NIF, as: NIF
use String
```doc
# Error
```
protocol Error, as: do
def message(self: Self): String

View file

@ -0,0 +1,13 @@
```doc
# LogicalNot
Values which wish to implement the `!` unary operator must implement this protocol.
```
protocol LogicalNot, as: do
```doc
Return the logical inverse of the value.
Must be implemented.
```
def not(self: Self): Boolean
end

View file

@ -19,6 +19,6 @@ protocol LogicalOr, as: do
If `LogicalOr.true?` is true for the left-hand value then it is returned, otherwise the right-hand value is returned.
```
def or(self: Self, _other: Any): Any, when: true?(self), as: self
def or(self: Self, _other: Any): Any, when: LogicalOr.true?(self), as: self
def or(_self: Self, other: Any): Any, as: other
end

View file

@ -3,6 +3,7 @@ use Boolean
use Option
use Outrun.Core.Some, as: Some
use Outrun.Core.None, as: None
use LogicalNot
```doc
# Option
@ -30,7 +31,7 @@ protocol Option, as: do
```doc
Returns true if the `Option` cannot provide a value, false otherwise.
```
def none?(option: Self): Boolean, as: !some?(option)
def none?(option: Self): Boolean, as: LogicalNot.not(Option.some?(option))
```doc
Create a new "some" value using the default some type.

View file

@ -43,3 +43,11 @@ impl LogicalAnd, for: Outrun.Core.Boolean, as: do
defp boolean_to_option(result: Self): Option, when: result, as: Option.some(result)
defp boolean_to_option(_result: Self): Option, as: Option.none()
end
impl LogicalNot, for: Outrun.Core.Boolean, as: do
```doc
Concrete implementation of `!`.
```
def not(self: Self): Self, when: self, as: false
def not(self: Self): Self, as: true
end

View file

@ -7,7 +7,7 @@ use Result
The default implementation of an "Error" result type.
```
struct Outrun.Core.Error(error: Error), as: do
defs new(error: Error), as: Self { error }
defs new(error: Error), as: Self { error: error }
end
impl Result, for: Outrun.Core.Error, as: do
@ -19,7 +19,7 @@ impl Result, for: Outrun.Core.Error, as: do
end
struct Outrun.Core.Error.UnwrapError(value: Outrun.Core.Error), as: do
defs new(value: Outrun.Core.Error), as: Self { value }
defs new(value: Outrun.Core.Error), as: Self { value: value }
def message(self: Self): String, as: "Attempted to unwrap on an error result"
end

View file

@ -15,7 +15,7 @@ impl Option, for: Outrun.Core.None, as: do
def none?(_self: Self): Boolean, as: true
def unwrap(_self: Self): Any, as: do
Error.raise(Self.UnwrapError)
Error.raise(Self.UnwrapError{})
end
defs new(): Self, as: Self {}

View file

@ -8,7 +8,7 @@ use String
The default implementation of an "Okay" result type.
```
struct Outrun.Core.Okay(value: Any), as: do
defs new(value: Any), as: Self { value }
defs new(value: Any), as: Self { value: value }
end
impl Result, for: Outrun.Core.Okay, as: do
@ -20,7 +20,7 @@ impl Result, for: Outrun.Core.Okay, as: do
end
struct Outrun.Core.Okay.UnwrapError(value: Outrun.Core.Okay), as: do
defs new(value: Outrun.Core.Okay), as: Self { value }
defs new(value: Outrun.Core.Okay), as: Self { value: value }
def message(self: Self): String, as: "Attempted to unwrap_error on a successful result"
end

View file

@ -17,5 +17,5 @@ impl Option, for: Outrun.Core.Some, as: do
def unwrap(self: Self): Any, as: self.value
defs new(value: Any): Self, as: Self { value }
defs new(value: Any): Self, as: Self { value: value }
end

View file

@ -4,6 +4,7 @@ use Error
use Result
use Outrun.Core.Okay
use Outrun.Core.Error
use LogicalNot
```doc
# Result
@ -24,7 +25,7 @@ protocol Result, as: do
```doc
Returns true if the result is an error.
```
def error?(self: Self): Boolean, as: !okay?(self)
def error?(self: Self): Boolean, as: LogicalNot.not(okay?(self))
```doc
Returns the value of the result on success.

View file

@ -1,49 +1,59 @@
#[macro_use]
extern crate miette;
extern crate outrun_compiler;
// use outrun_compiler::compiler::Error;
use outrun_compiler::{Error, Source};
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::Arc;
// pub fn init() -> Result<(), Error> {
// let path = Path::new(env!("CARGO_MANIFEST_DIR"));
// let path = path.join("lib");
// let path = path.as_path();
pub fn init() -> Result<(), Error> {
let path = Path::new(env!("CARGO_MANIFEST_DIR"));
let path = path.join("lib");
let path = path.as_path();
// let files = collect_files(path, Vec::new())?;
let files = collect_files(path, Vec::new())?;
// // for path in files.iter() {
// // outrun_compiler::compiler::compile_file(path)?;
// // }
for path in files.iter() {
let source = Source::from_file(path)?;
let source = Arc::new(source);
outrun_compiler::compile(source)?;
}
// Ok(())
// }
Ok(())
}
// fn collect_files(path: &Path, mut files: Vec<PathBuf>) -> Result<Vec<PathBuf>, Error> {
// let entries = fs::read_dir(path).map_err(|e| Error::from((path, e)))?;
// for entry in entries {
// let entry = entry.map_err(|e| Error::from((path, e)))?;
// let path = entry.path();
// let path = path.as_path();
// if path.is_dir() {
// files = collect_files(&path, files)?;
// } else if let Some(extension) = path.extension() {
// if extension == "run" {
// files.push(path.to_owned());
// }
// }
// }
fn collect_files(path: &Path, mut files: Vec<PathBuf>) -> Result<Vec<PathBuf>, Error> {
let entries = fs::read_dir(path)?;
for entry in entries {
let entry = entry?;
let path = entry.path();
let path = path.as_path();
if path.is_dir() {
files = collect_files(&path, files)?;
} else if let Some(extension) = path.extension() {
if extension == "outrun" {
files.push(path.to_owned());
}
}
}
// Ok(files)
// }
Ok(files)
}
// // #[cfg(test)]
// // mod test {
// // use super::*;
#[cfg(test)]
mod test {
use super::*;
// // #[test]
// // fn test_init() {
// // let result = init().unwrap();
// // println!("result: {:?}", result);
// // assert!(false);
// // }
// // }
use miette::Result;
#[test]
fn test_init() -> Result<()> {
let result = init()?;
// println!("result: {:?}", result);
assert!(false);
Ok(())
}
}