Shifting and naming.

This commit is contained in:
2025-10-11 14:46:02 -07:00
parent 55df27de98
commit 9ea6868938
9 changed files with 478 additions and 377 deletions

View File

@@ -1,277 +1,14 @@
mod ast;
mod error;
mod location;
mod name;
mod parse;
#[cfg(test)]
mod parser_tests;
pub mod tokens;
mod tokens;
mod universe;
pub use crate::syntax::error::ParserError;
use crate::syntax::parse::Parser;
use crate::syntax::tokens::Lexer;
use internment::ArcIntern;
pub use ast::*;
pub use location::{Located, Location};
use memmap2::Mmap;
pub use name::Name;
use proptest_derive::Arbitrary;
use std::collections::HashMap;
use std::fmt::Debug;
use std::ops::Range;
use std::path::{Path, PathBuf};
pub struct Universe {
pub files: HashMap<PathBuf, Mmap>,
pub modules: HashMap<PathBuf, Module>,
}
impl Default for Universe {
fn default() -> Self {
Universe {
files: HashMap::new(),
modules: HashMap::new(),
}
}
}
impl Universe {
pub fn add_file<P: AsRef<Path>>(&mut self, file: P) -> Result<(), ParserError> {
let filename = file.as_ref().to_string_lossy().into_owned();
let file_handle = std::fs::File::open(&file)
.map_err(|e| ParserError::OpenError {
file: filename.clone(),
error: e,
})?;
let contents = unsafe { Mmap::map(&file_handle) }
.map_err(|e| ParserError::ReadError {
file: filename.clone(),
error: e,
})?;
let string_contents = std::str::from_utf8(&contents)
.map_err(|e| ParserError::Utf8Error {
file: filename.clone(),
error: e,
})?;
let lexer = Lexer::from(string_contents);
let mut parser = Parser::new(&file, lexer);
let module = parser.parse_module()?;
self.modules.insert(file.as_ref().to_path_buf(), module);
Ok(())
}
}
#[derive(Debug)]
pub struct Module {
definitions: Vec<Definition>,
}
#[derive(Debug)]
pub struct Definition {
location: Location,
export: ExportClass,
type_restrictions: TypeRestrictions,
definition: Def,
}
impl Located for Definition {
fn location(&self) -> Location {
self.location.clone()
}
}
#[derive(Debug)]
pub enum Def {
Enumeration(EnumerationDef),
Structure(StructureDef),
Function(FunctionDef),
Value(ValueDef),
}
impl Located for Def {
fn location(&self) -> Location {
match self {
Def::Enumeration(def) => def.location.clone(),
Def::Structure(def) => def.location.clone(),
Def::Function(def) => def.location.clone(),
Def::Value(def) => def.location.clone(),
}
}
}
#[derive(Debug)]
pub struct EnumerationDef {
name: String,
location: Location,
variants: Vec<EnumerationVariant>,
}
#[derive(Debug)]
pub struct EnumerationVariant {
location: Location,
name: String,
argument: Option<Type>,
}
#[derive(Debug)]
pub struct StructureDef {
name: String,
location: Location,
fields: Vec<StructureField>,
}
#[derive(Debug)]
pub struct StructureField {
location: Location,
export: ExportClass,
name: String,
field_type: Option<Type>,
}
#[derive(Debug)]
pub struct FunctionDef {
name: String,
location: Location,
arguments: Vec<FunctionArg>,
return_type: Option<Type>,
body: Vec<Statement>,
}
#[derive(Debug)]
pub struct FunctionArg {
name: String,
arg_type: Option<Type>,
}
#[derive(Debug)]
pub struct ValueDef {
name: String,
location: Location,
value: Expression,
}
#[derive(Debug)]
pub enum ExportClass {
Public,
Private,
}
#[derive(Debug)]
pub enum Statement {
Binding(BindingStmt),
Expression(Expression),
}
#[derive(Debug)]
pub struct BindingStmt {
location: Location,
mutable: bool,
variable: Name,
value: Expression,
}
#[derive(Debug)]
pub enum Expression {
Value(ConstantValue),
Reference(Name),
EnumerationValue(Name, Name, Option<Box<Expression>>),
StructureValue(Name, Vec<FieldValue>),
Conditional(ConditionalExpr),
Call(Box<Expression>, CallKind, Vec<Expression>),
Block(Location, Vec<Statement>),
}
#[derive(Debug)]
pub struct ConditionalExpr {
location: Location,
test: Box<Expression>,
consequent: Box<Expression>,
alternative: Option<Box<Expression>>,
}
#[derive(Debug)]
pub enum CallKind {
Infix,
Normal,
Postfix,
Prefix,
}
#[derive(Debug)]
pub struct FieldValue {
field: Name,
value: Expression,
}
#[derive(Debug)]
pub struct TypeRestrictions {
restrictions: Vec<TypeRestriction>,
}
impl TypeRestrictions {
fn empty() -> Self {
TypeRestrictions {
restrictions: vec![],
}
}
}
#[derive(Debug)]
pub struct TypeRestriction {
constructor: Type,
arguments: Vec<Type>,
}
#[derive(Debug)]
pub enum Type {
Constructor(Location, String),
Variable(Location, String),
Primitive(Location, String),
Application(Box<Type>, Vec<Type>),
Function(Vec<Type>, Box<Type>),
}
impl Located for Type {
fn location(&self) -> Location {
match self {
Type::Constructor(l, _) => l.clone(),
Type::Variable(l, _) => l.clone(),
Type::Primitive(l, _) => l.clone(),
Type::Application(t1, ts) => {
let mut result = t1.location();
if let Some(last) = ts.last() {
result = result.extend_to(&last.location());
}
result
}
Type::Function(args, ret) => {
if let Some(first) = args.first() {
first.location().extend_to(&ret.location())
} else {
ret.location()
}
}
}
}
}
#[derive(Debug)]
pub enum ConstantValue {
Integer(Location, IntegerWithBase),
Character(Location, char),
String(Location, String),
}
#[derive(Clone, Debug, PartialEq, Eq, Arbitrary)]
pub struct IntegerWithBase {
#[proptest(strategy = "proptest::prop_oneof![ \
proptest::strategy::Just(None), \
proptest::strategy::Just(Some(2)), \
proptest::strategy::Just(Some(8)), \
proptest::strategy::Just(Some(10)), \
proptest::strategy::Just(Some(16)), \
]")]
base: Option<u8>,
value: u64,
}

