use crate::ast::{
Alt, Binding, ChoiceDecl, Consuming, Decl, DoStmt, Equation, ExpectedToken, Expr, FieldAssign,
FieldDecl, FunctionDecl, Identifier, ImportDecl, ImportStyle, InterfaceDecl,
InterfaceInstanceDecl, LitKind, MalformedSyntaxKind, Module, ModuleName, Operator,
ParseDiagnostic, ParseDiagnosticKind, Pat, SkippedDeclarationReason, Span, TemplateBodyDecl,
TemplateDecl, Type, TypeAnnotation, TypeAnnotationContext, UnsupportedSyntaxKind,
};
use crate::layout::resolve_layout;
use crate::lexer::{lex, Pos, Token, TokenKind};
use std::collections::HashMap;
pub const MAX_RECURSION_DEPTH: u32 = 128;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseModuleResult {
pub module: Module,
pub diagnostics: Vec<ParseDiagnostic>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseModuleError {
diagnostics: Vec<ParseDiagnostic>,
module: Box<Module>,
}
impl ParseModuleError {
#[must_use]
pub fn diagnostics(&self) -> &[ParseDiagnostic] {
&self.diagnostics
}
#[must_use]
pub fn module(&self) -> &Module {
&self.module
}
#[must_use]
pub fn into_parts(self) -> (Vec<ParseDiagnostic>, Module) {
(self.diagnostics, *self.module)
}
}
impl std::fmt::Display for ParseModuleError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"parse failed with {} diagnostic(s)",
self.diagnostics.len()
)?;
if let Some(first) = self.diagnostics.first() {
write!(f, ": {}", first.message)?;
}
Ok(())
}
}
impl std::error::Error for ParseModuleError {}
#[derive(Clone, Copy, Debug)]
enum DoExpressionMode {
Allow,
Disallow,
}
impl DoExpressionMode {
const fn allows_do(&self) -> bool {
matches!(self, Self::Allow)
}
}
impl ParseModuleResult {
#[must_use]
pub const fn has_errors(&self) -> bool {
!self.diagnostics.is_empty()
}
#[must_use]
pub fn into_parts(self) -> (Module, Vec<ParseDiagnostic>) {
(self.module, self.diagnostics)
}
pub fn into_result(self) -> Result<Module, ParseModuleError> {
if self.diagnostics.is_empty() {
Ok(self.module)
} else {
Err(ParseModuleError {
diagnostics: self.diagnostics,
module: Box::new(self.module),
})
}
}
}
#[must_use]
pub fn parse_module(source: &str) -> ParseModuleResult {
let lexed = lex(source);
let tokens = lexed.tokens;
let lex_errors = lexed.errors;
let tokens = resolve_layout(tokens);
let mut p = Parser {
toks: tokens,
src_len: source.len(),
i: 0,
depth: 0,
diags: lex_errors
.into_iter()
.map(|e| {
let range = e.byte_range_in(source);
ParseDiagnostic::new(
ParseDiagnosticKind::Lex(e.kind.clone()),
e.to_string(),
e.pos,
crate::ast::Span::from_usize(range.start, range.end),
)
})
.collect(),
};
let mut module = p.module();
module.span = crate::ast::Span::from_usize(0, source.len());
ParseModuleResult {
module,
diagnostics: p.diags,
}
}
pub fn parse_module_strict(source: &str) -> Result<Module, ParseModuleError> {
parse_module(source).into_result()
}
struct Parser {
toks: Vec<Token>,
src_len: usize,
i: usize,
diags: Vec<ParseDiagnostic>,
depth: u32,
}
impl Parser {
fn node_span(&self, from: usize) -> crate::ast::Span {
let mut a = from;
while a < self.i && self.toks[a].is_virtual() {
a += 1;
}
let mut b = self.i;
while b > a && self.toks[b - 1].is_virtual() {
b -= 1;
}
if a >= b {
let p = self.byte_at(a);
return crate::ast::Span::from_usize(p, p);
}
crate::ast::Span::from_usize(self.toks[a].start, self.toks[b - 1].end)
}
fn byte_at(&self, i: usize) -> usize {
self.toks.get(i).map(|t| t.start).unwrap_or(self.src_len)
}
fn end_byte(&self) -> usize {
let mut b = self.i;
while b > 0 && self.toks[b - 1].is_virtual() {
b -= 1;
}
if b == 0 {
0
} else {
self.toks[b - 1].end
}
}
}
impl Parser {
fn peek(&self) -> Option<&TokenKind> {
self.toks.get(self.i).map(|t| &t.kind)
}
fn peek_at(&self, n: usize) -> Option<&TokenKind> {
self.toks.get(self.i + n).map(|t| &t.kind)
}
fn pos(&self) -> Pos {
self.toks
.get(self.i)
.or_else(|| self.toks.last())
.map_or(Pos { line: 1, column: 1 }, |t| t.pos)
}
fn bump(&mut self) -> Option<Token> {
let t = self.toks.get(self.i).cloned();
if t.is_some() {
self.i += 1;
}
t
}
fn at_keyword(&self, kw: &str) -> bool {
self.peek().is_some_and(|t| t.is_keyword(kw))
}
fn eat_keyword(&mut self, kw: &str) -> bool {
if self.at_keyword(kw) {
self.i += 1;
true
} else {
false
}
}
fn at_op(&self, op: &str) -> bool {
self.peek().is_some_and(|t| t.is_op(op))
}
fn eat_op(&mut self, op: &str) -> bool {
if self.at_op(op) {
self.i += 1;
true
} else {
false
}
}
fn at(&self, tok: &TokenKind) -> bool {
self.peek() == Some(tok)
}
fn eat(&mut self, tok: &TokenKind) -> bool {
if self.at(tok) {
self.i += 1;
true
} else {
false
}
}
fn diag_expected(&mut self, expected: ExpectedToken, message: impl Into<String>) {
self.diag_kind(ParseDiagnosticKind::ExpectedToken(expected), message);
}
fn diag_malformed(&mut self, kind: MalformedSyntaxKind, message: impl Into<String>) {
self.diag_kind(ParseDiagnosticKind::MalformedSyntax(kind), message);
}
fn diag_kind(&mut self, kind: ParseDiagnosticKind, message: impl Into<String>) {
let pos = self.pos();
let span = self.cur_span();
self.diags
.push(ParseDiagnostic::new(kind, message, pos, span));
}
fn parse_type_annotation(
&mut self,
type_start: usize,
type_end: usize,
context: TypeAnnotationContext,
) -> TypeAnnotation {
let type_start = type_start.min(self.toks.len());
let type_end = type_end.min(self.toks.len());
let tokens = &self.toks[type_start..type_end];
let ty = parse_type_from_tokens(tokens).or_else(|| {
let trimmed = Self::trim_type_tokens_for_parse(tokens);
if trimmed < tokens.len() {
parse_type_from_tokens(&tokens[..trimmed])
} else {
None
}
});
match ty {
Some(ty) => TypeAnnotation::Present(ty),
None => {
let span = self.span_of_token_range(type_start, type_end);
self.diags.push(ParseDiagnostic::new(
ParseDiagnosticKind::MalformedTypeAnnotation(context),
format!("malformed {} type annotation", context.as_str()),
self.pos_of_token(type_start),
span,
));
TypeAnnotation::Malformed { span }
}
}
}
fn trim_type_tokens_for_parse(tokens: &[Token]) -> usize {
let mut depth = 0usize;
let mut bracket_depth = 0usize;
let mut i = 0usize;
while i < tokens.len() {
match &tokens[i].kind {
TokenKind::LParen | TokenKind::LBracket => {
depth += 1;
i += 1;
}
TokenKind::RParen | TokenKind::RBracket => {
depth = depth.saturating_sub(1);
i += 1;
}
TokenKind::LBrace => {
bracket_depth += 1;
i += 1;
}
TokenKind::RBrace => {
bracket_depth = bracket_depth.saturating_sub(1);
i += 1;
}
TokenKind::Op(o) if o.as_str() == "=" && depth == 0 && bracket_depth == 0 => {
return i;
}
TokenKind::Semi | TokenKind::VSemi if depth == 0 && bracket_depth == 0 => {
return i;
}
TokenKind::Comma
if depth == 0
&& bracket_depth == 0
&& matches!(
tokens.get(i + 1),
Some(Token {
kind: TokenKind::LowerId {
qualifier: None,
..
},
..
})
)
&& matches!(
tokens.get(i + 2),
Some(Token { kind: TokenKind::Op(o), .. }) if o.as_str() == ":"
) =>
{
return i;
}
_ => {
i += 1;
}
}
}
tokens.len()
}
fn pos_of_token(&self, idx: usize) -> Pos {
self.toks.get(idx).map_or_else(|| self.pos(), |tok| tok.pos)
}
fn span_of_token_range(&self, start: usize, end: usize) -> Span {
let start = start.min(self.toks.len());
let end = end.min(self.toks.len());
let span_start = self.byte_at(start);
if end <= start {
return Span::from_usize(span_start, span_start);
}
let mut cursor = end;
while cursor > start {
cursor -= 1;
let token = &self.toks[cursor];
if !token.is_virtual() {
return Span::from_usize(span_start, token.end);
}
}
Span::from_usize(span_start, span_start)
}
fn cur_span(&self) -> crate::ast::Span {
let mut j = self.i;
while self.toks.get(j).is_some_and(|t| t.is_virtual()) {
j += 1;
}
self.toks.get(j).map_or_else(
|| crate::ast::Span::from_usize(self.src_len, self.src_len),
|t| crate::ast::Span::from_usize(t.start, t.end),
)
}
fn skip_to_item_end(&mut self) {
let mut depth = 0usize;
let mut brackets = 0usize;
while let Some(t) = self.peek() {
match t {
TokenKind::VLBrace => depth += 1,
TokenKind::VRBrace => {
if depth == 0 {
return;
}
depth -= 1;
}
TokenKind::VSemi if depth == 0 && brackets == 0 => return,
TokenKind::LParen | TokenKind::LBracket | TokenKind::LBrace => brackets += 1,
TokenKind::RParen | TokenKind::RBracket | TokenKind::RBrace => {
if brackets == 0 {
return;
}
brackets -= 1;
}
_ => {}
}
self.i += 1;
}
}
fn slice_text(&self, start: usize) -> String {
render_token_slice(&self.toks[start..self.i])
}
fn module(&mut self) -> Module {
let pos = self.pos();
let header_start = self.i;
let mut header = crate::ast::Span::from_usize(0, 0);
let mut name = ModuleName::from("Unknown");
if self.eat_keyword("module") {
if let Some(TokenKind::UpperId { qualifier, name: n }) = self.peek().cloned() {
self.bump();
name = match qualifier {
Some(q) => format!("{q}.{n}").into(),
None => n.into(),
};
}
if self.at(&TokenKind::LParen) {
self.skip_balanced_parens();
}
if !self.eat_keyword("where") {
self.diag_expected(
ExpectedToken::WhereAfterModuleHeader,
"expected 'where' after module header",
);
}
header = self.node_span(header_start);
}
let mut imports = Vec::new();
let mut decls: Vec<Decl> = Vec::new();
let _ = self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace);
loop {
while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
match self.peek() {
None => break,
Some(TokenKind::VRBrace | TokenKind::RBrace) => {
self.bump();
break;
}
Some(TokenKind::RParen | TokenKind::RBracket) => {
let cpos = self.pos();
let cstart = self.i;
self.bump();
decls.push(Decl::Unknown {
raw: self.slice_text(cstart),
pos: cpos,
span: self.node_span(cstart),
});
continue;
}
_ => {}
}
let before = self.i;
self.declaration(&mut imports, &mut decls);
if self.i == before {
self.bump();
}
}
merge_functions(&mut decls);
Module {
name,
pos,
header,
imports,
decls,
span: crate::ast::Span::from_usize(0, self.src_len),
}
}
fn skip_balanced_parens(&mut self) {
let mut depth = 0usize;
while let Some(t) = self.peek() {
match t {
TokenKind::LParen => depth += 1,
TokenKind::RParen => {
if depth == 0 {
return;
}
depth -= 1;
if depth == 0 {
self.i += 1;
return;
}
}
_ => {}
}
self.i += 1;
}
}
fn try_infix_operator_decl(&mut self) -> bool {
let snap = self.i;
let saved_diags = self.diags.len();
if self.pattern().is_some()
&& matches!(self.peek(), Some(TokenKind::Op(o)) if !is_reserved_op(o))
{
self.skip_to_item_end();
return true;
}
self.i = snap;
self.diags.truncate(saved_diags);
false
}
fn declaration(&mut self, imports: &mut Vec<ImportDecl>, decls: &mut Vec<Decl>) {
let pos = self.pos();
let start = self.i;
if matches!(
self.peek(),
Some(TokenKind::UpperId { .. } | TokenKind::LBracket | TokenKind::LParen)
) && self.try_infix_operator_decl()
{
decls.push(Decl::Unknown {
raw: self.slice_text(start),
pos,
span: self.node_span(start),
});
return;
}
match self.peek() {
Some(t) if t.is_keyword("import") => {
let imp = self.import_decl();
self.skip_to_item_end();
if let Some(mut imp) = imp {
imp.span = self.node_span(start);
imports.push(imp);
}
}
Some(t) if t.is_keyword("template") => {
match self.template_decl() {
Some(t) => decls.push(Decl::Template(t)),
None => {
self.skip_to_item_end();
decls.push(Decl::Unknown {
raw: self.slice_text(start),
span: self.node_span(start),
pos,
});
}
}
}
Some(t) if t.is_keyword("interface") => match self.interface_decl() {
Some(i) => decls.push(Decl::Interface(i)),
None => {
self.skip_to_item_end();
decls.push(Decl::Unknown {
raw: self.slice_text(start),
span: self.node_span(start),
pos,
});
}
},
Some(t)
if matches!(
t.keyword(),
Some("infix" | "infixl" | "infixr" | "default" | "pattern")
) =>
{
self.skip_to_item_end();
decls.push(Decl::Unknown {
raw: self.slice_text(start),
pos,
span: self.node_span(start),
});
}
Some(t)
if matches!(
t.keyword(),
Some(
"data"
| "type"
| "newtype"
| "class"
| "instance"
| "exception"
| "deriving"
)
) =>
{
let keyword = t
.keyword()
.expect("declaration-head keyword token")
.to_string();
self.bump();
let name = match self.peek() {
Some(TokenKind::UpperId { qualifier, name }) => {
let n = qualifier
.as_ref()
.map_or_else(|| name.to_string(), |q| format!("{q}.{name}"));
self.bump();
n
}
_ => String::new(),
};
self.skip_to_item_end();
decls.push(Decl::TypeDef {
keyword,
name: name.into(),
pos,
span: self.node_span(start),
});
}
Some(TokenKind::LowerId { .. }) => match self.function_item() {
Some(d) => decls.push(d),
None => {
self.skip_to_item_end();
decls.push(Decl::Unknown {
raw: self.slice_text(start),
span: self.node_span(start),
pos,
});
}
},
Some(TokenKind::LParen)
if matches!(self.peek_at(1), Some(TokenKind::Op(_)))
&& self.peek_at(2) == Some(&TokenKind::RParen) =>
{
self.skip_to_item_end();
decls.push(Decl::Unknown {
raw: self.slice_text(start),
span: self.node_span(start),
pos,
});
}
Some(TokenKind::LParen | TokenKind::LBracket) => {
if self.binding().is_none() {
self.diag_kind(
ParseDiagnosticKind::SkippedDeclaration(
SkippedDeclarationReason::TopLevelPatternBinding,
),
"unparseable top-level pattern binding",
);
}
self.skip_to_item_end();
decls.push(Decl::Unknown {
raw: self.slice_text(start),
span: self.node_span(start),
pos,
});
}
_ => {
self.diag_kind(
ParseDiagnosticKind::SkippedDeclaration(
SkippedDeclarationReason::UnrecognizedDeclaration,
),
format!("unrecognized declaration: {:?}", self.peek()),
);
self.skip_to_item_end();
decls.push(Decl::Unknown {
raw: self.slice_text(start),
span: self.node_span(start),
pos,
});
}
}
}
fn import_decl(&mut self) -> Option<ImportDecl> {
let pos = self.pos();
let start_i = self.i;
self.bump(); let mut style = if self.eat_keyword("qualified") {
ImportStyle::Qualified
} else {
ImportStyle::Unqualified
};
if matches!(self.peek(), Some(TokenKind::StringLit(_))) {
self.bump();
}
let module_name = match self.peek().cloned() {
Some(TokenKind::UpperId { qualifier, name }) => {
self.bump();
match qualifier {
Some(q) => format!("{q}.{name}").into(),
None => name.into(),
}
}
_ => {
self.diag_expected(
ExpectedToken::ModuleNameAfterImport,
"expected module name after 'import'",
);
return None;
}
};
if self.eat_keyword("qualified") {
style = ImportStyle::Qualified;
}
let mut alias = None;
if self.eat_keyword("as") {
if let Some(TokenKind::UpperId { qualifier, name }) = self.peek().cloned() {
self.bump();
alias = Some(match qualifier {
Some(q) => format!("{q}.{name}").into(),
None => name.into(),
});
}
}
Some(ImportDecl {
module_name,
style,
alias,
pos,
span: self.node_span(start_i),
})
}
fn upper_name(&mut self) -> Option<ModuleName> {
match self.peek().cloned() {
Some(TokenKind::UpperId { qualifier, name }) => {
self.bump();
Some(match qualifier {
Some(q) => format!("{q}.{name}").into(),
None => name.into(),
})
}
_ => None,
}
}
fn template_decl(&mut self) -> Option<TemplateDecl> {
let pos = self.pos();
let start_i = self.i;
self.bump(); if self.at_keyword("instance") {
return None; }
let name = self.upper_name()?.to_string().into();
let fields = self
.eat_keyword("with")
.then(|| self.field_block())
.map(|parsed| parsed.fields)
.unwrap_or_default();
let body = if self.eat_keyword("where") {
self.template_body()
} else {
Vec::new()
};
Some(TemplateDecl {
name,
fields,
body,
pos,
span: self.node_span(start_i),
})
}
fn field_block(&mut self) -> FieldBlock {
let mut fields = Vec::new();
if !(self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace)) {
return FieldBlock {
fields,
dangling: false,
};
}
loop {
while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
match self.peek() {
None => break,
Some(TokenKind::VRBrace | TokenKind::RBrace) => {
self.bump();
break;
}
Some(TokenKind::RParen | TokenKind::RBracket) => {
self.bump();
continue;
}
_ => {}
}
{
let mut j = self.i;
while let Some(TokenKind::LowerId {
qualifier: None, ..
}) = self.toks.get(j).map(|t| &t.kind)
{
j += 1;
match self.toks.get(j).map(|t| &t.kind) {
Some(TokenKind::Comma) => j += 1,
_ => break,
}
}
let is_field = j > self.i
&& self
.toks
.get(j)
.map(|t| &t.kind)
.is_some_and(|t| t.is_op(":"));
if !is_field {
return FieldBlock {
fields,
dangling: true,
};
}
}
let mut names: Vec<(Identifier, Pos, Span)> = Vec::new();
while let Some(TokenKind::LowerId {
qualifier: None,
name,
}) = self.peek().cloned()
{
let p = self.pos();
let nspan = Span::from_usize(self.toks[self.i].start, self.toks[self.i].end);
self.bump();
names.push((name, p, nspan));
if !self.eat(&TokenKind::Comma) {
break;
}
}
if names.is_empty() || !self.eat_op(":") {
self.diag_expected(
ExpectedToken::FieldNameTypePair,
"expected 'name : Type' field",
);
self.skip_to_item_end();
continue;
}
let ty_start = self.i;
self.skip_to_item_end();
let ty = self.parse_type_annotation(ty_start, self.i, TypeAnnotationContext::Field);
let type_end = self.end_byte();
let last = names.len() - 1;
for (idx, (name, p, nspan)) in names.into_iter().enumerate() {
let span = if idx == last {
Span::from_usize(nspan.start_usize(), type_end.max(nspan.end_usize()))
} else {
nspan
};
fields.push(FieldDecl {
name: name.to_string().into(),
ty: ty.clone(),
pos: p,
span,
});
}
}
FieldBlock {
fields,
dangling: false,
}
}
fn template_body(&mut self) -> Vec<TemplateBodyDecl> {
let mut body = Vec::new();
if !(self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace)) {
return body;
}
loop {
while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
match self.peek() {
None => break,
Some(TokenKind::VRBrace | TokenKind::RBrace) => {
self.bump();
break;
}
Some(TokenKind::RParen | TokenKind::RBracket) => {
self.bump();
continue;
}
_ => {}
}
let pos = self.pos();
let start = self.i;
let decl = self.template_body_item(pos, start);
body.push(decl);
}
body
}
fn template_body_item(&mut self, pos: Pos, start: usize) -> TemplateBodyDecl {
match self.peek().and_then(|t| t.keyword()) {
Some("signatory") => {
self.bump();
let parties = self.expr_comma_list();
self.skip_to_item_end();
TemplateBodyDecl::Signatory {
parties,
pos,
span: self.node_span(start),
}
}
Some("observer") => {
self.bump();
let parties = self.expr_comma_list();
self.skip_to_item_end();
TemplateBodyDecl::Observer {
parties,
pos,
span: self.node_span(start),
}
}
Some("ensure") => {
self.bump();
let expr = self.expr();
self.skip_to_item_end();
TemplateBodyDecl::Ensure {
expr,
pos,
span: self.node_span(start),
}
}
Some("key") => {
self.bump();
let expr_start = self.i;
let expr = self.expr();
let ty = if self.eat_op(":") {
let ty_start = self.i;
self.skip_to_item_end();
self.parse_type_annotation(ty_start, self.i, TypeAnnotationContext::Key)
} else {
let mut depth = 0i32;
let mut colon = None;
for j in expr_start..self.i {
match &self.toks[j].kind {
TokenKind::LParen | TokenKind::LBracket => depth += 1,
TokenKind::RParen | TokenKind::RBracket if depth > 0 => depth -= 1,
TokenKind::Op(o) if o.as_str() == ":" && depth == 0 => colon = Some(j),
_ => {}
}
}
let ty = colon.map_or(TypeAnnotation::Absent, |j| {
self.parse_type_annotation(j + 1, self.i, TypeAnnotationContext::Key)
});
self.skip_to_item_end();
ty
};
TemplateBodyDecl::Key {
expr,
ty,
pos,
span: self.node_span(start),
}
}
Some("maintainer") => {
self.bump();
let expr = self.expr();
self.skip_to_item_end();
TemplateBodyDecl::Maintainer {
expr,
pos,
span: self.node_span(start),
}
}
Some("choice" | "nonconsuming" | "preconsuming" | "postconsuming") => {
self.choice_decl().map_or_else(
|| {
self.skip_to_item_end();
TemplateBodyDecl::Other {
raw: self.slice_text(start),
span: self.node_span(start),
pos,
}
},
TemplateBodyDecl::Choice,
)
}
Some("interface") => self.interface_instance_decl().map_or_else(
|| {
self.skip_to_item_end();
TemplateBodyDecl::Other {
raw: self.slice_text(start),
span: self.node_span(start),
pos,
}
},
TemplateBodyDecl::InterfaceInstance,
),
Some("controller") => {
self.diag_kind(
ParseDiagnosticKind::UnsupportedSyntax(
UnsupportedSyntaxKind::LegacyControllerCan,
),
"legacy 'controller ... can' syntax is not supported; \
choices inside this block are not analyzed",
);
self.skip_to_item_end();
TemplateBodyDecl::Other {
raw: self.slice_text(start),
span: self.node_span(start),
pos,
}
}
_ => {
self.skip_to_item_end();
TemplateBodyDecl::Other {
raw: self.slice_text(start),
span: self.node_span(start),
pos,
}
}
}
}
fn choice_decl(&mut self) -> Option<ChoiceDecl> {
let pos = self.pos();
let start_i = self.i;
let consuming = match self.peek().and_then(|t| t.keyword()) {
Some("nonconsuming") => {
self.bump();
Consuming::NonConsuming
}
Some("preconsuming") => {
self.bump();
Consuming::PreConsuming
}
Some("postconsuming") => {
self.bump();
Consuming::PostConsuming
}
_ => Consuming::Consuming,
};
if !self.eat_keyword("choice") {
return None;
}
let name = self.upper_name()?.to_string().into();
let return_ty = if self.eat_op(":") {
let ty_start = self.i;
self.skip_type_tokens();
self.parse_type_annotation(ty_start, self.i, TypeAnnotationContext::Choice)
} else {
TypeAnnotation::Absent
};
let (params, dangling) = if self.eat_keyword("with") {
let parsed = self.field_block();
(parsed.fields, parsed.dangling)
} else {
(Vec::new(), false)
};
let mut observers = Vec::new();
let mut controllers = Vec::new();
loop {
if dangling {
while self.eat(&TokenKind::VSemi) {}
}
if self.eat_keyword("observer") {
observers = self.expr_comma_list_no_do();
} else if self.eat_keyword("controller") {
controllers = self.expr_comma_list_no_do();
} else {
break;
}
}
if dangling {
while self.eat(&TokenKind::VSemi) {}
}
let body = if self.peek().is_some_and(|t| {
!matches!(
t,
TokenKind::VSemi | TokenKind::VRBrace | TokenKind::Semi | TokenKind::RBrace
)
}) {
Some(self.expr())
} else {
None
};
self.skip_to_item_end();
if dangling {
self.eat(&TokenKind::VRBrace);
self.skip_to_item_end();
}
Some(ChoiceDecl {
name,
consuming,
return_ty,
params,
controllers,
observers,
body,
pos,
span: self.node_span(start_i),
})
}
fn skip_type_tokens(&mut self) {
let mut brackets = 0usize;
while let Some(t) = self.peek() {
match t {
TokenKind::VSemi | TokenKind::VRBrace | TokenKind::VLBrace | TokenKind::Semi => {
return
}
TokenKind::LParen | TokenKind::LBracket => brackets += 1,
TokenKind::RParen | TokenKind::RBracket => {
if brackets == 0 {
return;
}
brackets -= 1;
}
_ if brackets == 0
&& matches!(
t.keyword(),
Some("with" | "controller" | "observer" | "do" | "where")
) =>
{
return
}
_ => {}
}
self.i += 1;
}
}
fn interface_decl(&mut self) -> Option<InterfaceDecl> {
let pos = self.pos();
let start_i = self.i;
self.bump(); if self.at_keyword("instance") {
return None;
}
let name = self.upper_name()?.to_string().into();
let mut requires = Vec::new();
if self.eat_keyword("requires") {
while let Some(r) = self.upper_name() {
requires.push(r);
if !self.eat(&TokenKind::Comma) {
break;
}
}
}
if !self.eat_keyword("where") {
return None;
}
let mut viewtype = None;
let mut methods = Vec::new();
let mut choices = Vec::new();
if !(self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace)) {
return Some(InterfaceDecl {
name,
requires,
viewtype,
methods,
choices,
pos,
span: self.node_span(start_i),
});
}
loop {
while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
match self.peek() {
None => break,
Some(TokenKind::VRBrace | TokenKind::RBrace) => {
self.bump();
break;
}
Some(TokenKind::RParen | TokenKind::RBracket) => {
self.bump();
continue;
}
_ => {}
}
match self.peek().and_then(|t| t.keyword()) {
Some("viewtype") => {
self.bump();
viewtype = self.upper_name();
self.skip_to_item_end();
}
Some("choice" | "nonconsuming" | "preconsuming" | "postconsuming") => {
if let Some(c) = self.choice_decl() {
choices.push(c);
} else {
self.skip_to_item_end();
}
}
_ => {
let mpos = self.pos();
if let Some(TokenKind::LowerId {
qualifier: None,
name: mname,
}) = self.peek().cloned()
{
if self.peek_at(1).is_some_and(|t| t.is_op(":")) {
let mstart = self.toks[self.i].start;
self.bump();
self.bump();
let ty_start = self.i;
self.skip_to_item_end();
methods.push(FieldDecl {
name: mname,
ty: self.parse_type_annotation(
ty_start,
self.i,
TypeAnnotationContext::InterfaceMethod,
),
pos: mpos,
span: Span::from_usize(mstart, self.end_byte().max(mstart)),
});
continue;
}
}
self.skip_to_item_end();
}
}
}
Some(InterfaceDecl {
name,
requires,
viewtype,
methods,
choices,
pos,
span: self.node_span(start_i),
})
}
fn interface_instance_decl(&mut self) -> Option<InterfaceInstanceDecl> {
let pos = self.pos();
let start_i = self.i;
self.bump(); if !self.eat_keyword("instance") {
return None;
}
let interface_name = self.upper_name()?;
let for_template = if self.eat_keyword("for") {
self.upper_name().map_or_else(
|| {
self.diag_expected(
ExpectedToken::TemplateNameAfterInterfaceInstanceFor,
"interface instance missing template name after 'for'",
);
None
},
Some,
)
} else {
None
};
let mut methods = Vec::new();
if self.eat_keyword("where")
&& (self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace))
{
loop {
while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
match self.peek() {
None => break,
Some(TokenKind::VRBrace | TokenKind::RBrace) => {
self.bump();
break;
}
Some(TokenKind::RParen | TokenKind::RBracket) => {
self.bump();
continue;
}
_ => {}
}
if let Some(b) = self.binding() {
methods.push(b);
} else {
self.skip_to_item_end();
}
}
}
Some(InterfaceInstanceDecl {
interface_name,
for_template,
methods,
pos,
span: self.node_span(start_i),
})
}
fn function_item(&mut self) -> Option<Decl> {
let pos = self.pos();
let start_i = self.i;
let Some(TokenKind::LowerId {
qualifier: None,
name,
}) = self.peek().cloned()
else {
return None;
};
let mut j = self.i + 1;
let mut is_sig = false;
loop {
match self.toks.get(j).map(|t| &t.kind) {
Some(TokenKind::Comma) => {
j += 1;
if matches!(
self.toks.get(j).map(|t| &t.kind),
Some(TokenKind::LowerId {
qualifier: None,
..
})
) {
j += 1;
continue;
}
break;
}
Some(TokenKind::Op(o)) if o.as_str() == ":" => {
is_sig = true;
break;
}
_ => break,
}
}
if is_sig {
self.bump(); while self.eat(&TokenKind::Comma) {
self.bump(); }
self.eat_op(":");
let ty_start = self.i;
self.skip_to_item_end();
let ty = self.parse_type_annotation(ty_start, self.i, TypeAnnotationContext::Function);
return Some(Decl::Function(FunctionDecl {
name,
ty,
equations: Vec::new(),
pos,
sig_span: Some(self.node_span(start_i)),
span: self.node_span(start_i),
}));
}
self.bump(); let mut params = Vec::new();
while !self.at_op("=") && !self.at_op("|") {
if self.at_op(":") {
self.bump();
let mut brackets = 0usize;
while let Some(t) = self.peek() {
match t {
TokenKind::Op(o) if o.as_str() == "=" && brackets == 0 => break,
TokenKind::VSemi
| TokenKind::VRBrace
| TokenKind::Semi
| TokenKind::RBrace => break,
TokenKind::LParen | TokenKind::LBracket => brackets += 1,
TokenKind::RParen | TokenKind::RBracket => {
brackets = brackets.saturating_sub(1)
}
_ => {}
}
self.i += 1;
}
continue;
}
if matches!(self.peek(), Some(TokenKind::Op(o)) if !is_reserved_op(o)) {
self.skip_to_item_end();
return None;
}
match self.peek() {
None
| Some(
TokenKind::VSemi | TokenKind::VRBrace | TokenKind::Semi | TokenKind::RBrace,
) => {
self.diag_malformed(
MalformedSyntaxKind::FunctionEquation,
format!("could not parse equation for '{name}'"),
);
return None;
}
_ => {}
}
match self.pattern_atom() {
Some(p) => params.push(p),
None => {
self.diag_malformed(
MalformedSyntaxKind::FunctionParameterPattern,
format!("bad parameter pattern in '{name}'"),
);
return None;
}
}
}
let (body, guards) = self.equation_rhs()?;
let where_bindings = if self.eat_keyword("where") {
self.binding_block()
} else {
Vec::new()
};
self.skip_to_item_end();
Some(Decl::Function(FunctionDecl {
name,
ty: TypeAnnotation::Absent,
equations: vec![Equation {
params,
body,
guards,
where_bindings,
pos,
span: self.node_span(start_i),
}],
pos,
sig_span: None,
span: self.node_span(start_i),
}))
}
fn equation_rhs(&mut self) -> Option<(Expr, Vec<(Expr, Expr)>)> {
if self.eat_op("=") {
return Some((self.expr(), Vec::new()));
}
let mut guards = Vec::new();
while self.eat_op("|") {
let g = loop {
let g = self.expr();
if self.eat_op("<-") {
let _ = self.expr(); }
if !self.eat(&TokenKind::Comma) {
break g;
}
};
if !self.eat_op("=") {
self.diag_expected(ExpectedToken::EqualsAfterGuard, "expected '=' after guard");
return None;
}
let e = self.expr();
guards.push((g, e));
}
if guards.is_empty() {
self.diag_expected(
ExpectedToken::EqualsOrGuardedRightHandSide,
"expected '=' or guarded right-hand side in equation",
);
None
} else {
let first = guards[0].1.clone();
Some((first, guards))
}
}
fn binding_block(&mut self) -> Vec<Binding> {
let mut bindings = Vec::new();
if !(self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace)) {
return bindings;
}
loop {
while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
match self.peek() {
None => break,
Some(TokenKind::VRBrace | TokenKind::RBrace) => {
self.bump();
break;
}
Some(TokenKind::RParen | TokenKind::RBracket) => {
self.bump();
continue;
}
_ => {}
}
match self.binding() {
Some(b) => bindings.push(b),
None => self.skip_to_item_end(),
}
}
bindings
}
fn binding(&mut self) -> Option<Binding> {
let pos = self.pos();
let start_i = self.i;
if self.at(&TokenKind::LParen)
&& matches!(self.peek_at(1), Some(TokenKind::Op(_)))
&& self.peek_at(2) == Some(&TokenKind::RParen)
{
self.skip_to_item_end();
return None;
}
let pat = self.pattern_atom()?;
let mut params = Vec::new();
loop {
if self.at_op("=") {
self.bump();
let expr = self.expr();
if self.eat_keyword("where") {
let _ = self.binding_block();
}
return Some(Binding {
pat,
params,
expr,
pos,
span: self.node_span(start_i),
});
}
if self.at_op("|") {
let (body, _) = self.equation_rhs()?;
if self.eat_keyword("where") {
let _ = self.binding_block();
}
return Some(Binding {
pat,
params,
expr: body,
pos,
span: self.node_span(start_i),
});
}
if self.at_op(":") {
if params.is_empty() {
self.skip_to_item_end();
return None;
}
self.bump();
let mut brackets = 0usize;
while let Some(t) = self.peek() {
match t {
TokenKind::Op(o) if o.as_str() == "=" && brackets == 0 => break,
TokenKind::VSemi
| TokenKind::VRBrace
| TokenKind::Semi
| TokenKind::RBrace => break,
TokenKind::LParen | TokenKind::LBracket => brackets += 1,
TokenKind::RParen | TokenKind::RBracket => {
brackets = brackets.saturating_sub(1)
}
_ => {}
}
self.i += 1;
}
continue;
}
if matches!(self.peek(), Some(TokenKind::Op(o)) if !is_reserved_op(o)) {
self.skip_to_item_end();
return None;
}
match self.peek() {
None
| Some(
TokenKind::VSemi | TokenKind::VRBrace | TokenKind::Semi | TokenKind::RBrace,
) => return None,
_ => {}
}
params.push(self.pattern_atom()?);
}
}
fn pattern_atom(&mut self) -> Option<Pat> {
if self.depth >= MAX_RECURSION_DEPTH {
return None;
}
self.depth += 1;
let result = self.pattern_atom_inner();
self.depth -= 1;
result
}
fn pattern_atom_inner(&mut self) -> Option<Pat> {
let pos = self.pos();
let start_i = self.i;
if self.at_op("~") || self.at_op("!") {
self.bump();
return self.pattern_atom();
}
match self.peek().cloned() {
Some(TokenKind::LowerId {
qualifier: None,
name,
}) => {
self.bump();
if name == "_" {
return Some(Pat::Wild {
pos,
span: self.node_span(start_i),
});
}
if self.at_op("@") {
self.bump();
let inner = self.pattern_atom()?;
return Some(Pat::As {
name,
pat: Box::new(inner),
pos,
span: self.node_span(start_i),
});
}
Some(Pat::Var {
name,
pos,
span: self.node_span(start_i),
})
}
Some(TokenKind::Op(o)) if o.as_str() == "_" => {
self.bump();
Some(Pat::Wild {
pos,
span: self.node_span(start_i),
})
}
Some(TokenKind::UpperId { qualifier, name }) => {
self.bump();
if self.at(&TokenKind::LBrace) {
self.skip_balanced_braces();
} else if self.eat_keyword("with") {
let _ = self.record_fields();
}
Some(Pat::Con {
qualifier,
name,
args: Vec::new(),
pos,
span: self.node_span(start_i),
})
}
Some(TokenKind::IntLit(text)) => {
self.bump();
Some(Pat::Lit {
kind: LitKind::Int,
text,
pos,
span: self.node_span(start_i),
})
}
Some(TokenKind::DecimalLit(text)) => {
self.bump();
Some(Pat::Lit {
kind: LitKind::Decimal,
text,
pos,
span: self.node_span(start_i),
})
}
Some(TokenKind::StringLit(text)) => {
self.bump();
Some(Pat::Lit {
kind: LitKind::Text,
text,
pos,
span: self.node_span(start_i),
})
}
Some(TokenKind::CharLit(text)) => {
self.bump();
Some(Pat::Lit {
kind: LitKind::Char,
text,
pos,
span: self.node_span(start_i),
})
}
Some(TokenKind::LParen) => {
self.bump();
if self.eat(&TokenKind::RParen) {
return Some(Pat::Con {
qualifier: None,
name: "()".into(),
args: Vec::new(),
pos,
span: self.node_span(start_i),
});
}
{
let mut depth = 0usize;
let mut j = self.i;
let mut arrow = None;
while let Some(t) = self.toks.get(j).map(|t| &t.kind) {
match t {
TokenKind::LParen | TokenKind::LBracket => depth += 1,
TokenKind::RParen | TokenKind::RBracket => {
if depth == 0 {
break;
}
depth -= 1;
}
TokenKind::Op(o) if o.as_str() == ":" && depth == 0 => break,
TokenKind::Op(o) if o.as_str() == "->" && depth == 0 => {
arrow = Some(j);
break;
}
TokenKind::VSemi | TokenKind::VRBrace => break,
TokenKind::Op(o) if o.as_str() == "\\" => break,
_ => {}
}
j += 1;
}
if let Some(j) = arrow {
self.i = j + 1; let inner = self.pattern()?;
self.eat(&TokenKind::RParen);
return Some(inner);
}
}
let first = self.pattern()?;
if self.at_op(":") {
let mut depth = 0usize;
while let Some(t) = self.peek() {
match t {
TokenKind::LParen | TokenKind::LBracket => depth += 1,
TokenKind::RParen if depth == 0 => break,
TokenKind::RParen | TokenKind::RBracket => {
depth = depth.saturating_sub(1)
}
TokenKind::VSemi | TokenKind::VRBrace => break,
_ => {}
}
self.i += 1;
}
}
if self.at(&TokenKind::Comma) {
let mut items = vec![first];
while self.eat(&TokenKind::Comma) {
items.push(self.pattern()?);
}
self.eat(&TokenKind::RParen);
return Some(Pat::Tuple {
items,
pos,
span: self.node_span(start_i),
});
}
self.eat(&TokenKind::RParen);
Some(first)
}
Some(TokenKind::LBracket) => {
self.bump();
let mut items = Vec::new();
if !self.eat(&TokenKind::RBracket) {
loop {
items.push(self.pattern()?);
if !self.eat(&TokenKind::Comma) {
break;
}
}
self.eat(&TokenKind::RBracket);
}
Some(Pat::List {
items,
pos,
span: self.node_span(start_i),
})
}
_ => None,
}
}
fn pattern(&mut self) -> Option<Pat> {
if self.depth >= MAX_RECURSION_DEPTH {
return None;
}
self.depth += 1;
let result = self.pattern_inner();
self.depth -= 1;
result
}
fn pattern_inner(&mut self) -> Option<Pat> {
let pos = self.pos();
let start_i = self.i;
let first = match self.peek().cloned() {
Some(TokenKind::UpperId { qualifier, name }) => {
self.bump();
if self.at(&TokenKind::LBrace) || self.at_keyword("with") {
if self.eat_keyword("with") {
let _ = self.record_fields();
} else {
self.skip_balanced_braces();
}
Pat::Con {
qualifier,
name,
args: Vec::new(),
pos,
span: self.node_span(start_i),
}
} else {
let mut args = Vec::new();
while let Some(a) = self.try_pattern_atom() {
args.push(a);
}
Pat::Con {
qualifier,
name,
args,
pos,
span: self.node_span(start_i),
}
}
}
_ => self.pattern_atom()?,
};
if self.at_op("::") {
self.bump();
let rest = self.pattern()?;
return Some(Pat::Con {
qualifier: None,
name: "::".into(),
args: vec![first, rest],
pos,
span: self.node_span(start_i),
});
}
Some(first)
}
fn try_pattern_atom(&mut self) -> Option<Pat> {
match self.peek() {
Some(
TokenKind::LowerId {
qualifier: None, ..
}
| TokenKind::UpperId { .. }
| TokenKind::IntLit(_)
| TokenKind::DecimalLit(_)
| TokenKind::StringLit(_)
| TokenKind::CharLit(_)
| TokenKind::LParen
| TokenKind::LBracket,
) => self.pattern_atom(),
_ => None,
}
}
fn skip_balanced_braces(&mut self) {
let mut depth = 0usize;
while let Some(t) = self.peek() {
match t {
TokenKind::LBrace => depth += 1,
TokenKind::RBrace => {
if depth == 0 {
return;
}
depth -= 1;
if depth == 0 {
self.i += 1;
return;
}
}
_ => {}
}
self.i += 1;
}
}
fn expr(&mut self) -> Expr {
self.expr_prec(0, DoExpressionMode::Allow)
}
fn expr_no_do(&mut self) -> Expr {
self.expr_prec(0, DoExpressionMode::Disallow)
}
fn expr_comma_list(&mut self) -> Vec<Expr> {
let mut out = vec![self.expr()];
while self.eat(&TokenKind::Comma) {
out.push(self.expr());
}
out
}
fn expr_comma_list_no_do(&mut self) -> Vec<Expr> {
let mut out = vec![self.expr_no_do()];
while self.eat(&TokenKind::Comma) {
out.push(self.expr_no_do());
}
out
}
fn expr_prec(&mut self, min_prec: u8, do_mode: DoExpressionMode) -> Expr {
let pos = self.pos();
let start_i = self.i;
if self.depth >= MAX_RECURSION_DEPTH {
self.diag_kind(
ParseDiagnosticKind::RecursionLimit {
limit: MAX_RECURSION_DEPTH,
},
"expression nesting too deep; truncated to raw text",
);
let start = self.i;
self.skip_to_item_end();
if self.i == start {
self.bump();
}
return Expr::Error {
raw: self.slice_text(start),
span: self.node_span(start),
pos,
};
}
self.depth += 1;
let result = self.expr_prec_inner(min_prec, do_mode, pos, start_i);
self.depth -= 1;
result
}
fn expr_prec_inner(
&mut self,
min_prec: u8,
do_mode: DoExpressionMode,
pos: Pos,
start_i: usize,
) -> Expr {
let Some(mut lhs) = self.unary(do_mode) else {
let start = self.i;
self.skip_to_item_end();
if self.i == start {
self.bump();
}
return Expr::Error {
raw: self.slice_text(start),
span: self.node_span(start),
pos,
};
};
loop {
let (op, prec, right_assoc) = match self.peek() {
Some(TokenKind::Op(o)) => {
let o = o.clone();
if is_reserved_op(&o) {
if o == ":" {
self.bump();
self.skip_type_tokens();
continue;
}
break;
}
let (p, r) = fixity(&o);
(o, p, r)
}
Some(TokenKind::Backtick) => {
let name = match self.peek_at(1) {
Some(
TokenKind::LowerId { qualifier, name }
| TokenKind::UpperId { qualifier, name },
) => qualifier
.as_ref()
.map_or_else(|| name.to_string(), |q| format!("{q}.{name}")),
_ => break,
};
if self.peek_at(2) != Some(&TokenKind::Backtick) {
break;
}
(format!("`{name}`").into(), 9, false)
}
_ => break,
};
if prec < min_prec {
break;
}
self.bump();
if op.starts_with('`') {
self.bump();
self.bump();
}
let next_min = if right_assoc { prec } else { prec + 1 };
let rhs = self.expr_prec(next_min, do_mode);
lhs = Expr::BinOp {
op,
lhs: Box::new(lhs),
rhs: Box::new(rhs),
pos,
span: self.node_span(start_i),
};
}
lhs
}
fn unary(&mut self, do_mode: DoExpressionMode) -> Option<Expr> {
let pos = self.pos();
let start_i = self.i;
if self.at_op("-") {
self.bump();
let e = self.unary(do_mode)?;
return Some(Expr::Neg {
expr: Box::new(e),
pos,
span: self.node_span(start_i),
});
}
self.application(do_mode)
}
fn application(&mut self, do_mode: DoExpressionMode) -> Option<Expr> {
let pos = self.pos();
let start_i = self.i;
let head0 = self.atom(do_mode)?;
let mut head = self.projection_tail(head0);
let mut args = Vec::new();
loop {
if self.at_keyword("with") {
let target = args.pop().unwrap_or_else(|| {
std::mem::replace(
&mut head,
Expr::Error {
raw: String::new(),
pos,
span: Span::default(),
},
)
});
self.bump(); let fields = self.record_fields();
let tpos = target.pos();
let sp = Span::from_usize(target.span().start_usize(), self.end_byte());
let rec = Expr::Record {
base: Box::new(target),
fields,
pos: tpos,
span: sp,
};
if matches!(head, Expr::Error { ref raw, .. } if raw.is_empty()) {
head = rec;
} else {
args.push(rec);
}
continue;
}
if !do_mode.allows_do() && self.at_keyword("do") {
break;
}
if self.at_op("@") {
self.bump();
match self.peek() {
Some(TokenKind::UpperId { .. } | TokenKind::LowerId { .. }) => {
self.bump();
}
Some(TokenKind::LParen) => self.skip_balanced_parens(),
Some(TokenKind::LBracket) => {
let mut depth = 0usize;
while let Some(t) = self.peek() {
match t {
TokenKind::LBracket => depth += 1,
TokenKind::RBracket => {
if depth == 0 {
break;
}
depth -= 1;
if depth == 0 {
self.i += 1;
break;
}
}
_ => {}
}
self.i += 1;
}
}
_ => {}
}
continue;
}
match self.try_atom(do_mode) {
Some(a) => args.push(self.projection_tail(a)),
None => break,
}
}
if args.is_empty() {
Some(head)
} else {
Some(Expr::App {
func: Box::new(head),
args,
pos,
span: self.node_span(start_i),
})
}
}
fn record_fields(&mut self) -> Vec<FieldAssign> {
let mut fields = Vec::new();
let explicit = self.at(&TokenKind::LBrace);
if !(self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace)) {
return fields;
}
loop {
while self.eat(&TokenKind::VSemi)
|| self.eat(&TokenKind::Semi)
|| self.eat(&TokenKind::Comma)
{}
match self.peek() {
None => break,
Some(TokenKind::VRBrace) if !explicit => {
self.bump();
break;
}
Some(TokenKind::RBrace) => {
self.bump();
break;
}
Some(TokenKind::RParen | TokenKind::RBracket) => {
self.bump();
continue;
}
_ => {}
}
let pos = self.pos();
let start_i = self.i;
if self.at_op("..") {
self.bump();
fields.push(FieldAssign::Wildcard {
pos,
span: self.node_span(start_i),
});
continue;
}
let name = match self.peek().cloned() {
Some(TokenKind::LowerId {
qualifier: None,
name,
}) => {
self.bump();
name
}
_ => {
self.skip_to_item_end();
continue;
}
};
if self.eat_op("=") {
let value = self.expr_prec(1, DoExpressionMode::Allow);
fields.push(FieldAssign::Assign {
name,
value,
pos,
span: self.node_span(start_i),
});
} else {
fields.push(FieldAssign::Pun {
name,
pos,
span: self.node_span(start_i),
});
}
}
fields
}
fn try_atom(&mut self, do_mode: DoExpressionMode) -> Option<Expr> {
match self.peek() {
Some(TokenKind::LowerId { .. }) => {
let kw = self.peek().and_then(|t| t.keyword());
match kw {
Some("do") if do_mode.allows_do() => self.atom(do_mode),
Some(
"if" | "case" | "do" | "let" | "try" | "where" | "then" | "else" | "of"
| "in" | "controller" | "with" | "catch",
) => None,
_ => self.atom(do_mode),
}
}
Some(
TokenKind::UpperId { .. }
| TokenKind::IntLit(_)
| TokenKind::DecimalLit(_)
| TokenKind::StringLit(_)
| TokenKind::CharLit(_)
| TokenKind::LParen
| TokenKind::LBracket,
) => self.atom(do_mode),
Some(TokenKind::Op(o)) if o.as_str() == "\\" => self.atom(do_mode),
_ => None,
}
}
fn projection_tail(&mut self, mut base: Expr) -> Expr {
while self.at_tight_projection() {
let start = base.span().start_usize();
let pos = base.pos();
self.bump(); let Some(field_tok) = self.bump() else {
self.diag_expected(
ExpectedToken::ProjectionFieldAfterDot,
"expected projection field after '.'",
);
return base;
};
let TokenKind::LowerId { qualifier, name } = field_tok.kind else {
self.diag_expected(
ExpectedToken::ProjectionFieldAfterDot,
"expected projection field after '.'",
);
return base;
};
let field = Expr::Var {
qualifier,
name,
pos: field_tok.pos,
span: Span::from_usize(field_tok.start, field_tok.end),
};
base = Expr::BinOp {
op: ".".into(),
lhs: Box::new(base),
rhs: Box::new(field),
pos,
span: Span::from_usize(start, self.end_byte()),
};
}
base
}
fn at_tight_projection(&self) -> bool {
if self.i == 0 {
return false;
}
let Some(dot) = self.toks.get(self.i) else {
return false;
};
if !matches!(&dot.kind, TokenKind::Op(o) if o.as_str() == ".") {
return false;
}
let prev = &self.toks[self.i - 1];
if prev.is_virtual() || prev.end != dot.start {
return false;
}
self.toks.get(self.i + 1).is_some_and(|t| {
matches!(
&t.kind,
TokenKind::LowerId {
qualifier: None,
..
}
) && t.start == dot.end
})
}
fn atom(&mut self, do_mode: DoExpressionMode) -> Option<Expr> {
let pos = self.pos();
let start_i = self.i;
match self.peek().cloned() {
Some(TokenKind::LowerId { qualifier, name }) => {
match name.as_str() {
"if" if qualifier.is_none() => return self.if_expr(),
"case" if qualifier.is_none() => return self.case_expr(),
"do" if qualifier.is_none() => {
if !do_mode.allows_do() {
return None;
}
return self.do_expr();
}
"let" if qualifier.is_none() => return self.let_expr(),
"try" if qualifier.is_none() => return self.try_expr(),
_ => {}
}
self.bump();
Some(Expr::Var {
qualifier,
name,
pos,
span: self.node_span(start_i),
})
}
Some(TokenKind::UpperId { qualifier, name }) => {
self.bump();
let base = Expr::Con {
qualifier,
name,
pos,
span: self.node_span(start_i),
};
if self.at(&TokenKind::LBrace) {
let fields = self.record_fields();
return Some(Expr::Record {
base: Box::new(base),
fields,
pos,
span: self.node_span(start_i),
});
}
Some(base)
}
Some(TokenKind::IntLit(text)) => {
self.bump();
Some(Expr::Lit {
kind: LitKind::Int,
text,
pos,
span: self.node_span(start_i),
})
}
Some(TokenKind::DecimalLit(text)) => {
self.bump();
Some(Expr::Lit {
kind: LitKind::Decimal,
text,
pos,
span: self.node_span(start_i),
})
}
Some(TokenKind::StringLit(text)) => {
self.bump();
Some(Expr::Lit {
kind: LitKind::Text,
text,
pos,
span: self.node_span(start_i),
})
}
Some(TokenKind::CharLit(text)) => {
self.bump();
Some(Expr::Lit {
kind: LitKind::Char,
text,
pos,
span: self.node_span(start_i),
})
}
Some(TokenKind::Op(o)) if o.as_str() == "\\" => self.lambda_expr(),
Some(TokenKind::LParen) => self.paren_expr(),
Some(TokenKind::LBracket) => self.list_expr(),
_ => None,
}
}
fn if_expr(&mut self) -> Option<Expr> {
let pos = self.pos();
let start_i = self.i;
self.bump(); let cond = self.expr();
self.eat(&TokenKind::VSemi); if !self.eat_keyword("then") {
self.diag_expected(ExpectedToken::ThenKeyword, "expected 'then'");
return Some(Expr::Error {
raw: format!("if {}", cond.render()),
pos,
span: self.node_span(start_i),
});
}
let then_branch = self.expr();
self.eat(&TokenKind::VSemi);
if !self.eat_keyword("else") {
self.diag_expected(ExpectedToken::ElseKeyword, "expected 'else'");
return Some(Expr::Error {
raw: format!("if {} then {}", cond.render(), then_branch.render()),
pos,
span: self.node_span(start_i),
});
}
let else_branch = self.expr();
Some(Expr::If {
cond: Box::new(cond),
then_branch: Box::new(then_branch),
else_branch: Box::new(else_branch),
pos,
span: self.node_span(start_i),
})
}
fn case_expr(&mut self) -> Option<Expr> {
let pos = self.pos();
let start_i = self.i;
self.bump(); let scrutinee = self.expr_no_do();
if !self.eat_keyword("of") {
self.diag_expected(
ExpectedToken::OfKeywordInCaseExpression,
"expected 'of' in case expression",
);
return Some(Expr::Error {
raw: format!("case {}", scrutinee.render()),
pos,
span: self.node_span(start_i),
});
}
let mut alts = Vec::new();
if self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace) {
loop {
while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
match self.peek() {
None => break,
Some(TokenKind::VRBrace | TokenKind::RBrace) => {
self.bump();
break;
}
Some(TokenKind::RParen | TokenKind::RBracket) => {
self.bump();
continue;
}
_ => {}
}
if self.eat_keyword("where") {
let _ = self.binding_block();
continue;
}
match self.case_alt() {
Some(a) => alts.push(a),
None => self.skip_to_item_end(),
}
}
}
Some(Expr::Case {
scrutinee: Box::new(scrutinee),
alts,
pos,
span: self.node_span(start_i),
})
}
fn case_alt(&mut self) -> Option<Alt> {
let pos = self.pos();
let start_i = self.i;
let pat = self.pattern()?;
if self.at_op("|") {
let mut first: Option<Expr> = None;
while self.eat_op("|") {
loop {
let _guard = self.expr();
if self.eat_op("<-") {
let _ = self.expr();
}
if !self.eat(&TokenKind::Comma) {
break;
}
}
if !self.eat_op("->") {
self.diag_expected(
ExpectedToken::ArrowInGuardedCaseAlternative,
"expected '->' in guarded case alternative",
);
return None;
}
let body = self.expr();
if first.is_none() {
first = Some(body);
}
}
return Some(Alt {
pat,
body: first?,
pos,
span: self.node_span(start_i),
});
}
if !self.eat_op("->") {
self.diag_expected(
ExpectedToken::ArrowInCaseAlternative,
"expected '->' in case alternative",
);
return None;
}
let body = self.expr();
Some(Alt {
pat,
body,
pos,
span: self.node_span(start_i),
})
}
fn do_expr(&mut self) -> Option<Expr> {
let pos = self.pos();
let start_i = self.i;
self.bump(); let mut stmts = Vec::new();
if self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace) {
loop {
while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
match self.peek() {
None => break,
Some(TokenKind::VRBrace | TokenKind::RBrace) => {
self.bump();
break;
}
Some(TokenKind::RParen | TokenKind::RBracket) => {
self.bump();
continue;
}
_ => {}
}
stmts.push(self.do_stmt());
}
}
Some(Expr::Do {
stmts,
pos,
span: self.node_span(start_i),
})
}
fn do_stmt(&mut self) -> DoStmt {
let pos = self.pos();
let start_i = self.i;
if self.at_keyword("let") {
self.bump();
let bindings = self.binding_block();
if self.eat_keyword("in") {
let body = self.expr();
return DoStmt::Expr {
expr: Expr::LetIn {
bindings,
body: Box::new(body),
pos,
span: self.node_span(start_i),
},
pos,
span: self.node_span(start_i),
};
}
return DoStmt::Let {
bindings,
pos,
span: self.node_span(start_i),
};
}
let snapshot = self.i;
if let Some(pat) = self.try_bind_pattern() {
if self.at_op("<-") {
self.bump();
let expr = self.expr();
return DoStmt::Bind {
pat,
expr,
pos,
span: self.node_span(start_i),
};
}
}
self.i = snapshot;
let expr = self.expr();
DoStmt::Expr {
expr,
pos,
span: self.node_span(start_i),
}
}
fn try_bind_pattern(&mut self) -> Option<Pat> {
self.pattern()
}
fn let_expr(&mut self) -> Option<Expr> {
let pos = self.pos();
let start_i = self.i;
self.bump(); let bindings = self.binding_block();
if self.eat_keyword("in") {
let body = self.expr();
return Some(Expr::LetIn {
bindings,
body: Box::new(body),
pos,
span: self.node_span(start_i),
});
}
Some(Expr::LetIn {
bindings,
body: Box::new(Expr::Error {
raw: String::new(),
pos,
span: self.node_span(start_i),
}),
pos,
span: self.node_span(start_i),
})
}
fn try_expr(&mut self) -> Option<Expr> {
let pos = self.pos();
let start_i = self.i;
self.bump(); let body = self.expr();
let mut handlers = Vec::new();
self.eat(&TokenKind::VSemi);
if self.eat_keyword("catch") {
if self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace) {
loop {
while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
match self.peek() {
None => break,
Some(TokenKind::VRBrace | TokenKind::RBrace) => {
self.bump();
break;
}
Some(TokenKind::RParen | TokenKind::RBracket) => {
self.bump();
continue;
}
_ => {}
}
match self.case_alt() {
Some(a) => handlers.push(a),
None => self.skip_to_item_end(),
}
}
} else if let Some(a) = self.case_alt() {
handlers.push(a);
}
}
Some(Expr::Try {
body: Box::new(body),
handlers,
pos,
span: self.node_span(start_i),
})
}
fn lambda_expr(&mut self) -> Option<Expr> {
let pos = self.pos();
let start_i = self.i;
self.bump(); if self.eat_keyword("case") {
let mut alts = Vec::new();
if self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace) {
loop {
while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
match self.peek() {
None => break,
Some(TokenKind::VRBrace | TokenKind::RBrace) => {
self.bump();
break;
}
Some(TokenKind::RParen | TokenKind::RBracket) => {
self.bump();
continue;
}
_ => {}
}
match self.case_alt() {
Some(a) => alts.push(a),
None => self.skip_to_item_end(),
}
}
}
return Some(Expr::Lambda {
params: vec![Pat::Var {
name: "_".into(),
pos,
span: Span::from_usize(self.byte_at(start_i), self.byte_at(start_i)),
}],
body: Box::new(Expr::Case {
scrutinee: Box::new(Expr::Var {
qualifier: None,
name: "_".into(),
pos,
span: Span::from_usize(self.byte_at(start_i), self.byte_at(start_i)),
}),
alts,
pos,
span: self.node_span(start_i),
}),
pos,
span: self.node_span(start_i),
});
}
let mut params = Vec::new();
while !self.at_op("->") {
match self.pattern_atom() {
Some(p) => params.push(p),
None => {
self.diag_malformed(
MalformedSyntaxKind::LambdaParameter,
"bad lambda parameter",
);
let start = self.i;
self.skip_to_item_end();
return Some(Expr::Error {
raw: format!("\\{}", self.slice_text(start)),
pos,
span: self.node_span(start_i),
});
}
}
}
self.bump(); let body = self.expr();
Some(Expr::Lambda {
params,
body: Box::new(body),
pos,
span: self.node_span(start_i),
})
}
fn paren_expr(&mut self) -> Option<Expr> {
let pos = self.pos();
let start_i = self.i;
self.bump(); if self.eat(&TokenKind::RParen) {
return Some(Expr::Con {
qualifier: None,
name: "()".into(),
pos,
span: self.node_span(start_i),
});
}
if let Some(TokenKind::Op(o)) = self.peek().cloned() {
if !is_reserved_op(&o) && o != "\\" && o != "-" {
self.bump();
if self.eat(&TokenKind::RParen) {
return Some(Expr::OperatorRef {
op: o,
pos,
span: self.node_span(start_i),
});
}
let operand = self.expr();
self.eat(&TokenKind::RParen);
return Some(Expr::RightSection {
op: o,
operand: Box::new(operand),
pos,
span: self.node_span(start_i),
});
}
}
let first = self.expr();
if self.at(&TokenKind::Comma) {
let mut items = vec![first];
while self.eat(&TokenKind::Comma) {
items.push(self.expr());
}
self.eat(&TokenKind::RParen);
return Some(Expr::Tuple {
items,
pos,
span: self.node_span(start_i),
});
}
if let Some(TokenKind::Op(o)) = self.peek().cloned() {
if !is_reserved_op(&o) && self.peek_at(1) == Some(&TokenKind::RParen) {
self.bump();
self.bump();
return Some(Expr::LeftSection {
op: o,
operand: Box::new(first),
pos,
span: self.node_span(start_i),
});
}
}
self.eat(&TokenKind::RParen);
Some(first)
}
fn list_expr(&mut self) -> Option<Expr> {
let pos = self.pos();
let start_i = self.i;
self.bump(); let mut items = Vec::new();
if self.eat(&TokenKind::RBracket) {
return Some(Expr::List {
items,
pos,
span: self.node_span(start_i),
});
}
loop {
let e = self.expr();
if self.at_op("..") {
self.bump();
let hi = if self.at(&TokenKind::RBracket) {
Expr::Error {
raw: String::new(),
pos,
span: self.node_span(start_i),
}
} else {
self.expr()
};
self.eat(&TokenKind::RBracket);
return Some(Expr::BinOp {
op: "..".into(),
lhs: Box::new(e),
rhs: Box::new(hi),
pos,
span: self.node_span(start_i),
});
}
if self.at_op("|") {
let start = self.i;
let mut brackets = 1usize;
while let Some(t) = self.peek() {
match t {
TokenKind::LBracket => brackets += 1,
TokenKind::RBracket => {
brackets -= 1;
if brackets == 0 {
break;
}
}
TokenKind::VSemi | TokenKind::VRBrace => break,
_ => {}
}
self.i += 1;
}
let raw = self.slice_text(start);
self.eat(&TokenKind::RBracket);
return Some(Expr::App {
func: Box::new(e),
args: vec![Expr::Error {
raw,
pos,
span: self.node_span(start_i),
}],
pos,
span: self.node_span(start_i),
});
}
items.push(e);
if !self.eat(&TokenKind::Comma) {
break;
}
}
self.eat(&TokenKind::RBracket);
Some(Expr::List {
items,
pos,
span: self.node_span(start_i),
})
}
}
fn is_reserved_op(op: &str) -> bool {
matches!(op, "=" | "<-" | "->" | "|" | ":" | "=>" | "@" | "\\" | "..")
}
fn fixity(op: &str) -> (u8, bool) {
match op {
"$" | "$!" => (1, true),
">>=" | ">>" | "=<<" | "<&>" => (2, false),
"||" => (3, true),
"&&" => (4, true),
"==" | "/=" | "<" | "<=" | ">" | ">=" => (5, false),
"::" | "++" | "<>" => (6, true),
"+" | "-" => (7, false),
"*" | "/" => (8, false),
"^" | "**" => (9, true),
"." | "!!" => (10, true),
_ => (9, false),
}
}
fn equations_extent(eqs: &[Equation]) -> Option<Span> {
let mut it = eqs.iter();
let first = it.next()?;
let mut s = first.span;
for e in it {
s.start = s.start.min(e.span.start);
s.end = s.end.max(e.span.end);
}
Some(s)
}
fn merge_functions(decls: &mut Vec<Decl>) {
let mut out: Vec<Decl> = Vec::with_capacity(decls.len());
let mut function_index_by_name: HashMap<Identifier, usize> = HashMap::new();
for decl in decls.drain(..) {
match decl {
Decl::Function(f) => {
if let Some(existing_index) = function_index_by_name.get(&f.name).copied() {
let Decl::Function(g) = &mut out[existing_index] else {
out.push(Decl::Function(f));
continue;
};
if g.ty.is_absent() {
g.ty = f.ty.clone();
}
if g.sig_span.is_none() {
g.sig_span = f.sig_span;
}
if g.equations.is_empty() && !f.equations.is_empty() {
g.pos = f.pos;
}
g.equations.extend(f.equations);
g.span = equations_extent(&g.equations)
.or(g.sig_span)
.unwrap_or(g.span);
} else {
function_index_by_name.insert(f.name.clone(), out.len());
out.push(Decl::Function(f));
}
}
other => out.push(other),
}
}
*decls = out;
}
pub(crate) fn parse_type_from_tokens(tokens: &[Token]) -> Option<Type> {
let real_tokens: Vec<&Token> = tokens.iter().filter(|t| !t.is_virtual()).collect();
if real_tokens.is_empty() {
return None;
}
let mut parser = TypeTokenParser {
tokens: &real_tokens,
cursor: 0,
};
let ty = parser.parse_type()?;
if parser.cursor == real_tokens.len() {
Some(ty)
} else {
None
}
}
struct TypeTokenParser<'a> {
tokens: &'a [&'a Token],
cursor: usize,
}
#[derive(Debug)]
struct FieldBlock {
fields: Vec<FieldDecl>,
dangling: bool,
}
enum TypeAtom {
ParsedType(Type),
DroppedLiteral(Span),
}
impl<'a> TypeTokenParser<'a> {
fn peek(&self) -> Option<&'a Token> {
self.tokens.get(self.cursor).copied()
}
fn eat_op(&mut self, op: &str) -> bool {
if self.peek().is_some_and(|t| t.kind.is_op(op)) {
self.cursor += 1;
true
} else {
false
}
}
fn parse_type(&mut self) -> Option<Type> {
let lhs = if self.eat_keyword("forall") {
self.parse_forall_type()?
} else {
self.parse_application_type()?
};
if self.eat_op("=>") {
let body = self.parse_type()?;
let span = Span::from_usize(lhs.span().start_usize(), body.span().end_usize());
return Some(Type::Constrained(Box::new(body), span));
}
if self.eat_op("->") {
let rhs = self.parse_type()?;
let span = Span::from_usize(lhs.span().start_usize(), rhs.span().end_usize());
return Some(Type::Fun(Box::new(lhs), Box::new(rhs), span));
}
Some(lhs)
}
fn parse_application_type(&mut self) -> Option<Type> {
let head = match self.parse_atom()? {
TypeAtom::ParsedType(t) => t,
TypeAtom::DroppedLiteral(_) => return None,
};
let mut args = Vec::new();
let start = head.span().start_usize();
let mut end = head.span().end_usize();
loop {
if !self.is_at_type_atom_start() {
break;
}
match self.parse_atom()? {
TypeAtom::ParsedType(t) => {
end = t.span().end_usize();
args.push(t);
}
TypeAtom::DroppedLiteral(span) => {
end = span.end_usize();
}
}
}
let span = Span::from_usize(start, end);
if args.is_empty() {
Some(head.with_span(span))
} else {
Some(Type::App(Box::new(head), args, span))
}
}
fn is_at_type_atom_start(&self) -> bool {
matches!(
self.peek().map(|t| &t.kind),
Some(
TokenKind::UpperId { .. }
| TokenKind::LowerId { .. }
| TokenKind::IntLit(_)
| TokenKind::DecimalLit(_)
| TokenKind::StringLit(_)
| TokenKind::CharLit(_)
| TokenKind::LBracket
| TokenKind::LParen
)
)
}
fn parse_atom(&mut self) -> Option<TypeAtom> {
let tok = self.peek()?;
match &tok.kind {
TokenKind::UpperId { qualifier, name } => {
let con = Type::Con {
qualifier: qualifier.clone(),
name: name.clone(),
span: Span::from_usize(tok.start, tok.end),
};
self.cursor += 1;
Some(TypeAtom::ParsedType(con))
}
TokenKind::LowerId { name, .. } => {
let var = Type::Var(name.clone(), Span::from_usize(tok.start, tok.end));
self.cursor += 1;
Some(TypeAtom::ParsedType(var))
}
TokenKind::IntLit(_) | TokenKind::DecimalLit(_) => {
self.cursor += 1;
Some(TypeAtom::DroppedLiteral(Span::from_usize(
tok.start, tok.end,
)))
}
TokenKind::StringLit(text) => {
self.cursor += 1;
Some(TypeAtom::ParsedType(Type::Lit {
kind: LitKind::Text,
text: text.clone(),
span: Span::from_usize(tok.start, tok.end),
}))
}
TokenKind::CharLit(text) => {
self.cursor += 1;
Some(TypeAtom::ParsedType(Type::Lit {
kind: LitKind::Char,
text: text.clone(),
span: Span::from_usize(tok.start, tok.end),
}))
}
TokenKind::LBracket => {
let start = tok.start;
self.cursor += 1;
let inner = self.parse_type()?;
self.eat_token(&TokenKind::RBracket).map(|end| {
TypeAtom::ParsedType(Type::List(
Box::new(inner),
Span::from_usize(start, end.end),
))
})
}
TokenKind::LParen => {
let start = tok.start;
self.cursor += 1;
if let Some(op) = self.eat_token_if_operator() {
let mut name = op.as_str().to_string();
while matches!(
self.tokens.get(self.cursor).map(|t| &t.kind),
Some(TokenKind::Op(_))
) {
self.cursor += 1;
if let TokenKind::Op(o) = &self.tokens[self.cursor - 1].kind {
name.push_str(o.as_str());
}
}
if self.eat_token(&TokenKind::RParen).is_some()
&& self
.tokens
.get(self.cursor)
.is_some_and(|t| Self::is_type_atom_start(&t.kind))
{
let end = self.tokens[self.cursor - 1];
return Some(TypeAtom::ParsedType(Type::Con {
qualifier: None,
name: name.into(),
span: Span::from_usize(start, end.end),
}));
}
return None;
}
if matches!(
self.tokens.get(self.cursor).map(|t| &t.kind),
Some(TokenKind::Comma)
) {
let mut name = String::from(",");
self.cursor += 1;
while matches!(
self.tokens.get(self.cursor).map(|t| &t.kind),
Some(TokenKind::Comma)
) {
self.cursor += 1;
name.push(',');
}
if self.eat_token(&TokenKind::RParen).is_some()
&& self
.tokens
.get(self.cursor)
.is_some_and(|t| Self::is_type_atom_start(&t.kind))
{
let end = self.tokens[self.cursor - 1];
return Some(TypeAtom::ParsedType(Type::Con {
qualifier: None,
name: name.into(),
span: Span::from_usize(start, end.end),
}));
}
return None;
}
if let Some(end) = self.eat_token(&TokenKind::RParen) {
return Some(TypeAtom::ParsedType(Type::Unit(Span::from_usize(
start, end.end,
))));
}
let first = self.parse_type()?;
if self.peek().map(|t| &t.kind) == Some(&TokenKind::Comma) {
let mut items = vec![first];
while self.eat_token(&TokenKind::Comma).is_some() {
items.push(self.parse_type()?);
}
self.eat_token(&TokenKind::RParen).map(|end| {
TypeAtom::ParsedType(Type::Tuple(items, Span::from_usize(start, end.end)))
})
} else {
self.eat_token(&TokenKind::RParen).map(|end| {
TypeAtom::ParsedType(first.with_span(Span::from_usize(start, end.end)))
})
}
}
_ => None,
}
}
fn eat_keyword(&mut self, kw: &str) -> bool {
if self.peek().is_some_and(|t| t.kind.is_keyword(kw)) {
self.cursor += 1;
true
} else {
false
}
}
fn parse_forall_type(&mut self) -> Option<Type> {
let start = self
.tokens
.get(self.cursor.wrapping_sub(1))
.map(|t| t.start)
.unwrap_or_default();
while self.cursor < self.tokens.len() {
if self.peek().is_some_and(|t| t.kind.is_op(".")) {
self.cursor += 1;
let body = self.parse_type()?;
let body_span = body.span();
return Some(body.with_span(Span::from_usize(start, body_span.end_usize())));
}
self.cursor += 1;
}
None
}
fn eat_token(&mut self, tok: &TokenKind) -> Option<&'a Token> {
if self.peek().is_some_and(|t| t.kind == *tok) {
let t = self.peek();
self.cursor += 1;
t
} else {
None
}
}
fn eat_token_if_operator(&mut self) -> Option<&'a Operator> {
match self.peek() {
Some(Token {
kind: TokenKind::Op(op),
..
}) => {
self.cursor += 1;
Some(op)
}
_ => None,
}
}
const fn is_type_atom_start(kind: &TokenKind) -> bool {
matches!(
kind,
TokenKind::UpperId { .. }
| TokenKind::LowerId { .. }
| TokenKind::IntLit(_)
| TokenKind::DecimalLit(_)
| TokenKind::StringLit(_)
| TokenKind::CharLit(_)
| TokenKind::LParen
| TokenKind::LBracket
)
}
}
fn render_token_slice(tokens: &[Token]) -> String {
let mut s = String::new();
let mut prev_no_space_after = true;
for t in tokens {
let (text, no_space_before, no_space_after): (String, bool, bool) = match &t.kind {
TokenKind::LowerId { qualifier, name } | TokenKind::UpperId { qualifier, name } => (
qualifier
.as_ref()
.map_or_else(|| name.to_string(), |q| format!("{q}.{name}")),
false,
false,
),
TokenKind::Op(o) => (o.to_string(), false, false),
TokenKind::IntLit(n) | TokenKind::DecimalLit(n) => (n.clone(), false, false),
TokenKind::StringLit(v) => (format!("{v:?}"), false, false),
TokenKind::CharLit(v) => (format!("'{v}'"), false, false),
TokenKind::LParen => ("(".to_string(), false, true),
TokenKind::RParen => (")".to_string(), true, false),
TokenKind::LBracket => ("[".to_string(), false, true),
TokenKind::RBracket => ("]".to_string(), true, false),
TokenKind::LBrace => ("{".to_string(), false, true),
TokenKind::RBrace => ("}".to_string(), true, false),
TokenKind::Comma => (",".to_string(), true, false),
TokenKind::Semi | TokenKind::VSemi => (";".to_string(), true, false),
TokenKind::Backtick => ("`".to_string(), false, false),
TokenKind::VLBrace | TokenKind::VRBrace => continue,
};
if !s.is_empty() && !no_space_before && !prev_no_space_after {
s.push(' ');
}
s.push_str(&text);
prev_no_space_after = no_space_after;
}
s
}
#[cfg(test)]
mod type_tests {
use super::*;
use crate::lexer::lex;
fn ty(s: &str) -> Option<Type> {
let (toks, errs) = lex(s).into_parts();
assert!(errs.is_empty(), "lex errors for {s:?}: {errs:?}");
parse_type_from_tokens(&toks)
}
fn con(name: &str) -> Type {
Type::Con {
qualifier: None,
name: name.into(),
span: Span::default(),
}
}
fn qualified_con(qualifier: &str, name: &str) -> Type {
Type::Con {
qualifier: Some(qualifier.into()),
name: name.into(),
span: Span::default(),
}
}
fn app(head: Type, args: Vec<Type>) -> Type {
Type::App(Box::new(head), args, Span::default())
}
fn list(inner: Type) -> Type {
Type::List(Box::new(inner), Span::default())
}
fn tuple(items: Vec<Type>) -> Type {
Type::Tuple(items, Span::default())
}
fn fun(param: Type, result: Type) -> Type {
Type::Fun(Box::new(param), Box::new(result), Span::default())
}
fn var(name: &str) -> Type {
Type::Var(name.into(), Span::default())
}
fn unit() -> Type {
Type::Unit(Span::default())
}
fn constrained(body: Type) -> Type {
Type::Constrained(Box::new(body), Span::default())
}
fn text_lit(value: &str) -> Type {
Type::Lit {
kind: LitKind::Text,
text: value.to_string(),
span: Span::default(),
}
}
fn char_lit(value: &str) -> Type {
Type::Lit {
kind: LitKind::Char,
text: value.to_string(),
span: Span::default(),
}
}
#[test]
fn atoms() {
assert_eq!(ty("Party"), Some(con("Party")));
assert_eq!(ty("Decimal"), Some(con("Decimal")));
assert_eq!(ty("a"), Some(var("a")));
assert_eq!(ty("()"), Some(unit()));
}
#[test]
fn application_vs_constructor() {
assert_eq!(
ty("ContractId Foo"),
Some(app(con("ContractId"), vec![con("Foo")]))
);
assert_eq!(
ty("Optional (ContractId Foo)"),
Some(app(
con("Optional"),
vec![app(con("ContractId"), vec![con("Foo")])]
))
);
assert_eq!(
ty("Map Text Int"),
Some(app(con("Map"), vec![con("Text"), con("Int")]))
);
}
#[test]
fn qualified_constructor_keeps_qualifier() {
assert_eq!(
ty("DA.Map.Map Text Int"),
Some(app(
qualified_con("DA.Map", "Map"),
vec![con("Text"), con("Int")]
))
);
}
#[test]
fn list_and_tuple() {
assert_eq!(ty("[Text]"), Some(list(con("Text"))));
assert_eq!(
ty("(Int, Text)"),
Some(tuple(vec![con("Int"), con("Text")]))
);
assert_eq!(
ty("(a, b, c)"),
Some(tuple(vec![var("a"), var("b"), var("c")]))
);
assert_eq!(ty("(Text)"), Some(con("Text")));
}
#[test]
fn function_types_are_arrows_not_names() {
assert_eq!(ty("Int -> Int"), Some(fun(con("Int"), con("Int"))));
assert_eq!(
ty("Int -> Text -> Bool"),
Some(fun(con("Int"), fun(con("Text"), con("Bool"))))
);
assert_eq!(
ty("Party -> Script ()"),
Some(fun(con("Party"), app(con("Script"), vec![unit()])))
);
}
#[test]
fn script_application() {
assert_eq!(ty("Script ()"), Some(app(con("Script"), vec![unit()])));
}
#[test]
fn numeric_nat_literal_is_dropped() {
assert_eq!(ty("Numeric 10"), Some(con("Numeric")));
assert_eq!(ty("Numeric n"), Some(app(con("Numeric"), vec![var("n")])));
}
#[test]
fn string_and_char_type_literals_are_structured() {
assert_eq!(
ty(r#"HasField "observers""#),
Some(app(con("HasField"), vec![text_lit("observers")]))
);
assert_eq!(
ty(r#"HasField "observers" t PartiesMap"#),
Some(app(
con("HasField"),
vec![text_lit("observers"), var("t"), con("PartiesMap")]
))
);
assert_eq!(
ty(r"HasField 'x'"),
Some(app(con("HasField"), vec![char_lit("x")]))
);
}
#[test]
fn constraint_context_is_dropped_body_kept() {
assert_eq!(
ty("NumericScale n => Numeric 37 -> Numeric n"),
Some(constrained(fun(
con("Numeric"),
app(con("Numeric"), vec![var("n")])
)))
);
assert_eq!(ty("(Eq a, Show a) => a"), Some(constrained(var("a"))));
}
#[test]
fn unparseable_is_none() {
assert_eq!(ty("Int ->"), None);
assert_eq!(ty("-> Int"), None);
}
}