Most base expressions work.

This commit is contained in:
2025-09-26 09:24:56 -07:00
parent e9fb4fcd0f
commit 4362d82034
6 changed files with 1449 additions and 373 deletions

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

@@ -0,0 +1,48 @@
use codespan_reporting::diagnostic::Label;
use std::cmp::{max, min};
use std::ops::Range;
pub trait Located {
fn location(&self) -> Location;
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Location {
file_id: usize,
span: Range<usize>,
}
impl Location {
pub fn new(file_id: usize, span: Range<usize>) -> Self {
Location { file_id, span }
}
pub fn extend_to(&self, other: &Location) -> Location {
assert_eq!(self.file_id, other.file_id);
Location {
file_id: self.file_id,
span: min(self.span.start, other.span.start)..max(self.span.end, other.span.end),
}
}
pub fn merge_span(mut self, span: Range<usize>) -> Location {
self.span = min(self.span.start, span.start)..max(self.span.end, span.end);
self
}
pub fn file_id(&self) -> usize {
self.file_id
}
pub fn span(&self) -> Range<usize> {
self.span.clone()
}
pub fn primary_label(&self) -> Label<usize> {
Label::primary(self.file_id, self.span.clone())
}
pub fn secondary_label(&self) -> Label<usize> {
Label::secondary(self.file_id, self.span.clone())
}
}

60
src/syntax/name.rs Normal file
View File

@@ -0,0 +1,60 @@
use crate::syntax::{Located, Location};
use std::cmp;
use std::fmt;
use std::hash;
use std::sync::atomic::{AtomicU64, Ordering};
static IDENTIFIER_COUNTER: AtomicU64 = AtomicU64::new(0);
#[derive(Debug)]
pub struct Name {
printable: String,
identifier: u64,
location: Option<Location>,
}
impl cmp::PartialEq for Name {
fn eq(&self, other: &Self) -> bool {
self.identifier == other.identifier
}
}
impl cmp::Eq for Name {}
impl hash::Hash for Name {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.identifier.hash(state);
}
}
impl fmt::Display for Name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.printable, self.identifier)
}
}
impl Name {
pub fn new<S: ToString>(location: Location, s: S) -> Name {
let my_id = IDENTIFIER_COUNTER.fetch_add(1, Ordering::SeqCst);
Name {
printable: s.to_string(),
identifier: my_id,
location: Some(location),
}
}
pub fn gensym(base: &'static str) -> Name {
let formatted = format!("<{base}>");
let my_id = IDENTIFIER_COUNTER.fetch_add(1, Ordering::SeqCst);
Name {
printable: formatted,
identifier: my_id,
location: None,
}
}
pub fn as_printed(&self) -> &str {
self.printable.as_str()
}
}

View File