228
src/syntax/ast.rs Normal file
View File

@@ -0,0 +1,228 @@
use crate::syntax::location::{Located, Location};
use crate::syntax::name::Name;
use proptest_derive::Arbitrary;
#[derive(Debug)]
pub struct Module {
pub definitions: Vec<Definition>,
}
#[derive(Debug)]
pub struct Definition {
pub location: Location,
pub export: ExportClass,
pub type_restrictions: TypeRestrictions,
pub definition: Def,
}
impl Located for Definition {
fn location(&self) -> Location {
self.location.clone()
}
}
#[derive(Debug)]
pub enum Def {
Enumeration(EnumerationDef),
Structure(StructureDef),
Function(FunctionDef),
Value(ValueDef),
}
impl Located for Def {
fn location(&self) -> Location {
match self {
Def::Enumeration(def) => def.location.clone(),
Def::Structure(def) => def.location.clone(),
Def::Function(def) => def.location.clone(),
Def::Value(def) => def.location.clone(),
}
}
}
#[derive(Debug)]
pub struct EnumerationDef {
pub name: Name,
pub location: Location,
pub variants: Vec<EnumerationVariant>,
}
#[derive(Debug)]
pub struct EnumerationVariant {
pub location: Location,
pub name: Name,
pub argument: Option<Type>,
}
#[derive(Debug)]
pub struct StructureDef {
pub name: Name,
pub location: Location,
pub fields: Vec<StructureField>,
}
#[derive(Debug)]
pub struct StructureField {
pub location: Location,
pub export: ExportClass,
pub name: Name,
pub field_type: Option<Type>,
}
#[derive(Debug)]
pub struct FunctionDef {
pub name: Name,
pub location: Location,
pub arguments: Vec<FunctionArg>,
pub return_type: Option<Type>,
pub body: Vec<Statement>,
}
#[derive(Debug)]
pub struct FunctionArg {
pub name: Name,
pub arg_type: Option<Type>,
}
#[derive(Debug)]
pub struct ValueDef {
pub name: Name,
pub location: Location,
pub value: Expression,
}
#[derive(Debug)]
pub enum ExportClass {
Public,
Private,
}
#[derive(Debug)]
pub enum Statement {
Binding(BindingStmt),
Expression(Expression),
}
#[derive(Debug)]
pub struct BindingStmt {
pub location: Location,
pub mutable: bool,
pub variable: Name,
pub value: Expression,
}
#[derive(Debug)]
pub enum Expression {
Value(ConstantValue),
Reference(Name),
EnumerationValue(Name, Name, Option<Box<Expression>>),
StructureValue(Name, Vec<FieldValue>),
Conditional(ConditionalExpr),
Match(MatchExpr),
Call(Box<Expression>, CallKind, Vec<Expression>),
Block(Location, Vec<Statement>),
}
#[derive(Debug)]
pub struct ConditionalExpr {
pub location: Location,
pub test: Box<Expression>,
pub consequent: Box<Expression>,
pub alternative: Option<Box<Expression>>,
}
#[derive(Debug)]
pub struct MatchExpr {
pub location: Location,
pub value: Box<Expression>,
pub cases: Vec<MatchCase>,
}
#[derive(Debug)]
pub struct MatchCase {}
#[derive(Debug)]
pub enum CallKind {
Infix,
Normal,
Postfix,
Prefix,
}
#[derive(Debug)]
pub struct FieldValue {
pub field: Name,
pub value: Expression,
}
#[derive(Debug)]
pub struct TypeRestrictions {
pub restrictions: Vec<TypeRestriction>,
}
impl TypeRestrictions {
pub fn empty() -> Self {
TypeRestrictions {
restrictions: vec![],
}
}
}
#[derive(Debug)]
pub struct TypeRestriction {
pub constructor: Type,
pub arguments: Vec<Type>,
}
#[derive(Debug)]
pub enum Type {
Constructor(Location, Name),
Variable(Location, Name),
Primitive(Location, Name),
Application(Box<Type>, Vec<Type>),
Function(Vec<Type>, Box<Type>),
}
impl Located for Type {
fn location(&self) -> Location {
match self {
Type::Constructor(l, _) => l.clone(),
Type::Variable(l, _) => l.clone(),
Type::Primitive(l, _) => l.clone(),
Type::Application(t1, ts) => {
let mut result = t1.location();
if let Some(last) = ts.last() {
result = result.extend_to(&last.location());
}
result
}
Type::Function(args, ret) => {
if let Some(first) = args.first() {
first.location().extend_to(&ret.location())
} else {
ret.location()
}
}
}
}
}
#[derive(Debug)]
pub enum ConstantValue {
Integer(Location, IntegerWithBase),
Character(Location, char),
String(Location, String),
}
#[derive(Clone, Debug, PartialEq, Eq, Arbitrary)]
pub struct IntegerWithBase {
#[proptest(strategy = "proptest::prop_oneof![ \
proptest::strategy::Just(None), \
proptest::strategy::Just(Some(2)), \
proptest::strategy::Just(Some(8)), \
proptest::strategy::Just(Some(10)), \
proptest::strategy::Just(Some(16)), \
]")]
pub base: Option<u8>,
pub value: u64,
}

