🤔 Add a type inference engine, along with typed literals. #4
13
build.rs
13
build.rs
@@ -58,6 +58,11 @@ fn generate_tests(f: &mut File, path_so_far: PathBuf) -> std::io::Result<()> {
|
||||
" assert_ne!(errors.len(), 0, \"should have seen an error\");"
|
||||
)?;
|
||||
} else {
|
||||
// NOTE: Since the advent of defaulting rules and type checking, we
|
||||
// can't guarantee that syntax.eval() will return the same result as
|
||||
// ir.eval() or backend::eval(). We must do type checking to force
|
||||
// constants into the right types, first. So this now checks only that
|
||||
// the result of ir.eval() and backend::eval() are the same.
|
||||
writeln!(
|
||||
f,
|
||||
" let syntax = syntax.expect(\"file should have parsed\");"
|
||||
@@ -67,17 +72,13 @@ fn generate_tests(f: &mut File, path_so_far: PathBuf) -> std::io::Result<()> {
|
||||
f,
|
||||
" assert_eq!(errors.len(), 0, \"file should have no validation errors\");"
|
||||
)?;
|
||||
writeln!(f, " let syntax_result = syntax.eval();")?;
|
||||
writeln!(
|
||||
f,
|
||||
" let ir = syntax.type_infer().expect(\"example is typed correctly\");"
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
" assert_eq!(syntax_result, ir.eval(), \"syntax equivalent to IR\");"
|
||||
)?;
|
||||
writeln!(f, " let ir_result = ir.eval();")?;
|
||||
writeln!(f, " let compiled_result = Backend::<JITModule>::eval(ir);")?;
|
||||
writeln!(f, " assert_eq!(syntax_result, compiled_result);")?;
|
||||
writeln!(f, " assert_eq!(ir_result, compiled_result);")?;
|
||||
}
|
||||
writeln!(f, "}}")?;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::backend::{Backend, BackendError};
|
||||
use crate::type_infer::TypeInferenceResult;
|
||||
use crate::syntax::{ConstantType, Location, ParserError, Statement};
|
||||
use crate::type_infer::TypeInferenceResult;
|
||||
use codespan_reporting::diagnostic::Diagnostic;
|
||||
use codespan_reporting::files::SimpleFiles;
|
||||
use codespan_reporting::term::{self, Config};
|
||||
|
||||
@@ -108,23 +108,19 @@ impl ParserError {
|
||||
ParseError::InvalidToken { location } => {
|
||||
ParserError::InvalidToken(Location::new(file_idx, location..location + 1))
|
||||
}
|
||||
ParseError::UnrecognizedEof { location, expected } => {
|
||||
ParserError::UnrecognizedEOF(Location::new(file_idx, location..location+1), expected)
|
||||
}
|
||||
ParseError::UnrecognizedEof { location, expected } => ParserError::UnrecognizedEOF(
|
||||
Location::new(file_idx, location..location + 1),
|
||||
expected,
|
||||
),
|
||||
ParseError::UnrecognizedToken {
|
||||
token: (start, token, end),
|
||||
expected,
|
||||
} => ParserError::UnrecognizedToken(
|
||||
Location::new(file_idx, start..end),
|
||||
token,
|
||||
expected,
|
||||
),
|
||||
} => {
|
||||
ParserError::UnrecognizedToken(Location::new(file_idx, start..end), token, expected)
|
||||
}
|
||||
ParseError::ExtraToken {
|
||||
token: (start, token, end),
|
||||
} => ParserError::ExtraToken(
|
||||
Location::new(file_idx, start..end),
|
||||
token,
|
||||
),
|
||||
} => ParserError::ExtraToken(Location::new(file_idx, start..end), token),
|
||||
ParseError::User { error } => match error {
|
||||
LexerError::LexFailure(offset) => {
|
||||
ParserError::LexFailure(Location::new(file_idx, offset..offset + 1))
|
||||
@@ -185,9 +181,7 @@ impl<'a> From<&'a ParserError> for Diagnostic<usize> {
|
||||
|
||||
Diagnostic::error()
|
||||
.with_message(expected_str)
|
||||
.with_labels(vec![
|
||||
loc.primary_label().with_message(unexpected_str)
|
||||
])
|
||||
.with_labels(vec![loc.primary_label().with_message(unexpected_str)])
|
||||
}
|
||||
|
||||
// I think we get this when we get a token, but were expected EOF
|
||||
@@ -198,9 +192,7 @@ impl<'a> From<&'a ParserError> for Diagnostic<usize> {
|
||||
|
||||
Diagnostic::error()
|
||||
.with_message(expected_str)
|
||||
.with_labels(vec![
|
||||
loc.primary_label().with_message(unexpected_str)
|
||||
])
|
||||
.with_labels(vec![loc.primary_label().with_message(unexpected_str)])
|
||||
}
|
||||
|
||||
// simple lexer errors
|
||||
@@ -289,7 +281,10 @@ fn order_of_operations() {
|
||||
Location::new(testfile, 6..7),
|
||||
"+".to_string(),
|
||||
vec![
|
||||
Expression::Value(Location::new(testfile, 4..5), Value::Number(None, None, 1),),
|
||||
Expression::Value(
|
||||
Location::new(testfile, 4..5),
|
||||
Value::Number(None, None, 1),
|
||||
),
|
||||
Expression::Primitive(
|
||||
Location::new(testfile, 10..11),
|
||||
"*".to_string(),
|
||||
|
||||
@@ -67,18 +67,17 @@ impl Arbitrary for Program {
|
||||
defined_variables.insert(psi.name.name.clone(), psi.binding_type);
|
||||
statements.push(
|
||||
expr.prop_map(move |expr| {
|
||||
Statement::Binding(
|
||||
Location::manufactured(),
|
||||
psi.name.clone(),
|
||||
expr,
|
||||
)
|
||||
Statement::Binding(Location::manufactured(), psi.name.clone(), expr)
|
||||
})
|
||||
.boxed(),
|
||||
);
|
||||
} else {
|
||||
let printers = defined_variables
|
||||
.keys()
|
||||
.map(|n| Just(Statement::Print(Location::manufactured(), Name::manufactured(n))));
|
||||
let printers = defined_variables.keys().map(|n| {
|
||||
Just(Statement::Print(
|
||||
Location::manufactured(),
|
||||
Name::manufactured(n),
|
||||
))
|
||||
});
|
||||
statements.push(Union::new(printers).boxed());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,11 +31,17 @@ pub struct Name {
|
||||
|
||||
impl Name {
|
||||
pub fn new<S: ToString>(n: S, location: Location) -> Name {
|
||||
Name{ name: n.to_string(), location }
|
||||
Name {
|
||||
name: n.to_string(),
|
||||
location,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn manufactured<S: ToString>(n: S) -> Name {
|
||||
Name{ name: n.to_string(), location: Location::manufactured() }
|
||||
Name {
|
||||
name: n.to_string(),
|
||||
location: Location::manufactured(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn intern(self) -> ArcIntern<String> {
|
||||
|
||||
@@ -76,10 +76,7 @@ impl Location {
|
||||
/// this particular location. You'll need to extend it with actually useful
|
||||
/// information, like what kind of error it is.
|
||||
pub fn error(&self) -> Diagnostic<usize> {
|
||||
Diagnostic::error().with_labels(vec![Label::primary(
|
||||
self.file_idx,
|
||||
self.location.clone(),
|
||||
)])
|
||||
Diagnostic::error().with_labels(vec![Label::primary(self.file_idx, self.location.clone())])
|
||||
}
|
||||
|
||||
/// Return an error diagnostic centered at this location, with the given message.
|
||||
@@ -89,11 +86,9 @@ impl Location {
|
||||
/// even more information to ut, using [`Diagnostic::with_labels`],
|
||||
/// [`Diagnostic::with_notes`], or [`Diagnostic::with_code`].
|
||||
pub fn labelled_error(&self, msg: &str) -> Diagnostic<usize> {
|
||||
Diagnostic::error().with_labels(vec![Label::primary(
|
||||
self.file_idx,
|
||||
self.location.clone(),
|
||||
)
|
||||
.with_message(msg)])
|
||||
Diagnostic::error().with_labels(vec![
|
||||
Label::primary(self.file_idx, self.location.clone()).with_message(msg)
|
||||
])
|
||||
}
|
||||
|
||||
/// Merge two locations into a single location spanning the whole range between
|
||||
@@ -105,9 +100,20 @@ impl Location {
|
||||
if self.file_idx != other.file_idx {
|
||||
None
|
||||
} else {
|
||||
let start = if self.location.start <= other.location.start { self.location.start } else { other.location.start };
|
||||
let end = if self.location.end >= other.location.end { self.location.end } else { other.location.end };
|
||||
Some(Location { file_idx: self.file_idx, location: start..end })
|
||||
let start = if self.location.start <= other.location.start {
|
||||
self.location.start
|
||||
} else {
|
||||
other.location.start
|
||||
};
|
||||
let end = if self.location.end >= other.location.end {
|
||||
self.location.end
|
||||
} else {
|
||||
other.location.end
|
||||
};
|
||||
Some(Location {
|
||||
file_idx: self.file_idx,
|
||||
location: start..end,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::ast as ir;
|
||||
use super::ast::Type;
|
||||
use crate::eval::PrimitiveType;
|
||||
use crate::type_infer::solve::Constraint;
|
||||
use crate::syntax::{self, ConstantType};
|
||||
use crate::type_infer::solve::Constraint;
|
||||
use internment::ArcIntern;
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
@@ -112,7 +112,10 @@ fn convert_expression(
|
||||
let newtype = ir::gentype();
|
||||
let newval = ir::Value::Unknown(base, value);
|
||||
|
||||
constraint_db.push(Constraint::ConstantNumericType(loc.clone(), newtype.clone()));
|
||||
constraint_db.push(Constraint::ConstantNumericType(
|
||||
loc.clone(),
|
||||
newtype.clone(),
|
||||
));
|
||||
(newval, newtype)
|
||||
}
|
||||
Some(ConstantType::U8) => (
|
||||
|
||||
@@ -373,10 +373,13 @@ pub fn solve_constraints(
|
||||
// we try to advance it to a primitive
|
||||
Constraint::ConstantNumericType(loc, Type::Variable(vloc, var)) => {
|
||||
match resolutions.get(&var) {
|
||||
None => constraint_db
|
||||
.push(Constraint::ConstantNumericType(loc, Type::Variable(vloc, var))),
|
||||
None => constraint_db.push(Constraint::ConstantNumericType(
|
||||
loc,
|
||||
Type::Variable(vloc, var),
|
||||
)),
|
||||
Some(nt) => {
|
||||
constraint_db.push(Constraint::ConstantNumericType(loc, Type::Primitive(*nt)));
|
||||
constraint_db
|
||||
.push(Constraint::ConstantNumericType(loc, Type::Primitive(*nt)));
|
||||
changed_something = true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user