@@ -46,9 +46,651 @@ impl<'a> Parser<'a> {
}
fn to_location(&self, span: Range<usize>) -> Location {
Location {
Location::new(self.file_id, span)
}
pub fn parse_module(&mut self) -> Result<Module, ParserError> {
let mut definitions = vec![];
loop {
let next_token = self.next()?;
if next_token.is_none() {
return Ok(Module { definitions });
}
definitions.push(self.parse_definition()?);
}
}
pub fn parse_definition(&mut self) -> Result<Definition, ParserError> {
let (export, start) = self.parse_export_class()?;
let type_restrictions = self.parse_type_restrictions()?;
let definition = self.parse_def()?;
let location = definition.location().merge_span(start);
Ok(Definition {
location,
export,
type_restrictions,
definition,
})
}
fn parse_export_class(&mut self) -> Result<(ExportClass, Range<usize>), ParserError> {
let maybe_export = self
.next()?
.ok_or_else(|| self.bad_eof("looking for possible export"))?;
if matches!(maybe_export.token, Token::ValueName(ref x) if x == "export") {
Ok((ExportClass::Public, maybe_export.span))
} else {
let start = maybe_export.span.clone();
self.save(maybe_export);
Ok((ExportClass::Private, start))
}
}
pub fn parse_type_restrictions(&mut self) -> Result<TypeRestrictions, ParserError> {
let Some(maybe_restrict) = self.next()? else {
return Ok(TypeRestrictions::empty());
};
if !matches!(maybe_restrict.token, Token::ValueName(ref x) if x == "restrict") {
self.save(maybe_restrict);
return Ok(TypeRestrictions::empty());
}
let maybe_paren = self
.next()?
.ok_or_else(|| self.bad_eof("Looking for open paren after restrict"))?;
if !matches!(maybe_paren.token, Token::OpenParen) {
return Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: maybe_paren.span,
token: maybe_paren.token,
expected: "open parenthesis, following the restrict keyword",
});
}
let mut restrictions = vec![];
while let Some(type_restriction) = self.parse_type_restriction()? {
restrictions.push(type_restriction);
}
let maybe_paren = self
.next()?
.ok_or_else(|| self.bad_eof("Looking for open paren after restrict"))?;
if !matches!(maybe_paren.token, Token::CloseParen) {
return Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: maybe_paren.span,
token: maybe_paren.token,
expected: "close parenthesis following type restrictions",
});
}
Ok(TypeRestrictions { restrictions })
}
fn parse_type_restriction(&mut self) -> Result<Option<TypeRestriction>, ParserError> {
let maybe_constructor = self
.next()?
.ok_or_else(|| self.bad_eof("Looking for constructor for type restriction"))?;
let constructor = match maybe_constructor.token {
Token::TypeName(str) => {
Type::Constructor(self.to_location(maybe_constructor.span), str)
}
Token::PrimitiveTypeName(str) => {
Type::Primitive(self.to_location(maybe_constructor.span), str)
}
token @ Token::CloseParen | token @ Token::Comma => {
self.save(LocatedToken {
token,
span: maybe_constructor.span,
});
return Ok(None);
}
weird => {
return Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: maybe_constructor.span,
token: weird,
expected: "Constructor name, comma, or close parenthesis in type restriction",
});
}
};
let mut arguments = vec![];
while let Ok(t) = self.parse_base_type() {
arguments.push(t);
}
let restriction = TypeRestriction {
constructor,
arguments,
};
let Some(maybe_comma) = self.next()? else {
return Ok(Some(restriction));
};
match maybe_comma.token {
Token::Comma => {}
_ => self.save(maybe_comma),
}
Ok(Some(restriction))
}
fn parse_def(&mut self) -> Result<Def, ParserError> {
let next = self
.next()?
.ok_or_else(|| self.bad_eof("looking for definition body"))?;
if let Ok(structure) = self.parse_structure() {
return Ok(Def::Structure(structure));
}
if let Ok(enumeration) = self.parse_enumeration() {
return Ok(Def::Enumeration(enumeration));
}
if let Ok(fun_or_val) = self.parse_function_or_value() {
return Ok(fun_or_val);
}
Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span,
span: next.span,
token: next.token,
expected: "'structure', 'enumeration', or a value identifier",
})
}
pub fn parse_structure(&mut self) -> Result<StructureDef, ParserError> {
let structure_token = self
.next()?
.ok_or_else(|| self.bad_eof("looking for definition"))?;
if !matches!(structure_token.token, Token::ValueName(ref s) if s == "structure") {
return Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: structure_token.span,
token: structure_token.token,
expected: "the 'structure' keyword",
});
}
let name = self
.next()?
.ok_or_else(|| self.bad_eof("looking for structure name"))?;
let structure_name = match name.token {
Token::TypeName(str) => str,
_ => {
return Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: name.span,
token: name.token,
expected: "a structure name",
});
}
};
let brace = self
.next()?
.ok_or_else(|| self.bad_eof("the open brace after a structure name"))?;
if !matches!(brace.token, Token::OpenBrace) {
return Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: brace.span,
token: brace.token,
expected: "the brace after a structure name",
});
}
let mut fields = vec![];
while let Some(field_definition) = self.parse_field_definition()? {
fields.push(field_definition);
}
let brace = self.next()?.ok_or_else(|| {
self.bad_eof("the close brace after at the end of a structure definition")
})?;
if !matches!(brace.token, Token::CloseBrace) {
return Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: brace.span,
token: brace.token,
expected: "the brace at the end of a structure definition",
});
}
let location = self
.to_location(structure_token.span)
.extend_to(&self.to_location(brace.span));
Ok(StructureDef {
name: structure_name,
location,
fields,
})
}
pub fn parse_field_value(&mut self) -> Result<Option<FieldValue>, ParserError> {
let maybe_name = self
.next()?
.ok_or_else(|| self.bad_eof("parsing field definition"))?;
let field = match maybe_name.token {
Token::ValueName(x) => Name::new(self.to_location(maybe_name.span), x),
_ => {
self.save(maybe_name.clone());
return Ok(None);
}
};
let maybe_colon = self.next()?.ok_or_else(|| {
self.bad_eof("looking for colon, comma, or close brace after field name")
})?;
if !matches!(maybe_colon.token, Token::Colon) {
return Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: maybe_colon.span,
token: maybe_colon.token,
expected: "colon after field name in constructor",
});
}
let value = self.parse_expression()?;
let end_token = self.next()?.ok_or_else(|| {
self.bad_eof("looking for comma or close brace after field definition")
})?;
if !matches!(end_token.token, Token::Comma) {
self.save(end_token);
}
Ok(Some(FieldValue { field, value }))
}
pub fn parse_field_definition(&mut self) -> Result<Option<StructureField>, ParserError> {
let (export, start) = self.parse_export_class()?;
let maybe_name = self
.next()?
.ok_or_else(|| self.bad_eof("parsing field definition"))?;
let name = match maybe_name.token {
Token::ValueName(x) => x,
_ => {
self.save(maybe_name.clone());
if matches!(export, ExportClass::Private) {
return Ok(None);
} else {
return Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: maybe_name.span,
token: maybe_name.token,
expected: "a field name",
});
}
}
};
let start_location = self.to_location(start);
let maybe_colon = self.next()?.ok_or_else(|| {
self.bad_eof("looking for colon, comma, or close brace after field name")
})?;
let field_type = match maybe_colon.token {
Token::Comma | Token::CloseBrace => {
self.save(maybe_colon);
None
}
Token::Colon => Some(self.parse_type()?),
_ => {
return Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: maybe_colon.span,
token: maybe_colon.token,
expected: "colon, comma, or close brace after field name",
});
}
};
let end_token = self.next()?.ok_or_else(|| {
self.bad_eof("looking for comma or close brace after field definition")
})?;
let maybe_end_location = match end_token.token {
Token::Comma => Some(self.to_location(end_token.span)),
Token::CloseBrace => {
self.save(end_token);
None
}
_ => {
return Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: end_token.span,
token: end_token.token,
expected: "looking for comma or close brace after field definition",
});
}
};
let end_location = maybe_end_location
.or_else(|| field_type.as_ref().map(|x| x.location()))
.unwrap_or_else(|| self.to_location(maybe_name.span));
let location = start_location.extend_to(&end_location);
Ok(Some(StructureField {
location,
export,
name,
field_type,
}))
}
pub fn parse_enumeration(&mut self) -> Result<EnumerationDef, ParserError> {
let enumeration_token = self
.next()?
.ok_or_else(|| self.bad_eof("looking for definition"))?;
if !matches!(enumeration_token.token, Token::ValueName(ref e) if e == "enumeration") {
return Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: enumeration_token.span,
token: enumeration_token.token,
expected: "the 'enumeration' keyword",
});
}
let name = self
.next()?
.ok_or_else(|| self.bad_eof("looking for enumeration name"))?;
let enumeration_name = match name.token {
Token::TypeName(str) => str,
_ => {
return Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: name.span,
token: name.token,
expected: "an enumeration name",
});
}
};
let brace = self
.next()?
.ok_or_else(|| self.bad_eof("the open brace after an enumeration name"))?;
if !matches!(brace.token, Token::OpenBrace) {
return Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: brace.span,
token: brace.token,
expected: "the brace after an enumeration name",
});
}
let mut variants = vec![];
while let Some(variant_definition) = self.parse_enum_variant()? {
variants.push(variant_definition);
}
let brace = self.next()?.ok_or_else(|| {
self.bad_eof("the close brace after at the end of an enumeration definition")
})?;
if !matches!(brace.token, Token::CloseBrace) {
return Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: brace.span,
token: brace.token,
expected: "the brace at the end of an enumeration definition",
});
}
let location = self
.to_location(enumeration_token.span)
.extend_to(&self.to_location(brace.span));
Ok(EnumerationDef {
name: enumeration_name,
location,
variants,
})
}
pub fn parse_enum_variant(&mut self) -> Result<Option<EnumerationVariant>, ParserError> {
let maybe_name = self
.next()?
.ok_or_else(|| self.bad_eof("looking for enumeration name"))?;
let name = match maybe_name.token {
Token::TypeName(x) => x,
Token::CloseBrace => {
self.save(maybe_name);
return Ok(None);
}
_ => {
self.save(maybe_name.clone());
return Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: maybe_name.span,
token: maybe_name.token,
expected: "variant name (identifier starting with a capital)",
});
}
};
let start_location = self.to_location(maybe_name.span);
let maybe_paren = self
.next()?
.ok_or_else(|| self.bad_eof("trying to understand enumeration variant"))?;
let (argument, arg_location) = if matches!(maybe_paren.token, Token::OpenParen) {
let t = self.parse_type()?;
let maybe_close = self
.next()?
.ok_or_else(|| self.bad_eof("trying to parse a enumeration variant's type"))?;
if !matches!(maybe_close.token, Token::CloseParen) {
return Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: maybe_close.span,
token: maybe_close.token,
expected: "close paren to end an enumeration variant's type argument",
});
}
let location = t.location();
(Some(t), location)
} else {
self.save(maybe_paren);
(None, start_location.clone())
};
let ender = self.next()?.ok_or_else(|| {
self.bad_eof("looking for comma or close brace after enumeration variant")
})?;
let end_location = match ender.token {
Token::Comma => self.to_location(ender.span),
Token::CloseBrace => {
self.save(ender);
arg_location
}
_ => {
self.save(ender.clone());
return Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: ender.span,
token: ender.token,
expected: "comma or close brace after enumeration variant",
});
}
};
let location = start_location.extend_to(&end_location);
Ok(Some(EnumerationVariant {
name,
location,
argument,
}))
}
pub fn parse_function_or_value(&mut self) -> Result<Def, ParserError> {
unimplemented!()
}
pub fn parse_expression(&mut self) -> Result<Expression, ParserError> {
self.parse_base_expression()
}
pub fn parse_base_expression(&mut self) -> Result<Expression, ParserError> {
if let Ok(v) = self.parse_constant() {
return Ok(Expression::Value(v));
}
let next = self
.next()?
.ok_or_else(|| self.bad_eof("looking for an expression"))?;
match next.token {
Token::OpenBrace => unimplemented!(),
Token::OpenParen => {
let inner = self.parse_expression()?;
let hopefully_close = self
.next()?
.ok_or_else(|| self.bad_eof("looking for close paren to finish expression"))?;
if matches!(hopefully_close.token, Token::CloseParen) {
Ok(inner)
} else {
self.save(hopefully_close.clone());
Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: hopefully_close.span,
token: hopefully_close.token,
expected: "close paren after expression",
})
}
}
Token::TypeName(n) | Token::PrimitiveTypeName(n) => {
let type_name = Name::new(self.to_location(next.span), n);
let after_type_name = self.next()?.ok_or_else(|| {
self.bad_eof("looking for colon, open brace, or open paren in constructor")
})?;
match after_type_name.token {
Token::OpenBrace => {
let mut fields = vec![];
while let Some(field) = self.parse_field_value()? {
fields.push(field);
}
let closer = self.next()?.ok_or_else(|| {
self.bad_eof("looking for close brace in structure value")
})?;
if !matches!(closer.token, Token::CloseBrace) {
return Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: closer.span,
token: closer.token,
expected: "close brace or comma after field value",
});
}
Ok(Expression::StructureValue(type_name, fields))
}
Token::Colon => {
let second_colon = self.next()?.ok_or_else(|| {
self.bad_eof("looking for second colon in enumeration value")
})?;
if !matches!(second_colon.token, Token::Colon) {
return Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: second_colon.span,
token: second_colon.token,
expected: "second colon in enumeration value",
});
}
let vname = self
.next()?
.ok_or_else(|| self.bad_eof("looking for enumeration value name"))?;
let value_name = match vname.token {
Token::TypeName(s) => {
let loc = self.to_location(vname.span);
Name::new(loc, s)
}
_ => {
return Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: vname.span,
token: vname.token,
expected: "enumeration value name",
});
}
};
let arg = if let Some(maybe_paren) = self.next()? {
let expr = self.parse_expression()?;
let tok = self.next()?.ok_or_else(|| {
self.bad_eof("looking for close paren after enum value argument")
})?;
if !matches!(tok.token, Token::CloseParen) {
return Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: tok.span,
token: tok.token,
expected: "close paren after enum value argument",
});
}
Some(Box::new(expr))
} else {
None
};
Ok(Expression::EnumerationValue(type_name, value_name, arg))
}
_ => Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: after_type_name.span,
token: after_type_name.token,
expected: "colon, open brace, or open paren in constructor",
}),
}
}
Token::ValueName(n) | Token::PrimitiveValueName(n) => {
let location = self.to_location(next.span);
let name = Name::new(location, n);
Ok(Expression::Reference(name))
}
_ => {
self.save(next.clone());
Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: next.span,
token: next.token,
expected: "some base expression or an open brace",
})
}
}
}
@@ -60,12 +702,10 @@ impl<'a> Parser<'a> {
let mut args = Vec::new();
while let Ok(t) = self.parse_type_application() {
println!("got argument type: {t:?}");
args.push(t);
}
let Some(maybe_arrow) = self.next()? else {
println!("no arrow token");
match args.pop() {
None => {
return Err(ParserError::UnacceptableEof {
@@ -86,11 +726,10 @@ impl<'a> Parser<'a> {
};
if maybe_arrow.token == Token::Arrow {
println!("found function arrow");
let right = self.parse_function_type()?;
Ok(Type::Function(args, Box::new(right)))
} else if args.len() == 1 {
println!("found non function arrow token {}", maybe_arrow.token);
self.save(maybe_arrow);
Ok(args.pop().expect("length = 1 works"))
} else {
self.save(maybe_arrow.clone());
@@ -113,7 +752,6 @@ impl<'a> Parser<'a> {
Token::TypeName(x) => Type::Constructor(self.to_location(span), x),
Token::PrimitiveTypeName(x) => Type::Primitive(self.to_location(span), x),
_ => {
println!("saving {token}");
self.save(LocatedToken { token, span });
return self.parse_base_type();
}
@@ -136,6 +774,23 @@ impl<'a> Parser<'a> {
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::OpenParen => {
let t = self.parse_type()?;
let closer = self
.next()?
.ok_or_else(|| self.bad_eof("close paren in type"))?;
if !matches!(closer.token, Token::CloseParen) {
return Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: closer.span,
token: closer.token,
expected: "close parenthesis to finish a type",
});
}
Ok(t)
}
token => {
self.save(LocatedToken {
token: token.clone(),
@@ -153,20 +808,32 @@ impl<'a> Parser<'a> {
}
pub fn parse_constant(&mut self) -> Result<ConstantValue, ParserError> {
let LocatedToken { token, span } = self
let maybe_constant = self
.next()?
.ok_or_else(|| self.bad_eof("looking for a constant"))?;
match token {
Token::Integer(iwb) => Ok(ConstantValue::Integer(self.to_location(span), iwb)),
Token::Character(c) => Ok(ConstantValue::Character(self.to_location(span), c)),
Token::String(s) => Ok(ConstantValue::String(self.to_location(span), s)),
_ => Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span,
token,
expected: "constant value",
}),
match maybe_constant.token {
Token::Integer(iwb) => Ok(ConstantValue::Integer(
self.to_location(maybe_constant.span),
iwb,
)),
Token::Character(c) => Ok(ConstantValue::Character(
self.to_location(maybe_constant.span),
c,
)),
Token::String(s) => Ok(ConstantValue::String(
self.to_location(maybe_constant.span),
s,
)),
_ => {
self.save(maybe_constant.clone());
Err(ParserError::UnexpectedToken {
file_id: self.file_id,
span: maybe_constant.span,
token: maybe_constant.token,
expected: "constant value",
})
}
}
}
}

519
src/syntax/parser_tests.rs Normal file
View File

@@ -0,0 +1,519 @@
use crate::syntax::parse::Parser;
use crate::syntax::tokens::Lexer;
use crate::syntax::*;
#[test]
fn constants() {
let parse_constant = |str| {
let lexer = Lexer::from(str);
let mut result = Parser::new(0, lexer);
result.parse_constant()
};
assert!(matches!(
parse_constant("16"),
Ok(ConstantValue::Integer(
_,
IntegerWithBase {
base: None,
value: 16,
}
))
));
assert!(matches!(
parse_constant("0x10"),
Ok(ConstantValue::Integer(
_,
IntegerWithBase {
base: Some(16),
value: 16,
}
))
));
assert!(matches!(
parse_constant("0o20"),
Ok(ConstantValue::Integer(
_,
IntegerWithBase {
base: Some(8),
value: 16,
}
))
));
assert!(matches!(
parse_constant("0b10000"),
Ok(ConstantValue::Integer(
_,
IntegerWithBase {
base: Some(2),
value: 16,
}
))
));
assert!(
matches!(parse_constant("\"foo\""), Ok(ConstantValue::String(_, x))
if x == "foo")
);
assert!(matches!(
parse_constant("'f'"),
Ok(ConstantValue::Character(_, 'f'))
));
}
#[test]
fn types() {
let parse_type = |str| {
let lexer = Lexer::from(str);
let mut result = Parser::new(0, lexer);
result.parse_type()
};
assert!(matches!(
parse_type("Cons"),
Ok(Type::Application(cons, empty)) if
matches!(cons.as_ref(), Type::Constructor(_, c) if c == "Cons") &&
empty.is_empty()
));
assert!(matches!(
parse_type("cons"),
Ok(Type::Variable(_, c)) if c == "cons"
));
assert!(matches!(
parse_type("Cons a b"),
Ok(Type::Application(a, b))
if matches!(a.as_ref(), Type::Constructor(_, c) if c == "Cons") &&
matches!(b.as_slice(), [Type::Variable(_, b1), Type::Variable(_, b2)]
if b1 == "a" && b2 == "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")
));
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")
));
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")
));
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") &&
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")
));
}
#[test]
fn type_restrictions() {
let parse_tr = |str| {
let lexer = Lexer::from(str);
let mut result = Parser::new(0, lexer);
result.parse_type_restrictions()
};
assert!(matches!(
parse_tr("restrict()"),
Ok(TypeRestrictions{ restrictions }) if restrictions.is_empty()
));
assert!(matches!(
parse_tr("restrict(Cons a b)"),
Ok(TypeRestrictions { restrictions }) if restrictions.len() == 1 &&
matches!(&restrictions[0], TypeRestriction {
constructor,
arguments,
} if matches!(constructor, Type::Constructor(_, x) if x == "Cons") &&
arguments.len() == 2 &&
matches!(&arguments[0], Type::Variable(_, x) if x == "a") &&
matches!(&arguments[1], Type::Variable(_, x) if x == "b"))));
assert!(matches!(
parse_tr("restrict(Cons a b,)"),
Ok(TypeRestrictions { restrictions }) if restrictions.len() == 1 &&
matches!(&restrictions[0], TypeRestriction {
constructor,
arguments,
} if matches!(constructor, Type::Constructor(_, x) if x == "Cons") &&
arguments.len() == 2 &&
matches!(&arguments[0], Type::Variable(_, x) if x == "a") &&
matches!(&arguments[1], Type::Variable(_, x) if x == "b"))));
assert!(matches!(parse_tr("restrict(,Cons a b,)"), Err(_)));
assert!(matches!(
parse_tr("restrict(Cons a b, Monad m)"),
Ok(TypeRestrictions { restrictions }) if restrictions.len() == 2 &&
matches!(&restrictions[0], TypeRestriction {
constructor,
arguments,
} if matches!(constructor, Type::Constructor(_, x) if x == "Cons") &&
arguments.len() == 2 &&
matches!(&arguments[0], Type::Variable(_, x) if x == "a") &&
matches!(&arguments[1], Type::Variable(_, x) if x == "b")) &&
matches!(&restrictions[1], TypeRestriction {
constructor,
arguments,
} if matches!(constructor, Type::Constructor(_, x) if x == "Monad") &&
arguments.len() == 1 &&
matches!(&arguments[0], Type::Variable(_, x) if x == "m"))));
assert!(matches!(
parse_tr("restrict(Cons a b, Monad m,)"),
Ok(TypeRestrictions { restrictions }) if restrictions.len() == 2 &&
matches!(&restrictions[0], TypeRestriction {
constructor,
arguments,
} if matches!(constructor, Type::Constructor(_, x) if x == "Cons") &&
arguments.len() == 2 &&
matches!(&arguments[0], Type::Variable(_, x) if x == "a") &&
matches!(&arguments[1], Type::Variable(_, x) if x == "b")) &&
matches!(&restrictions[1], TypeRestriction {
constructor,
arguments,
} if matches!(constructor, Type::Constructor(_, x) if x == "Monad") &&
arguments.len() == 1 &&
matches!(&arguments[0], Type::Variable(_, x) if x == "m"))));
}
#[test]
fn field_definition() {
let parse_fd = |str| {
let lexer = Lexer::from(str);
let mut result = Parser::new(0, lexer);
result.parse_field_definition()
};
assert!(matches!(parse_fd("foo"), Err(_),));
assert!(matches!(
parse_fd("foo,"),
Ok(Some(StructureField{ name, export: ExportClass::Private, field_type: None, .. }))
if name == "foo"
));
assert!(matches!(
parse_fd("foo}"),
Ok(Some(StructureField{ name, export: ExportClass::Private, field_type: None, .. }))
if name == "foo"
));
assert!(matches!(
parse_fd("foo: Word8,"),
Ok(Some(StructureField{ name, field_type, .. }))
if name == "foo" &&
matches!(&field_type, Some(Type::Application(c, args))
if matches!(c.as_ref(), Type::Constructor(_, c) if c == "Word8") &&
args.is_empty())));
assert!(matches!(
parse_fd("foo: Cons a b,"),
Ok(Some(StructureField{ name, field_type, .. }))
if name == "foo" &&
matches!(&field_type, Some(Type::Application(c, args))
if matches!(c.as_ref(), Type::Constructor(_, c) if c == "Cons") &&
matches!(&args.as_slice(), &[Type::Variable(_, v1), Type::Variable(_, v2)]
if v1 == "a" && v2 == "b"))));
assert!(matches!(
parse_fd("foo: a -> b,"),
Ok(Some(StructureField{ name, field_type, .. }))
if name == "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"))));
assert!(matches!(
parse_fd("export foo: a -> b,"),
Ok(Some(StructureField{ name, export: ExportClass::Public, field_type, .. }))
if name == "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"))));
}
#[test]
fn structures() {
let parse_st = |str| {
let lexer = Lexer::from(str);
let mut result = Parser::new(0, lexer);
result.parse_structure()
};
assert!(matches!(parse_st("structure { }"), Err(_)));
assert!(matches!(parse_st("structure {"), Err(_)));
assert!(matches!(parse_st("structure foo {}"), Err(_)));
assert!(matches!(
parse_st("structure Foo {}"),
Ok(StructureDef { name, fields, .. })
if name == "Foo" && fields.is_empty()));
assert!(matches!(
parse_st("structure Foo { bar }"),
Ok(StructureDef { name, fields, .. })
if name == "Foo" &&
matches!(fields.as_slice(), &[StructureField { ref name, ref field_type, .. }]
if name == "bar" && matches!(field_type, None))));
assert!(matches!(
parse_st("structure Foo { bar: Word8 }"),
Ok(StructureDef { name, fields, .. })
if name == "Foo" &&
matches!(fields.as_slice(), &[StructureField { ref name, ref field_type, .. }]
if name == "bar" &&
matches!(field_type, Some(Type::Application(c, args))
if matches!(c.as_ref(), Type::Constructor(_, c) if c == "Word8") &&
args.is_empty()))));
assert!(matches!(
parse_st("structure Foo { bar: Word8, goo }"),
Ok(StructureDef { name, fields, .. })
if name == "Foo" &&
matches!(fields.as_slice(),
&[StructureField { ref name, ref field_type, .. },
StructureField { name: ref name2, field_type: None, .. }]
if name == "bar" &&
name2 == "goo" &&
matches!(field_type, Some(Type::Application(c, args))
if matches!(c.as_ref(), Type::Constructor(_, c) if c == "Word8") &&
args.is_empty()))));
assert!(matches!(
parse_st("structure Foo { bar: b c -> a, goo }"),
Ok(StructureDef { name, fields, .. })
if name == "Foo" &&
matches!(fields.as_slice(),
&[StructureField { ref name, ref field_type, .. },
StructureField { name: ref name2, field_type: None, .. }]
if name == "bar" &&
name2 == "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")))));
assert!(matches!(
parse_st("structure Foo { bar: b c -> a, goo, }"),
Ok(StructureDef { name, fields, .. })
if name == "Foo" &&
matches!(fields.as_slice(),
&[StructureField { ref name, ref field_type, .. },
StructureField { name: ref name2, field_type: None, .. }]
if name == "bar" &&
name2 == "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")))));
}
#[test]
fn enum_variant() {
let parse_ev = |str| {
let lexer = Lexer::from(str);
let mut result = Parser::new(0, lexer);
result.parse_enum_variant()
};
assert!(matches!(parse_ev("foo"), Err(_),));
assert!(matches!(parse_ev("foo,"), Err(_),));
assert!(matches!(parse_ev("Cons foo,"), Err(_),));
assert!(matches!(parse_ev(""), Err(_)));
assert!(matches!(parse_ev("}"), Ok(None)));
assert!(matches!(
parse_ev("Cons,"),
Ok(Some(EnumerationVariant { name, argument, .. }))
if name == "Cons" && argument.is_none()));
assert!(matches!(
parse_ev("Cons }"),
Ok(Some(EnumerationVariant { name, argument, .. }))
if name == "Cons" && argument.is_none()));
assert!(matches!(
parse_ev("Cons, }"),
Ok(Some(EnumerationVariant { name, argument, .. }))
if name == "Cons" && argument.is_none()));
assert!(matches!(
parse_ev("Cons(Pair a),"),
Ok(Some(EnumerationVariant { name, ref argument, .. }))
if name == "Cons" &&
matches!(argument, Some(Type::Application(typef, args))
if matches!(typef.as_ref(), Type::Constructor(_, name)
if name == "Pair") &&
matches!(&args.as_slice(), &[Type::Variable(_, argname)]
if argname == "a"))));
assert!(matches!(
parse_ev("Cons(Pair a) }"),
Ok(Some(EnumerationVariant { name, ref argument, .. }))
if name == "Cons" &&
matches!(argument, Some(Type::Application(typef, args))
if matches!(typef.as_ref(), Type::Constructor(_, name)
if name == "Pair") &&
matches!(&args.as_slice(), &[Type::Variable(_, argname)]
if argname == "a"))));
assert!(matches!(
parse_ev("Cons(a b -> c) }"),
Ok(Some(EnumerationVariant { name, ref argument, .. }))
if name == "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"))));
}
#[test]
fn enumerations() {
let parse_en = |str| {
let lexer = Lexer::from(str);
let mut result = Parser::new(0, lexer);
result.parse_enumeration()
};
assert!(matches!(parse_en("enumeration { }"), Err(_)));
assert!(matches!(parse_en("enumeration {"), Err(_)));
assert!(matches!(parse_en("enumeration"), Err(_)));
assert!(matches!(
parse_en("enumeration Empty { }"),
Ok(EnumerationDef { name, variants, .. })
if name == "Empty" && variants.is_empty()));
assert!(matches!(
parse_en("enumeration Alternates { A, B }"),
Ok(EnumerationDef { name, variants, .. })
if name == "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())));
assert!(matches!(
parse_en("enumeration Alternates { A, B, }"),
Ok(EnumerationDef { name, variants, .. })
if name == "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())));
}
#[test]
fn expressions() {
let parse_ex = |str| {
let lexer = Lexer::from(str);
let mut result = Parser::new(0, lexer);
result.parse_expression()
};
assert!(matches!(parse_ex(""), Err(_)));
assert!(matches!(
parse_ex("x"),
Ok(Expression::Reference(n)) if n.as_printed() == "x"));
assert!(matches!(
parse_ex("(x)"),
Ok(Expression::Reference(n)) if n.as_printed() == "x"));
assert!(matches!(
parse_ex("'c'"),
Ok(Expression::Value(ConstantValue::Character(_, _)))
));
assert!(matches!(
parse_ex("\"c\""),
Ok(Expression::Value(ConstantValue::String(_, _)))
));
assert!(matches!(
parse_ex("1"),
Ok(Expression::Value(ConstantValue::Integer(_, _)))
));
assert!(matches!(
parse_ex("(1)"),
Ok(Expression::Value(ConstantValue::Integer(_, _)))
));
}
#[test]
fn enumeration_values() {
let parse_ex = |str| {
let lexer = Lexer::from(str);
let mut result = Parser::new(0, lexer);
result.parse_expression()
};
assert!(matches!(parse_ex("Hello::world"), Err(_)));
assert!(matches!(
parse_ex("Hello::World"),
Ok(Expression::EnumerationValue(t, v, None))
if t.as_printed() == "Hello" &&
v.as_printed() == "World"));
assert!(matches!(
parse_ex("Hello::World(a)"),
Ok(Expression::EnumerationValue(t, v, Some(_)))
if t.as_printed() == "Hello" &&
v.as_printed() == "World"));
}
#[test]
fn structure_value() {
let parse_st = |str| {
let lexer = Lexer::from(str);
let mut result = Parser::new(0, lexer);
result.parse_expression()
};
assert!(matches!(parse_st("Foo{ , }"), Err(_)));
assert!(matches!(parse_st("Foo{ foo, }"), Err(_)));
assert!(matches!(parse_st("Foo{ foo: , }"), Err(_)));
assert!(matches!(parse_st("Foo{ , foo: 1, }"), Err(_)));
assert!(matches!(
parse_st("Foo{ foo: 1 }"),
Ok(Expression::StructureValue(sname, values))
if sname.as_printed() == "Foo" &&
matches!(values.as_slice(), [FieldValue{ field, value }]
if field.as_printed() == "foo" &&
matches!(value, Expression::Value(ConstantValue::Integer(_,_))))));
assert!(matches!(
parse_st("Foo{ foo: 1, }"),
Ok(Expression::StructureValue(sname, values))
if sname.as_printed() == "Foo" &&
matches!(values.as_slice(), [FieldValue{ field, value }]
if field.as_printed() == "foo" &&
matches!(value, Expression::Value(ConstantValue::Integer(_,_))))));
assert!(matches!(
parse_st("Foo{ foo: 1, bar: \"foo\" }"),
Ok(Expression::StructureValue(sname, values))
if sname.as_printed() == "Foo" &&
matches!(values.as_slice(), [FieldValue{ field: f1, value: v1 },
FieldValue{ field: f2, value: v2 }]
if f1.as_printed() == "foo" &&
f2.as_printed() == "bar" &&
matches!(v1, Expression::Value(ConstantValue::Integer(_,_))) &&
matches!(v2, Expression::Value(ConstantValue::String(_,_))))));
assert!(matches!(
parse_st("Foo{ foo: 1, bar: \"foo\", }"),
Ok(Expression::StructureValue(sname, values))
if sname.as_printed() == "Foo" &&
matches!(values.as_slice(), [FieldValue{ field: f1, value: v1 },
FieldValue{ field: f2, value: v2 }]
if f1.as_printed() == "foo" &&
f2.as_printed() == "bar" &&
matches!(v1, Expression::Value(ConstantValue::Integer(_,_))) &&
matches!(v2, Expression::Value(ConstantValue::String(_,_))))));
assert!(matches!(
parse_st("Foo{ foo: 1,, bar: \"foo\", }"),
Err(_)));
}