View File

@@ -1,17 +1,23 @@
//use codespan_reporting::diagnostic::{Diagnostic, Label};
use crate::syntax::tokens::Token;
use internment::ArcIntern;
use std::ops::Range;
use std::path::PathBuf;
use internment::ArcIntern;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ParserError {
#[error("Lexer error at {file}: {error}")]
LexerError { file: ArcIntern<PathBuf>, error: LexerError },
LexerError {
file: ArcIntern<PathBuf>,
error: LexerError,
},
#[error("Unacceptable end of file at {file} while {place}")]
UnacceptableEof { file: ArcIntern<PathBuf>, place: &'static str },
UnacceptableEof {
file: ArcIntern<PathBuf>,
place: &'static str,
},
#[error("Unexpected token at {file}: expected {expected}, saw {token}")]
UnexpectedToken {
@@ -28,7 +34,10 @@ pub enum ParserError {
ReadError { file: String, error: std::io::Error },
#[error("UTF-8 problem reading file {file}: {error}")]
Utf8Error { file: String, error: std::str::Utf8Error },
Utf8Error {
file: String,
error: std::str::Utf8Error,
},
}
#[derive(Clone, Debug, Error, PartialEq)]

View File

@@ -32,7 +32,10 @@ impl Span for Location {
impl Location {
pub fn new(file: &ArcIntern<PathBuf>, span: Range<usize>) -> Self {
Location { file: file.clone(), span }
Location {
file: file.clone(),
span,
}
}
pub fn extend_to(&self, other: &Location) -> Location {

View File

@@ -57,4 +57,8 @@ impl Name {
pub fn as_printed(&self) -> &str {
self.printable.as_str()
}
pub fn bind_to(&mut self, other: &Name) {
self.identifier = other.identifier;
}
}

View File

@@ -3,6 +3,8 @@ use crate::syntax::tokens::{Lexer, LocatedToken, Token};
use crate::syntax::*;
use internment::ArcIntern;
use std::collections::HashMap;
use std::ops::Range;
use std::path::{Path, PathBuf};
pub struct Parser<'lexer> {
file: ArcIntern<PathBuf>,
@@ -26,10 +28,7 @@ impl<'lexer> Parser<'lexer> {
/// error messages. If you don't care about either, you can use
/// 0 with no loss of functionality. (Obviously, it will be harder
/// to create quality error messages, but you already knew that.)
pub fn new<P: AsRef<Path>>(
file: P,
lexer: Lexer<'lexer>
) -> Parser<'lexer> {
pub fn new<P: AsRef<Path>>(file: P, lexer: Lexer<'lexer>) -> Parser<'lexer> {
Parser {
file: ArcIntern::new(file.as_ref().to_path_buf()),
lexer,
@@ -200,10 +199,12 @@ impl<'lexer> Parser<'lexer> {
let constructor = match maybe_constructor.token {
Token::TypeName(str) => {
Type::Constructor(self.to_location(maybe_constructor.span), str)
let name = Name::new(self.to_location(maybe_constructor.span.clone()), str);
Type::Constructor(self.to_location(maybe_constructor.span), name)
}
Token::PrimitiveTypeName(str) => {
Type::Primitive(self.to_location(maybe_constructor.span), str)
let name = Name::new(self.to_location(maybe_constructor.span.clone()), str);
Type::Primitive(self.to_location(maybe_constructor.span), name)
}
token @ Token::CloseParen | token @ Token::Comma => {
@@ -289,7 +290,7 @@ impl<'lexer> Parser<'lexer> {
.next()?
.ok_or_else(|| self.bad_eof("looking for structure name"))?;
let structure_name = match name.token {
Token::TypeName(str) => str,
Token::TypeName(str) => Name::new(self.to_location(name.span), str),
_ => {
return Err(ParserError::UnexpectedToken {
file: self.file.clone(),
@@ -385,7 +386,7 @@ impl<'lexer> Parser<'lexer> {
.ok_or_else(|| self.bad_eof("parsing field definition"))?;
let name = match maybe_name.token {
Token::ValueName(x) => x,
Token::ValueName(x) => Name::new(self.to_location(maybe_name.span.clone()), x),
_ => {
self.save(maybe_name.clone());
if matches!(export, ExportClass::Private) {
@@ -474,7 +475,7 @@ impl<'lexer> Parser<'lexer> {
.next()?
.ok_or_else(|| self.bad_eof("looking for enumeration name"))?;
let enumeration_name = match name.token {
Token::TypeName(str) => str,
Token::TypeName(str) => Name::new(self.to_location(name.span), str),
_ => {
return Err(ParserError::UnexpectedToken {
file: self.file.clone(),
@@ -531,7 +532,7 @@ impl<'lexer> Parser<'lexer> {
.next()?
.ok_or_else(|| self.bad_eof("looking for enumeration name"))?;
let name = match maybe_name.token {
Token::TypeName(x) => x,
Token::TypeName(x) => Name::new(self.to_location(maybe_name.span.clone()), x),
Token::CloseBrace => {
self.save(maybe_name);
return Ok(None);
@@ -613,7 +614,9 @@ impl<'lexer> Parser<'lexer> {
self.save(next.clone());
match next.token {
Token::ValueName(x) if x == "match" => self.parse_match_expression(),
Token::ValueName(x) if x == "match" => {
Ok(Expression::Match(self.parse_match_expression()?))
}
Token::ValueName(x) if x == "if" => {
Ok(Expression::Conditional(self.parse_if_expression()?))
}
@@ -621,7 +624,64 @@ impl<'lexer> Parser<'lexer> {
}
}
fn parse_match_expression(&mut self) -> Result<Expression, ParserError> {
fn parse_match_expression(&mut self) -> Result<MatchExpr, ParserError> {
let next = self
.next()?
.ok_or_else(|| self.bad_eof("looking for a 'match' to open a pattern match"))?;
if !matches!(next.token, Token::ValueName(ref x) if x == "match") {
return Err(ParserError::UnexpectedToken {
file: self.file.clone(),
span: next.span,
token: next.token,
expected: "an 'match' to start a pattern match",
});
}
let start = self.to_location(next.span);
let value = Box::new(self.parse_arithmetic(0)?);
let next = self
.next()?
.ok_or_else(|| self.bad_eof("looking for an open brace after 'match'"))?;
if !matches!(next.token, Token::OpenBrace) {
return Err(ParserError::UnexpectedToken {
file: self.file.clone(),
span: next.span,
token: next.token,
expected: "an open brace after the match expression",
});
}
let mut cases = vec![];
while let Some(case) = self.parse_match_case()? {
cases.push(case);
}
let next = self
.next()?
.ok_or_else(|| self.bad_eof("looking for an open brace after 'match'"))?;
if !matches!(next.token, Token::CloseBrace) {
return Err(ParserError::UnexpectedToken {
file: self.file.clone(),
span: next.span,
token: next.token,
expected: "a close brace to end a match expression",
});
}
let end = self.to_location(next.span);
let location = start.extend_to(&end);
Ok(MatchExpr {
location,
value,
cases,
})
}
fn parse_match_case(&mut self) -> Result<Option<MatchCase>, ParserError> {
unimplemented!()
}
@@ -668,7 +728,7 @@ impl<'lexer> Parser<'lexer> {
};
Ok(ConditionalExpr {
location,
location: start.extend_to(&location),
test: Box::new(test),
consequent: Box::new(consequent),
alternative,
@@ -1164,8 +1224,14 @@ impl<'lexer> Parser<'lexer> {
self.next()?.ok_or_else(|| self.bad_eof("parsing type"))?;
let constructor = match token {
Token::TypeName(x) => Type::Constructor(self.to_location(span), x),
Token::PrimitiveTypeName(x) => Type::Primitive(self.to_location(span), x),
Token::TypeName(x) => {
let name = Name::new(self.to_location(span.clone()), x);
Type::Constructor(self.to_location(span), name)
}
Token::PrimitiveTypeName(x) => {
let name = Name::new(self.to_location(span.clone()), x);
Type::Primitive(self.to_location(span), name)
}
_ => {
self.save(LocatedToken { token, span });
return self.parse_base_type();
@@ -1186,9 +1252,18 @@ impl<'lexer> Parser<'lexer> {
self.next()?.ok_or_else(|| self.bad_eof("parsing type"))?;
match token {
Token::TypeName(x) => Ok(Type::Constructor(self.to_location(span), x)),
Token::PrimitiveTypeName(x) => Ok(Type::Primitive(self.to_location(span), x)),
Token::ValueName(x) => Ok(Type::Variable(self.to_location(span), x)),
Token::TypeName(x) => {
let name = Name::new(self.to_location(span.clone()), x);
Ok(Type::Constructor(self.to_location(span), name))
}
Token::PrimitiveTypeName(x) => {
let name = Name::new(self.to_location(span.clone()), x);
Ok(Type::Primitive(self.to_location(span), name))
}
Token::ValueName(x) => {
let name = Name::new(self.to_location(span.clone()), x);
Ok(Type::Variable(self.to_location(span), name))
}
Token::OpenParen => {
let t = self.parse_type()?;
let closer = self

View File

@@ -72,50 +72,47 @@ fn types() {
assert!(matches!(
parse_type("Cons"),
Ok(Type::Application(cons, empty)) if
matches!(cons.as_ref(), Type::Constructor(_, c) if c == "Cons") &&
matches!(cons.as_ref(), Type::Constructor(_, c) if c.as_printed() == "Cons") &&
empty.is_empty()
));
assert!(matches!(
parse_type("cons"),
Ok(Type::Variable(_, c)) if c == "cons"
Ok(Type::Variable(_, c)) if c.as_printed() == "cons"
));
assert!(matches!(
parse_type("Cons a b"),
Ok(Type::Application(a, b))
if matches!(a.as_ref(), Type::Constructor(_, c) if c == "Cons") &&
if matches!(a.as_ref(), Type::Constructor(_, c) if c.as_printed() == "Cons") &&
matches!(b.as_slice(), [Type::Variable(_, b1), Type::Variable(_, b2)]
if b1 == "a" && b2 == "b")
if b1.as_printed() == "a" && b2.as_printed() == "b")
));
assert!(matches!(
parse_type("a -> z"),
Ok(Type::Function(a, z))
if matches!(a.as_slice(), [Type::Variable(_, a1)] if a1 == "a") &&
matches!(z.as_ref(), Type::Variable(_, z1) if z1 == "z")
if matches!(a.as_slice(), [Type::Variable(_, a1)] if a1.as_printed() == "a") &&
matches!(z.as_ref(), Type::Variable(_, z1) if z1.as_printed() == "z")
));
println!("-------------");
println!("{:?}", parse_type("(a -> z)"));
println!("-------------");
assert!(matches!(
parse_type("(a -> z)"),
Ok(Type::Function(a, z))
if matches!(a.as_slice(), [Type::Variable(_, a1)] if a1 == "a") &&
matches!(z.as_ref(), Type::Variable(_, z1) if z1 == "z")
if matches!(a.as_slice(), [Type::Variable(_, a1)] if a1.as_printed() == "a") &&
matches!(z.as_ref(), Type::Variable(_, z1) if z1.as_printed() == "z")
));
assert!(matches!(
parse_type("a b -> z"),
Ok(Type::Function(a, z))
if matches!(a.as_slice(), [Type::Variable(_, a1), Type::Variable(_, b1)]
if a1 == "a" && b1 == "b") &&
matches!(z.as_ref(), Type::Variable(_, z1) if z1 == "z")
if a1.as_printed() == "a" && b1.as_printed() == "b") &&
matches!(z.as_ref(), Type::Variable(_, z1) if z1.as_printed() == "z")
));
assert!(matches!(
parse_type("Cons a b -> z"),
Ok(Type::Function(a, z))
if matches!(a.as_slice(), [Type::Application(cons, appargs)]
if matches!(cons.as_ref(), Type::Constructor(_, c) if c == "Cons") &&
if matches!(cons.as_ref(), Type::Constructor(_, c) if c.as_printed() == "Cons") &&
matches!(appargs.as_slice(), [Type::Variable(_, b1), Type::Variable(_, b2)]
if b1 == "a" && b2 == "b")) &&
matches!(z.as_ref(), Type::Variable(_, z1) if z1 == "z")
if b1.as_printed() == "a" && b2.as_printed() == "b")) &&
matches!(z.as_ref(), Type::Variable(_, z1) if z1.as_printed() == "z")
));
}
@@ -138,10 +135,10 @@ fn type_restrictions() {
matches!(&restrictions[0], TypeRestriction {
constructor,
arguments,
} if matches!(constructor, Type::Constructor(_, x) if x == "Cons") &&
} if matches!(constructor, Type::Constructor(_, x) if x.as_printed() == "Cons") &&
arguments.len() == 2 &&
matches!(&arguments[0], Type::Variable(_, x) if x == "a") &&
matches!(&arguments[1], Type::Variable(_, x) if x == "b"))));
matches!(&arguments[0], Type::Variable(_, x) if x.as_printed() == "a") &&
matches!(&arguments[1], Type::Variable(_, x) if x.as_printed() == "b"))));
assert!(matches!(
parse_tr("restrict(Cons a b,)"),
@@ -149,10 +146,10 @@ fn type_restrictions() {
matches!(&restrictions[0], TypeRestriction {
constructor,
arguments,
} if matches!(constructor, Type::Constructor(_, x) if x == "Cons") &&
} if matches!(constructor, Type::Constructor(_, x) if x.as_printed() == "Cons") &&
arguments.len() == 2 &&
matches!(&arguments[0], Type::Variable(_, x) if x == "a") &&
matches!(&arguments[1], Type::Variable(_, x) if x == "b"))));
matches!(&arguments[0], Type::Variable(_, x) if x.as_printed() == "a") &&
matches!(&arguments[1], Type::Variable(_, x) if x.as_printed() == "b"))));
assert!(matches!(parse_tr("restrict(,Cons a b,)"), Err(_)));
@@ -162,16 +159,16 @@ fn type_restrictions() {
matches!(&restrictions[0], TypeRestriction {
constructor,
arguments,
} if matches!(constructor, Type::Constructor(_, x) if x == "Cons") &&
} if matches!(constructor, Type::Constructor(_, x) if x.as_printed() == "Cons") &&
arguments.len() == 2 &&
matches!(&arguments[0], Type::Variable(_, x) if x == "a") &&
matches!(&arguments[1], Type::Variable(_, x) if x == "b")) &&
matches!(&arguments[0], Type::Variable(_, x) if x.as_printed() == "a") &&
matches!(&arguments[1], Type::Variable(_, x) if x.as_printed() == "b")) &&
matches!(&restrictions[1], TypeRestriction {
constructor,
arguments,
} if matches!(constructor, Type::Constructor(_, x) if x == "Monad") &&
} if matches!(constructor, Type::Constructor(_, x) if x.as_printed() == "Monad") &&
arguments.len() == 1 &&
matches!(&arguments[0], Type::Variable(_, x) if x == "m"))));
matches!(&arguments[0], Type::Variable(_, x) if x.as_printed() == "m"))));
assert!(matches!(
parse_tr("restrict(Cons a b, Monad m,)"),
@@ -179,16 +176,16 @@ fn type_restrictions() {
matches!(&restrictions[0], TypeRestriction {
constructor,
arguments,
} if matches!(constructor, Type::Constructor(_, x) if x == "Cons") &&
} if matches!(constructor, Type::Constructor(_, x) if x.as_printed() == "Cons") &&
arguments.len() == 2 &&
matches!(&arguments[0], Type::Variable(_, x) if x == "a") &&
matches!(&arguments[1], Type::Variable(_, x) if x == "b")) &&
matches!(&arguments[0], Type::Variable(_, x) if x.as_printed() == "a") &&
matches!(&arguments[1], Type::Variable(_, x) if x.as_printed() == "b")) &&
matches!(&restrictions[1], TypeRestriction {
constructor,
arguments,
} if matches!(constructor, Type::Constructor(_, x) if x == "Monad") &&
} if matches!(constructor, Type::Constructor(_, x) if x.as_printed() == "Monad") &&
arguments.len() == 1 &&
matches!(&arguments[0], Type::Variable(_, x) if x == "m"))));
matches!(&arguments[0], Type::Variable(_, x) if x.as_printed() == "m"))));
}
#[test]
@@ -203,46 +200,46 @@ fn field_definition() {
assert!(matches!(
parse_fd("foo,"),
Ok(Some(StructureField{ name, export: ExportClass::Private, field_type: None, .. }))
if name == "foo"
if name.as_printed() == "foo"
));
assert!(matches!(
parse_fd("foo}"),
Ok(Some(StructureField{ name, export: ExportClass::Private, field_type: None, .. }))
if name == "foo"
if name.as_printed() == "foo"
));
assert!(matches!(
parse_fd("foo: Word8,"),
Ok(Some(StructureField{ name, field_type, .. }))
if name == "foo" &&
if name.as_printed() == "foo" &&
matches!(&field_type, Some(Type::Application(c, args))
if matches!(c.as_ref(), Type::Constructor(_, c) if c == "Word8") &&
if matches!(c.as_ref(), Type::Constructor(_, c) if c.as_printed() == "Word8") &&
args.is_empty())));
assert!(matches!(
parse_fd("foo: Cons a b,"),
Ok(Some(StructureField{ name, field_type, .. }))
if name == "foo" &&
if name.as_printed() == "foo" &&
matches!(&field_type, Some(Type::Application(c, args))
if matches!(c.as_ref(), Type::Constructor(_, c) if c == "Cons") &&
if matches!(c.as_ref(), Type::Constructor(_, c) if c.as_printed() == "Cons") &&
matches!(&args.as_slice(), &[Type::Variable(_, v1), Type::Variable(_, v2)]
if v1 == "a" && v2 == "b"))));
if v1.as_printed() == "a" && v2.as_printed() == "b"))));
assert!(matches!(
parse_fd("foo: a -> b,"),
Ok(Some(StructureField{ name, field_type, .. }))
if name == "foo" &&
if name.as_printed() == "foo" &&
matches!(&field_type, Some(Type::Function(args, ret))
if matches!(&args.as_slice(), &[Type::Variable(_, a)] if a == "a") &&
matches!(ret.as_ref(), Type::Variable(_, b) if b == "b"))));
if matches!(&args.as_slice(), &[Type::Variable(_, a)] if a.as_printed() == "a") &&
matches!(ret.as_ref(), Type::Variable(_, b) if b.as_printed() == "b"))));
assert!(matches!(
parse_fd("export foo: a -> b,"),
Ok(Some(StructureField{ name, export: ExportClass::Public, field_type, .. }))
if name == "foo" &&
if name.as_printed() == "foo" &&
matches!(&field_type, Some(Type::Function(args, ret))
if matches!(&args.as_slice(), &[Type::Variable(_, a)] if a == "a") &&
matches!(ret.as_ref(), Type::Variable(_, b) if b == "b"))));
if matches!(&args.as_slice(), &[Type::Variable(_, a)] if a.as_printed() == "a") &&
matches!(ret.as_ref(), Type::Variable(_, b) if b.as_printed() == "b"))));
}
#[test]
@@ -260,65 +257,65 @@ fn structures() {
assert!(matches!(
parse_st("structure Foo {}"),
Ok(StructureDef { name, fields, .. })
if name == "Foo" && fields.is_empty()));
if name.as_printed() == "Foo" && fields.is_empty()));
assert!(matches!(
parse_st("structure Foo { bar }"),
Ok(StructureDef { name, fields, .. })
if name == "Foo" &&
if name.as_printed() == "Foo" &&
matches!(fields.as_slice(), &[StructureField { ref name, ref field_type, .. }]
if name == "bar" && matches!(field_type, None))));
if name.as_printed() == "bar" && matches!(field_type, None))));
assert!(matches!(
parse_st("structure Foo { bar: Word8 }"),
Ok(StructureDef { name, fields, .. })
if name == "Foo" &&
if name.as_printed() == "Foo" &&
matches!(fields.as_slice(), &[StructureField { ref name, ref field_type, .. }]
if name == "bar" &&
if name.as_printed() == "bar" &&
matches!(field_type, Some(Type::Application(c, args))
if matches!(c.as_ref(), Type::Constructor(_, c) if c == "Word8") &&
if matches!(c.as_ref(), Type::Constructor(_, c) if c.as_printed() == "Word8") &&
args.is_empty()))));
assert!(matches!(
parse_st("structure Foo { bar: Word8, goo }"),
Ok(StructureDef { name, fields, .. })
if name == "Foo" &&
if name.as_printed() == "Foo" &&
matches!(fields.as_slice(),
&[StructureField { ref name, ref field_type, .. },
StructureField { name: ref name2, field_type: None, .. }]
if name == "bar" &&
name2 == "goo" &&
if name.as_printed() == "bar" &&
name2.as_printed() == "goo" &&
matches!(field_type, Some(Type::Application(c, args))
if matches!(c.as_ref(), Type::Constructor(_, c) if c == "Word8") &&
if matches!(c.as_ref(), Type::Constructor(_, c) if c.as_printed() == "Word8") &&
args.is_empty()))));
assert!(matches!(
parse_st("structure Foo { bar: b c -> a, goo }"),
Ok(StructureDef { name, fields, .. })
if name == "Foo" &&
if name.as_printed() == "Foo" &&
matches!(fields.as_slice(),
&[StructureField { ref name, ref field_type, .. },
StructureField { name: ref name2, field_type: None, .. }]
if name == "bar" &&
name2 == "goo" &&
if name.as_printed() == "bar" &&
name2.as_printed() == "goo" &&
matches!(field_type, Some(Type::Function(args, ret))
if matches!(&args.as_slice(), &[Type::Variable(_, b), Type::Variable(_, c)]
if b == "b" && c == "c") &&
matches!(ret.as_ref(), Type::Variable(_, a) if a == "a")))));
if b.as_printed() == "b" && c.as_printed() == "c") &&
matches!(ret.as_ref(), Type::Variable(_, a) if a.as_printed() == "a")))));
assert!(matches!(
parse_st("structure Foo { bar: b c -> a, goo, }"),
Ok(StructureDef { name, fields, .. })
if name == "Foo" &&
if name.as_printed() == "Foo" &&
matches!(fields.as_slice(),
&[StructureField { ref name, ref field_type, .. },
StructureField { name: ref name2, field_type: None, .. }]
if name == "bar" &&
name2 == "goo" &&
if name.as_printed() == "bar" &&
name2.as_printed() == "goo" &&
matches!(field_type, Some(Type::Function(args, ret))
if matches!(&args.as_slice(), &[Type::Variable(_, b), Type::Variable(_, c)]
if b == "b" && c == "c") &&
matches!(ret.as_ref(), Type::Variable(_, a) if a == "a")))));
if b.as_printed() == "b" && c.as_printed() == "c") &&
matches!(ret.as_ref(), Type::Variable(_, a) if a.as_printed() == "a")))));
}
#[test]
@@ -339,43 +336,43 @@ fn enum_variant() {
assert!(matches!(
parse_ev("Cons,"),
Ok(Some(EnumerationVariant { name, argument, .. }))
if name == "Cons" && argument.is_none()));
if name.as_printed() == "Cons" && argument.is_none()));
assert!(matches!(
parse_ev("Cons }"),
Ok(Some(EnumerationVariant { name, argument, .. }))
if name == "Cons" && argument.is_none()));
if name.as_printed() == "Cons" && argument.is_none()));
assert!(matches!(
parse_ev("Cons, }"),
Ok(Some(EnumerationVariant { name, argument, .. }))
if name == "Cons" && argument.is_none()));
if name.as_printed() == "Cons" && argument.is_none()));
assert!(matches!(
parse_ev("Cons(Pair a),"),
Ok(Some(EnumerationVariant { name, ref argument, .. }))
if name == "Cons" &&
if name.as_printed() == "Cons" &&
matches!(argument, Some(Type::Application(typef, args))
if matches!(typef.as_ref(), Type::Constructor(_, name)
if name == "Pair") &&
if name.as_printed() == "Pair") &&
matches!(&args.as_slice(), &[Type::Variable(_, argname)]
if argname == "a"))));
if argname.as_printed() == "a"))));
assert!(matches!(
parse_ev("Cons(Pair a) }"),
Ok(Some(EnumerationVariant { name, ref argument, .. }))
if name == "Cons" &&
if name.as_printed() == "Cons" &&
matches!(argument, Some(Type::Application(typef, args))
if matches!(typef.as_ref(), Type::Constructor(_, name)
if name == "Pair") &&
if name.as_printed() == "Pair") &&
matches!(&args.as_slice(), &[Type::Variable(_, argname)]
if argname == "a"))));
if argname.as_printed() == "a"))));
assert!(matches!(
parse_ev("Cons(a b -> c) }"),
Ok(Some(EnumerationVariant { name, ref argument, .. }))
if name == "Cons" &&
if name.as_printed() == "Cons" &&
matches!(argument, Some(Type::Function(args, ret))
if matches!(&args.as_slice(), &[Type::Variable(_, a), Type::Variable(_, b)]
if a == "a" && b == "b") &&
matches!(ret.as_ref(), Type::Variable(_, c) if c == "c"))));
if a.as_printed() == "a" && b.as_printed() == "b") &&
matches!(ret.as_ref(), Type::Variable(_, c) if c.as_printed() == "c"))));
}
#[test]
@@ -393,25 +390,25 @@ fn enumerations() {
assert!(matches!(
parse_en("enumeration Empty { }"),
Ok(EnumerationDef { name, variants, .. })
if name == "Empty" && variants.is_empty()));
if name.as_printed() == "Empty" && variants.is_empty()));
assert!(matches!(
parse_en("enumeration Alternates { A, B }"),
Ok(EnumerationDef { name, variants, .. })
if name == "Alternates" &&
if name.as_printed() == "Alternates" &&
matches!(&variants.as_slice(), &[
EnumerationVariant { name: name1, argument: arg1, ..},
EnumerationVariant { name: name2, argument: arg2, ..},
] if name1 == "A" && arg1.is_none() &&
name2 == "B" && arg2.is_none())));
] if name1.as_printed() == "A" && arg1.is_none() &&
name2.as_printed() == "B" && arg2.is_none())));
assert!(matches!(
parse_en("enumeration Alternates { A, B, }"),
Ok(EnumerationDef { name, variants, .. })
if name == "Alternates" &&
if name.as_printed() == "Alternates" &&
matches!(&variants.as_slice(), &[
EnumerationVariant { name: name1, argument: arg1, ..},
EnumerationVariant { name: name2, argument: arg2, ..},
] if name1 == "A" && arg1.is_none() &&
name2 == "B" && arg2.is_none())));
] if name1.as_printed() == "A" && arg1.is_none() &&
name2.as_printed() == "B" && arg2.is_none())));
}
#[test]

View File

@@ -89,7 +89,7 @@ impl fmt::Display for Token {
pub enum Lexer<'a> {
Working(LexerState<'a>),
Errored(LexerError),
Done(usize),
Done,
}
struct LexerState<'a> {
@@ -120,7 +120,7 @@ impl<'a> Iterator for Lexer<'a> {
fn next(&mut self) -> Option<Self::Item> {
match self {
Lexer::Done(_) => None,
Lexer::Done => None,
Lexer::Errored(e) => Some(Err(e.clone())),
Lexer::Working(state) => match state.next_token() {
Err(e) => {
@@ -130,7 +130,7 @@ impl<'a> Iterator for Lexer<'a> {
}
Ok(None) => {
*self = Lexer::Done(state.stream.offset());
*self = Lexer::Done;
None
}

48
src/syntax/universe.rs Normal file
View File

@@ -0,0 +1,48 @@
use crate::syntax::ast::*;
use crate::syntax::error::ParserError;
use crate::syntax::parse::Parser;
use crate::syntax::tokens::Lexer;
use memmap2::Mmap;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
pub struct Universe {
pub files: HashMap<PathBuf, Mmap>,
pub modules: HashMap<PathBuf, Module>,
}
impl Default for Universe {
fn default() -> Self {
Universe {
files: HashMap::new(),
modules: HashMap::new(),
}
}
}
impl Universe {
pub fn add_file<P: AsRef<Path>>(&mut self, file: P) -> Result<(), ParserError> {
let filename = file.as_ref().to_string_lossy().into_owned();
let file_handle = std::fs::File::open(&file).map_err(|e| ParserError::OpenError {
file: filename.clone(),
error: e,
})?;
let contents = unsafe { Mmap::map(&file_handle) }.map_err(|e| ParserError::ReadError {
file: filename.clone(),
error: e,
})?;
let string_contents =
std::str::from_utf8(&contents).map_err(|e| ParserError::Utf8Error {
file: filename.clone(),
error: e,
})?;
let lexer = Lexer::from(string_contents);
let mut parser = Parser::new(&file, lexer);
let module = parser.parse_module()?;
self.modules.insert(file.as_ref().to_path_buf(), module);
Ok(())
}
}