+syntax
This commit is contained in:
parent
80b80f4760
commit
0e3f680f89
9 changed files with 1043 additions and 3 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -441,8 +441,6 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"miette",
|
"miette",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"unicode-ident",
|
|
||||||
"unicode-normalization",
|
|
||||||
"yz-string-utils",
|
"yz-string-utils",
|
||||||
"zxtw2-literal",
|
"zxtw2-literal",
|
||||||
]
|
]
|
||||||
|
|
14
crates/zxtw2-syntax/Cargo.toml
Normal file
14
crates/zxtw2-syntax/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "zxtw2-syntax"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
miette = "7.2"
|
||||||
|
thiserror = "1.0"
|
||||||
|
zxtw2-literal.path = "../zxtw2-literal"
|
||||||
|
|
||||||
|
[dependencies.yz-string-utils]
|
||||||
|
version = "0.4"
|
||||||
|
features = ["consume-ident"]
|
168
crates/zxtw2-syntax/src/error.rs
Normal file
168
crates/zxtw2-syntax/src/error.rs
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Alain Zscheile <fogti+devel@ytrizja.de>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
use core::{fmt, result::Result as CoreResult};
|
||||||
|
use miette::Diagnostic;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::EvEqSourceSpan;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Error {
|
||||||
|
pub span: EvEqSourceSpan,
|
||||||
|
pub kind: ErrorKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
if f.alternate() {
|
||||||
|
write!(f, "{}: ", self.span)?;
|
||||||
|
}
|
||||||
|
fmt::Display::fmt(&self.kind, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {
|
||||||
|
#[inline(always)]
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
|
self.kind.source()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Result<T> = CoreResult<T, Error>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FullError<S: miette::SourceCode + 'static> {
|
||||||
|
pub span: EvEqSourceSpan,
|
||||||
|
pub kind: ErrorKind,
|
||||||
|
pub code: miette::NamedSource<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
#[inline]
|
||||||
|
pub fn with_code<S: miette::SourceCode + 'static>(
|
||||||
|
self,
|
||||||
|
code: miette::NamedSource<S>,
|
||||||
|
) -> FullError<S> {
|
||||||
|
FullError {
|
||||||
|
span: self.span,
|
||||||
|
kind: self.kind,
|
||||||
|
code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: miette::SourceCode + 'static> fmt::Display for FullError<S> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt::Display::fmt(&self.kind, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: miette::SourceCode + fmt::Debug + 'static> std::error::Error for FullError<S> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
|
self.kind.source()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: miette::SourceCode + fmt::Debug + 'static> miette::Diagnostic for FullError<S> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
|
||||||
|
self.kind.code()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn severity(&self) -> Option<miette::Severity> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
|
||||||
|
Some(&self.code)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
|
||||||
|
use miette::LabeledSpan as Lsp;
|
||||||
|
Some(Box::new(
|
||||||
|
Some(Lsp::new(None, self.span.0.offset(), self.span.0.len())).into_iter(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Diagnostic, thiserror::Error)]
|
||||||
|
pub enum ErrorKind {
|
||||||
|
#[error("expected {0}")]
|
||||||
|
#[diagnostic(code(yanais::parser::expected))]
|
||||||
|
Expected(ErrorCtx),
|
||||||
|
|
||||||
|
#[error("end of file encountered inside {0}")]
|
||||||
|
#[diagnostic(code(yanais::parser::unexpected_eof))]
|
||||||
|
UnexpectedEof(ErrorCtx),
|
||||||
|
|
||||||
|
#[error("unexpected token in context {0:?}: {1:?}")]
|
||||||
|
UnexpectedToken(ErrorCtx, crate::lex::TokenKind),
|
||||||
|
|
||||||
|
#[error("unhandled character '{0}'")]
|
||||||
|
#[diagnostic(code(yanais::lexer::unhandled_char))]
|
||||||
|
UnhandledChar(char),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
#[diagnostic(code(yanais::parser::invalid_int))]
|
||||||
|
InvalidInt(#[from] core::num::ParseIntError),
|
||||||
|
|
||||||
|
#[error("comment nesting overflowed")]
|
||||||
|
#[diagnostic(code(yanais::lexer::comment_nest_overflow))]
|
||||||
|
CommentNestOverflow,
|
||||||
|
|
||||||
|
#[error("duplicated record field identifier {0:?}")]
|
||||||
|
#[diagnostic(code(yanais::parser::record_dup_ident))]
|
||||||
|
RecordDupIdent(Arc<str>),
|
||||||
|
|
||||||
|
#[error("duplicated pattern field identifier {0:?}")]
|
||||||
|
#[diagnostic(code(yanais::parser::pattern_dup_ident))]
|
||||||
|
PatternDupIdent(Arc<str>),
|
||||||
|
|
||||||
|
#[error("unknown identifer {0:?}")]
|
||||||
|
#[diagnostic(code(yanais::parser::unknown_ident))]
|
||||||
|
UnknownIdent(Arc<str>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum ErrorCtx {
|
||||||
|
Comment,
|
||||||
|
Expression,
|
||||||
|
Lambda,
|
||||||
|
Let,
|
||||||
|
Literal,
|
||||||
|
Parentheses,
|
||||||
|
Pattern,
|
||||||
|
Record,
|
||||||
|
RefOf,
|
||||||
|
Select,
|
||||||
|
String,
|
||||||
|
Ident,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ErrorCtx {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(match self {
|
||||||
|
ErrorCtx::Comment => "comment",
|
||||||
|
ErrorCtx::Expression => "expression",
|
||||||
|
ErrorCtx::Lambda => "(ty)lambda",
|
||||||
|
ErrorCtx::Let => "let expression",
|
||||||
|
ErrorCtx::Literal => "literal",
|
||||||
|
ErrorCtx::Parentheses => "parentheses",
|
||||||
|
ErrorCtx::Pattern => "pattern",
|
||||||
|
ErrorCtx::Record => "record",
|
||||||
|
ErrorCtx::RefOf => "ref-of",
|
||||||
|
ErrorCtx::Select => "selection",
|
||||||
|
ErrorCtx::String => "string",
|
||||||
|
ErrorCtx::Ident => "identifier",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
182
crates/zxtw2-syntax/src/expr.rs
Normal file
182
crates/zxtw2-syntax/src/expr.rs
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
use crate::lex::{Token, TokenKind as Tok};
|
||||||
|
use crate::{
|
||||||
|
pat::PatternMT, record::Record, Env as ParseEnv, Error, ErrorCtx as Ectx, ErrorKind as Pek,
|
||||||
|
EvEqSourceSpan, Parse, Result, SelIdent,
|
||||||
|
};
|
||||||
|
use zxtw2_literal::Literal;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Lambda {
|
||||||
|
pub pat: PatternMT,
|
||||||
|
pub body_span: EvEqSourceSpan,
|
||||||
|
pub body: Box<Expr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Lambda {
|
||||||
|
fn parse(env: &mut ParseEnv<'_>) -> Result<Self> {
|
||||||
|
let pat = PatternMT::parse(env)?;
|
||||||
|
env.lxr.expect(Tok::RArr, Ectx::Lambda)?;
|
||||||
|
let body_start = env.lxr.offset();
|
||||||
|
let body = Box::new(Expr::parse(env)?);
|
||||||
|
let body_end = env.lxr.offset();
|
||||||
|
Ok(Self {
|
||||||
|
pat,
|
||||||
|
body_span: (body_start..body_end).into(),
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Expr {
|
||||||
|
Infer,
|
||||||
|
|
||||||
|
Literal(Literal),
|
||||||
|
|
||||||
|
Quote(usize),
|
||||||
|
Use(SelIdent),
|
||||||
|
RefUse(SelIdent),
|
||||||
|
|
||||||
|
Lambda(Lambda),
|
||||||
|
TyLambda { binds: Box<Expr>, lam: Lambda },
|
||||||
|
Apply(Box<Expr>, Vec<(EvEqSourceSpan, Expr)>),
|
||||||
|
|
||||||
|
Record(Record<Expr>),
|
||||||
|
TyRecord(Record<Expr>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// parse a "minimal expression", meaning it can be a lambda or argument for an apply expression
|
||||||
|
/// so it shouldn't "snap up" stuff like an apply expression
|
||||||
|
fn parse_minexpr(env: &mut ParseEnv<'_>) -> Result<Expr> {
|
||||||
|
let Token {
|
||||||
|
span: fi_span,
|
||||||
|
kind: fi_kind,
|
||||||
|
} = env.lxr.next_in_noeof(Ectx::Expression)?;
|
||||||
|
match fi_kind {
|
||||||
|
Tok::Ident(i) => {
|
||||||
|
if let Some(x) = env.lookup(&i) {
|
||||||
|
Ok(Expr::Use(SelIdent {
|
||||||
|
span: fi_span,
|
||||||
|
dbidx: x,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Err(Error {
|
||||||
|
span: fi_span,
|
||||||
|
kind: Pek::UnknownIdent(i),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Tok::RefOf => {
|
||||||
|
let Token { span, kind } = env.lxr.next_in_noeof(Ectx::RefOf)?;
|
||||||
|
match kind {
|
||||||
|
Tok::Ident(i) => {
|
||||||
|
if let Some(x) = env.lookup(&i) {
|
||||||
|
Ok(Expr::Use(SelIdent { span, dbidx: x }))
|
||||||
|
} else {
|
||||||
|
Err(Error {
|
||||||
|
span,
|
||||||
|
kind: Pek::UnknownIdent(i),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(Error {
|
||||||
|
span,
|
||||||
|
kind: Pek::UnexpectedToken(Ectx::RefOf, kind),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Tok::Infer => Ok(Expr::Infer),
|
||||||
|
Tok::Literal(lit) => Ok(Expr::Literal(lit)),
|
||||||
|
Tok::Lambda => Lambda::parse(env).map(Expr::Lambda),
|
||||||
|
Tok::TyLambda => {
|
||||||
|
env.lxr.expect(Tok::LParen, Ectx::Parentheses)?;
|
||||||
|
let binds = Box::new(Expr::parse(env)?);
|
||||||
|
env.lxr.expect(Tok::RParen, Ectx::Parentheses)?;
|
||||||
|
let lam = Lambda::parse(env)?;
|
||||||
|
Ok(Expr::TyLambda { binds, lam })
|
||||||
|
}
|
||||||
|
|
||||||
|
Tok::LParen => {
|
||||||
|
let inner = Expr::parse(env)?;
|
||||||
|
env.lxr.expect(Tok::RParen, Ectx::Parentheses)?;
|
||||||
|
Ok(inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => Err(Error {
|
||||||
|
span: fi_span,
|
||||||
|
kind: Pek::UnexpectedToken(Ectx::Expression, fi_kind),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
// TODO: support record destructuring when dealing with references to records...
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Expr {
|
||||||
|
fn parse(env: &mut ParseEnv<'_>) -> Result<Self> {
|
||||||
|
fn parse_inner(env: &mut ParseEnv<'_>) -> Result<Expr> {
|
||||||
|
use crate::pat::PatternTrait;
|
||||||
|
let mut letbinds = Vec::<(_, EvEqSourceSpan, _, usize)>::new();
|
||||||
|
|
||||||
|
while env.lxr.got(Tok::Let).is_some() {
|
||||||
|
let key = PatternMT::parse(env)?;
|
||||||
|
env.lxr.expect(Tok::Assign, Ectx::Let)?;
|
||||||
|
let value_start = env.lxr.offset();
|
||||||
|
let value = Expr::parse(env)?;
|
||||||
|
let value_end = env.lxr.offset();
|
||||||
|
key.push_to_penv(env);
|
||||||
|
env.lxr.expect(Tok::SemiColon, Ectx::Let)?;
|
||||||
|
let next_start = env.lxr.peek_span().offset();
|
||||||
|
letbinds.push((key, (value_start..value_end).into(), value, next_start));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut args = Vec::new();
|
||||||
|
let mut base = parse_minexpr(env)?;
|
||||||
|
let reside_knamcnt = env.names.len();
|
||||||
|
loop {
|
||||||
|
let lxrbak = env.lxr.clone();
|
||||||
|
let arg_start = env.lxr.offset();
|
||||||
|
let arg = parse_minexpr(env);
|
||||||
|
let arg_end = env.lxr.offset();
|
||||||
|
assert_eq!(env.names.len(), reside_knamcnt);
|
||||||
|
match arg {
|
||||||
|
Ok(x) => args.push(((arg_start..arg_end).into(), x)),
|
||||||
|
Err(_) => {
|
||||||
|
// do not eat errors without backtracking the lexer
|
||||||
|
env.lxr = lxrbak;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !args.is_empty() {
|
||||||
|
base = Expr::Apply(Box::new(base), args);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !letbinds.is_empty() {
|
||||||
|
// turn let bindings into lambda invocations...
|
||||||
|
let body_span_end = env.lxr.offset();
|
||||||
|
for (pat, value_span, value, next_start) in letbinds.into_iter().rev() {
|
||||||
|
base = Expr::Apply(
|
||||||
|
Box::new(Expr::Lambda(Lambda {
|
||||||
|
pat,
|
||||||
|
body_span: (next_start..body_span_end).into(),
|
||||||
|
body: Box::new(base),
|
||||||
|
})),
|
||||||
|
vec![(value_span, value)],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(base)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we don't leak names
|
||||||
|
// (the assert_eq checks serve the same purpose)
|
||||||
|
let knamcnt = env.names.len();
|
||||||
|
let res = parse_inner(env);
|
||||||
|
assert!(env.names.len() >= knamcnt);
|
||||||
|
env.names.truncate(knamcnt);
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
263
crates/zxtw2-syntax/src/lex.rs
Normal file
263
crates/zxtw2-syntax/src/lex.rs
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Alain Zscheile <fogti+devel@ytrizja.de>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
use core::fmt;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::{Error, ErrorCtx, ErrorKind, EvEqSourceSpan, Result};
|
||||||
|
pub use yz_string_utils::StrLexerBase;
|
||||||
|
use zxtw2_literal::Literal;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Lexer<'a> {
|
||||||
|
inner: StrLexerBase<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Token {
|
||||||
|
pub kind: TokenKind,
|
||||||
|
pub span: EvEqSourceSpan,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Token {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "@ {}: {:?}", self.span, self.kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum TokenKind {
|
||||||
|
Ident(Arc<str>),
|
||||||
|
PatOut(Arc<str>),
|
||||||
|
DotIdent(Arc<str>),
|
||||||
|
String(Box<str>),
|
||||||
|
Literal(Literal),
|
||||||
|
|
||||||
|
Caret,
|
||||||
|
LParen,
|
||||||
|
RParen,
|
||||||
|
LBrace,
|
||||||
|
RBrace,
|
||||||
|
LArr,
|
||||||
|
RArr,
|
||||||
|
Dot,
|
||||||
|
RefOf,
|
||||||
|
DubColon,
|
||||||
|
SemiColon,
|
||||||
|
Assign,
|
||||||
|
|
||||||
|
// keywords
|
||||||
|
Lambda,
|
||||||
|
TyLambda,
|
||||||
|
|
||||||
|
Infer,
|
||||||
|
Let,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::MaybeParse for (EvEqSourceSpan, Literal) {
|
||||||
|
const DFL_CTX: ErrorCtx = ErrorCtx::Literal;
|
||||||
|
|
||||||
|
fn maybe_parse(env: &mut crate::Env<'_>) -> Result<Option<Self>> {
|
||||||
|
let mut nxtlxr = env.lxr.clone();
|
||||||
|
let Token {
|
||||||
|
kind,
|
||||||
|
span: tok_span,
|
||||||
|
} = match nxtlxr.next().transpose()? {
|
||||||
|
None => return Ok(None),
|
||||||
|
Some(x) => x,
|
||||||
|
};
|
||||||
|
|
||||||
|
use TokenKind as Tk;
|
||||||
|
Ok(if let Tk::Literal(lit) = kind {
|
||||||
|
env.lxr = nxtlxr;
|
||||||
|
Some((tok_span, lit))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Lexer<'a> {
|
||||||
|
#[inline]
|
||||||
|
pub fn new(inp: &'a str) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: StrLexerBase { inp, offset: 0 },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn offset(&self) -> usize {
|
||||||
|
self.inner.offset
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn peek(&self) -> Option<Result<Token>> {
|
||||||
|
self.clone().next()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn peek_span(&self) -> EvEqSourceSpan {
|
||||||
|
let mut this = self.clone();
|
||||||
|
match this.next() {
|
||||||
|
Some(Ok(Token { span, .. })) => span,
|
||||||
|
Some(Err(Error { span, .. })) => span,
|
||||||
|
None => (this.inner.offset..this.inner.offset).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle EOF as error
|
||||||
|
pub fn next_in_noeof(&mut self, ctx: ErrorCtx) -> Result<Token> {
|
||||||
|
let offset = self.offset();
|
||||||
|
self.next().unwrap_or_else(|| {
|
||||||
|
Err(Error {
|
||||||
|
span: (offset..offset).into(),
|
||||||
|
kind: ErrorKind::UnexpectedEof(ctx),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// consume token if it is expected (for optional tokens)
|
||||||
|
pub fn got(&mut self, xkind: TokenKind) -> Option<EvEqSourceSpan> {
|
||||||
|
let mut nxt = self.clone();
|
||||||
|
match nxt.next() {
|
||||||
|
Some(Ok(Token { span, kind })) if xkind == kind => {
|
||||||
|
*self = nxt;
|
||||||
|
Some(span)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// like `got`, but produce a proper error message if it is not there
|
||||||
|
pub fn expect(&mut self, xkind: TokenKind, ctx: ErrorCtx) -> Result<EvEqSourceSpan> {
|
||||||
|
let mut nxt = self.clone();
|
||||||
|
let Token { span, kind } = nxt.next_in_noeof(ctx)?;
|
||||||
|
if xkind == kind {
|
||||||
|
*self = nxt;
|
||||||
|
Ok(span)
|
||||||
|
} else {
|
||||||
|
Err(Error {
|
||||||
|
span,
|
||||||
|
kind: ErrorKind::Expected(ctx),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for Lexer<'_> {
|
||||||
|
type Item = Result<Token>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Result<Token>> {
|
||||||
|
use TokenKind as Tk;
|
||||||
|
let slb = &mut self.inner;
|
||||||
|
let mut offset;
|
||||||
|
let tmp = 'lvl: loop {
|
||||||
|
// handle whitespace
|
||||||
|
slb.consume_select(|i| i.is_whitespace());
|
||||||
|
if slb.inp.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
offset = slb.offset;
|
||||||
|
break match slb.inp.chars().next()? {
|
||||||
|
'0'..='9' => {
|
||||||
|
let s = slb.consume_select(|i| i.is_ascii_digit());
|
||||||
|
debug_assert!(!s.is_empty());
|
||||||
|
s.parse()
|
||||||
|
.map(|i| TokenKind::Literal(Literal::Natural(i)))
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
'"' => {
|
||||||
|
let mut escape = false;
|
||||||
|
let mut it = slb.inp.chars().peekable();
|
||||||
|
let mut res = String::new();
|
||||||
|
loop {
|
||||||
|
let x = match it.next() {
|
||||||
|
None => break 'lvl Err(ErrorKind::UnexpectedEof(ErrorCtx::String)),
|
||||||
|
Some(x) => x,
|
||||||
|
};
|
||||||
|
slb.consume(x.len_utf8());
|
||||||
|
if escape {
|
||||||
|
escape = false;
|
||||||
|
// TODO: parse escape codes...
|
||||||
|
res.push(x);
|
||||||
|
} else {
|
||||||
|
match x {
|
||||||
|
'"' => break,
|
||||||
|
'\\' => escape = true,
|
||||||
|
_ => res.push(x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Tk::String(res.into_boxed_str()))
|
||||||
|
}
|
||||||
|
|
||||||
|
c => {
|
||||||
|
if let Some(s) = slb.try_consume_ident() {
|
||||||
|
// identifier
|
||||||
|
let s: Arc<str> = s.into();
|
||||||
|
// handle keywords
|
||||||
|
break Ok(match &*s {
|
||||||
|
"λ" => Tk::Lambda,
|
||||||
|
"Λ" => Tk::TyLambda,
|
||||||
|
"infer" => Tk::Infer,
|
||||||
|
"let" => Tk::Let,
|
||||||
|
_ => Tk::Ident(s),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
slb.consume(c.len_utf8());
|
||||||
|
Ok(match c {
|
||||||
|
'.' => Tk::DotIdent(slb.try_consume_ident().map_or_else(|| Arc::from(""), Arc::from)),
|
||||||
|
'$' => Tk::PatOut(slb.try_consume_ident().map_or_else(|| Arc::from(""), Arc::from)),
|
||||||
|
'&' => Tk::RefOf,
|
||||||
|
';' => Tk::SemiColon,
|
||||||
|
'^' => Tk::Caret,
|
||||||
|
'←' => Tk::LArr,
|
||||||
|
'→' => Tk::RArr,
|
||||||
|
'{' => Tk::LBrace,
|
||||||
|
'}' => Tk::RBrace,
|
||||||
|
'(' /* ')' */ => {
|
||||||
|
if slb.inp.starts_with('*') {
|
||||||
|
// comment
|
||||||
|
let mut lvl = 1u32;
|
||||||
|
let mut it = slb.inp.chars().peekable();
|
||||||
|
while lvl > 0 {
|
||||||
|
let c = match it.next() {
|
||||||
|
Some(c) => c,
|
||||||
|
None => break 'lvl Err(ErrorKind::UnexpectedEof(ErrorCtx::Comment)),
|
||||||
|
};
|
||||||
|
slb.consume(c.len_utf8());
|
||||||
|
let c2 = it.peek().copied();
|
||||||
|
match (c, c2) {
|
||||||
|
('(', Some('*')) => lvl = match lvl.checked_add(1) {
|
||||||
|
Some(x) => x,
|
||||||
|
None => break 'lvl Err(ErrorKind::CommentNestOverflow),
|
||||||
|
},
|
||||||
|
('*', Some(')')) => {
|
||||||
|
lvl -= 1;
|
||||||
|
it.next();
|
||||||
|
slb.consume(1);
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
Tk::LParen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* '(' */ ')' => Tk::RParen,
|
||||||
|
_ => break 'lvl Err(ErrorKind::UnhandledChar(c)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let span = (offset..slb.offset).into();
|
||||||
|
Some(
|
||||||
|
tmp.map(|kind| Token { span, kind })
|
||||||
|
.map_err(|kind| Error { span, kind }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
199
crates/zxtw2-syntax/src/lib.rs
Normal file
199
crates/zxtw2-syntax/src/lib.rs
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Alain Zscheile <fogti+devel@ytrizja.de>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
use core::{cmp, fmt};
|
||||||
|
use miette::SourceSpan;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
mod error;
|
||||||
|
pub use error::{Error, ErrorCtx, ErrorKind, FullError, Result};
|
||||||
|
|
||||||
|
pub mod expr;
|
||||||
|
pub mod lex;
|
||||||
|
pub mod pat;
|
||||||
|
pub mod record;
|
||||||
|
|
||||||
|
pub use zxtw2_literal::Literal;
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! none_up {
|
||||||
|
($x:expr) => {
|
||||||
|
match $x {
|
||||||
|
None => return Ok(None),
|
||||||
|
Some(x) => x,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A SourceSpan which is always equal to all other SourceSpans
|
||||||
|
/// (to be used to ignore span differences in expression comparisons)
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct EvEqSourceSpan(pub SourceSpan);
|
||||||
|
|
||||||
|
impl EvEqSourceSpan {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn offset(&self) -> usize {
|
||||||
|
self.0.offset()
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.0.len() == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl cmp::PartialEq for EvEqSourceSpan {
|
||||||
|
#[inline(always)]
|
||||||
|
fn eq(&self, _: &Self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for EvEqSourceSpan {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}..+{}", self.0.offset(), self.0.len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for EvEqSourceSpan {
|
||||||
|
#[inline(always)]
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
<Self as fmt::Display>::fmt(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(usize, usize)> for EvEqSourceSpan {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(x: (usize, usize)) -> Self {
|
||||||
|
Self(x.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<core::ops::Range<usize>> for EvEqSourceSpan {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(x: core::ops::Range<usize>) -> Self {
|
||||||
|
Self(x.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<EvEqSourceSpan> for SourceSpan {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(x: EvEqSourceSpan) -> SourceSpan {
|
||||||
|
x.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a EvEqSourceSpan> for SourceSpan {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(x: &'a EvEqSourceSpan) -> SourceSpan {
|
||||||
|
x.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Env<'a> {
|
||||||
|
pub lxr: lex::Lexer<'a>,
|
||||||
|
pub names: Vec<Arc<str>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Env<'a> {
|
||||||
|
pub fn new(lxr: lex::Lexer<'a>) -> Self {
|
||||||
|
Self {
|
||||||
|
lxr,
|
||||||
|
names: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lookup(&self, name: &str) -> Option<usize> {
|
||||||
|
self.names.iter().rev().enumerate().find_map(
|
||||||
|
|(n, i)| {
|
||||||
|
if &**i == name {
|
||||||
|
Some(n)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Parse: Sized {
|
||||||
|
fn parse(env: &mut Env<'_>) -> Result<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait MaybeParse: Sized {
|
||||||
|
const DFL_CTX: ErrorCtx;
|
||||||
|
/// this function allows to clearly differentiate between
|
||||||
|
/// recoverable failures and unrecoverable ones.
|
||||||
|
fn maybe_parse(env: &mut Env<'_>) -> Result<Option<Self>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: MaybeParse> Parse for T {
|
||||||
|
fn parse(env: &mut Env<'_>) -> Result<Self> {
|
||||||
|
let knamcnt = env.names.len();
|
||||||
|
let span = env.lxr.peek_span();
|
||||||
|
let mres = T::maybe_parse(env);
|
||||||
|
assert_eq!(env.names.len(), knamcnt);
|
||||||
|
mres?.ok_or_else(|| Error {
|
||||||
|
span,
|
||||||
|
kind: ErrorKind::Expected(<T as MaybeParse>::DFL_CTX),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct TaggedIdent {
|
||||||
|
pub span: EvEqSourceSpan,
|
||||||
|
pub name: Arc<str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct SelIdent {
|
||||||
|
pub span: EvEqSourceSpan,
|
||||||
|
// de-Bruijn index
|
||||||
|
pub dbidx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MaybeParse for TaggedIdent {
|
||||||
|
const DFL_CTX: ErrorCtx = ErrorCtx::Ident;
|
||||||
|
|
||||||
|
fn maybe_parse(env: &mut Env<'_>) -> Result<Option<Self>> {
|
||||||
|
let lxrbak = env.lxr.clone();
|
||||||
|
let lex::Token { span, kind } = none_up!(env.lxr.next())?;
|
||||||
|
|
||||||
|
Ok(if let lex::TokenKind::Ident(name) = kind {
|
||||||
|
Some(TaggedIdent { span, name })
|
||||||
|
} else {
|
||||||
|
env.lxr = lxrbak;
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MaybeParse for SelIdent {
|
||||||
|
const DFL_CTX: ErrorCtx = ErrorCtx::Ident;
|
||||||
|
|
||||||
|
fn maybe_parse(env: &mut Env<'_>) -> Result<Option<Self>> {
|
||||||
|
let lxrbak = env.lxr.clone();
|
||||||
|
let lex::Token { span, kind } = none_up!(env.lxr.next())?;
|
||||||
|
|
||||||
|
if let lex::TokenKind::Ident(name) = kind {
|
||||||
|
match env.lookup(&name) {
|
||||||
|
Some(dbidx) => Ok(Some(SelIdent { span, dbidx })),
|
||||||
|
None => Err(Error {
|
||||||
|
span,
|
||||||
|
kind: ErrorKind::UnknownIdent(name),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
env.lxr = lxrbak;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
148
crates/zxtw2-syntax/src/pat.rs
Normal file
148
crates/zxtw2-syntax/src/pat.rs
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Alain Zscheile <fogti+devel@ytrizja.de>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
use core::convert::Infallible;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::lex::{self, TokenKind as Tok};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
expr::Expr, none_up, record::Record, Env as ParseEnv, Error, ErrorCtx, ErrorKind as Pek,
|
||||||
|
EvEqSourceSpan, MaybeParse, Parse, TaggedIdent,
|
||||||
|
};
|
||||||
|
|
||||||
|
// infallible patterns
|
||||||
|
|
||||||
|
pub trait PatternTrait {
|
||||||
|
fn foreach_exports<'i, E, F>(&'i self, f: &mut F) -> Result<(), E>
|
||||||
|
where
|
||||||
|
F: FnMut(&'i TaggedIdent) -> Result<(), E>;
|
||||||
|
|
||||||
|
fn push_to_penv(&self, env: &mut ParseEnv<'_>) {
|
||||||
|
self.foreach_exports::<Infallible, _>(&mut |i| {
|
||||||
|
env.names.push(Arc::clone(&i.name));
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count_exports(&self) -> usize {
|
||||||
|
let mut counter = 0;
|
||||||
|
self.foreach_exports::<Infallible, _>(&mut |_| {
|
||||||
|
counter += 1;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
counter
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_from_penv(&self, env: &mut ParseEnv<'_>) {
|
||||||
|
let counter = self.count_exports();
|
||||||
|
let enl = env.names.len();
|
||||||
|
env.names.truncate(enl - counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_exports(&self, outp: &mut Vec<Arc<str>>) -> Result<(), Error> {
|
||||||
|
self.foreach_exports::<Error, _>(&mut |i| {
|
||||||
|
let i2 = Arc::clone(&i.name);
|
||||||
|
if outp.iter().any(|j| i.name == *j) {
|
||||||
|
Err(Error {
|
||||||
|
span: i.span,
|
||||||
|
kind: Pek::PatternDupIdent(i2),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
outp.push(i2);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Pattern {
|
||||||
|
Ignore(EvEqSourceSpan),
|
||||||
|
Name(TaggedIdent),
|
||||||
|
Record(Record<Pattern>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PatternTrait for Pattern {
|
||||||
|
fn foreach_exports<'i, E, F>(&'i self, f: &mut F) -> Result<(), E>
|
||||||
|
where
|
||||||
|
F: FnMut(&'i TaggedIdent) -> Result<(), E>,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Pattern::Ignore(_) => Ok(()),
|
||||||
|
Pattern::Name(i) => f(i),
|
||||||
|
Pattern::Record(xs) => xs.0[..].iter().try_for_each(|i| i.2.foreach_exports(f)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MaybeParse for Pattern {
|
||||||
|
const DFL_CTX: ErrorCtx = ErrorCtx::Pattern;
|
||||||
|
|
||||||
|
fn maybe_parse(env: &mut ParseEnv<'_>) -> Result<Option<Self>, Error> {
|
||||||
|
let mut nxtlxr = env.lxr.clone();
|
||||||
|
let lex::Token {
|
||||||
|
kind,
|
||||||
|
span: tok_span,
|
||||||
|
} = match nxtlxr.next().transpose()? {
|
||||||
|
None => return Ok(None),
|
||||||
|
Some(x) => x,
|
||||||
|
};
|
||||||
|
|
||||||
|
use lex::TokenKind as Tk;
|
||||||
|
let ret = match kind {
|
||||||
|
Tk::PatOut(nam) if nam.is_empty() => Pattern::Ignore(tok_span),
|
||||||
|
Tk::PatOut(name) => Pattern::Name(TaggedIdent {
|
||||||
|
span: tok_span,
|
||||||
|
name,
|
||||||
|
}),
|
||||||
|
Tk::LBrace => return Record::parse(env).map(|i| Some(Pattern::Record(i))),
|
||||||
|
_ => return Ok(None),
|
||||||
|
};
|
||||||
|
env.lxr = nxtlxr;
|
||||||
|
Ok(Some(ret))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct PatternMT {
|
||||||
|
pub pat: Pattern,
|
||||||
|
pub pty: Option<Box<(EvEqSourceSpan, Expr)>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PatternTrait for PatternMT {
|
||||||
|
#[inline(always)]
|
||||||
|
fn foreach_exports<'i, E, F>(&'i self, f: &mut F) -> Result<(), E>
|
||||||
|
where
|
||||||
|
F: FnMut(&'i TaggedIdent) -> Result<(), E>,
|
||||||
|
{
|
||||||
|
self.pat.foreach_exports(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MaybeParse for PatternMT {
|
||||||
|
const DFL_CTX: ErrorCtx = ErrorCtx::Pattern;
|
||||||
|
|
||||||
|
fn maybe_parse(env: &mut ParseEnv<'_>) -> Result<Option<Self>, Error> {
|
||||||
|
let pat = none_up!(Pattern::maybe_parse(env)?);
|
||||||
|
|
||||||
|
// check for name collisions
|
||||||
|
pat.extract_exports(&mut Vec::new())?;
|
||||||
|
|
||||||
|
let pty = if env.lxr.got(Tok::DubColon).is_some() {
|
||||||
|
let span_start = env.lxr.offset();
|
||||||
|
let ptyx = Expr::parse(env)?;
|
||||||
|
let span_end = env.lxr.offset();
|
||||||
|
Some(Box::new(((span_start..span_end).into(), ptyx)))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(PatternMT { pat, pty }))
|
||||||
|
}
|
||||||
|
}
|
68
crates/zxtw2-syntax/src/record.rs
Normal file
68
crates/zxtw2-syntax/src/record.rs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Alain Zscheile <fogti+devel@ytrizja.de>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
lex::{Token, TokenKind as Tok},
|
||||||
|
none_up, Env as ParseEnv, Error as Perr, ErrorCtx as PeCtx, ErrorKind as Pek, EvEqSourceSpan,
|
||||||
|
MaybeParse, Parse, Result as Pres,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Record<V>(pub Vec<(EvEqSourceSpan, Option<Arc<str>>, V)>);
|
||||||
|
|
||||||
|
impl<V: Parse> MaybeParse for Record<V> {
|
||||||
|
const DFL_CTX: PeCtx = PeCtx::Record;
|
||||||
|
|
||||||
|
fn maybe_parse(env: &mut ParseEnv<'_>) -> Pres<Option<Self>> {
|
||||||
|
none_up!(env.lxr.got(Tok::LBrace));
|
||||||
|
let mut fields: Vec<(_, Option<Arc<str>>, _)> = Vec::new();
|
||||||
|
|
||||||
|
// warning: this code executes in O(|fields|²)
|
||||||
|
loop {
|
||||||
|
let span_start;
|
||||||
|
let name = {
|
||||||
|
let mut lxrnxt = env.lxr.clone();
|
||||||
|
let Token { kind, span } = match lxrnxt.next_in_noeof(PeCtx::Record) {
|
||||||
|
Err(e) => {
|
||||||
|
env.lxr = lxrnxt;
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
Ok(x) => x,
|
||||||
|
};
|
||||||
|
span_start = span.offset();
|
||||||
|
|
||||||
|
match kind {
|
||||||
|
Tok::DotIdent(i) => {
|
||||||
|
if env.lxr.expect(Tok::Assign, PeCtx::Record).is_ok() {
|
||||||
|
if fields.iter().any(|(_, j, _)| j.as_ref() == Some(&i)) {
|
||||||
|
return Err(Perr {
|
||||||
|
span,
|
||||||
|
kind: Pek::RecordDupIdent(i),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// "fronttrack"
|
||||||
|
env.lxr = lxrnxt;
|
||||||
|
Some(i)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Tok::RBrace => break,
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let expr = V::parse(env)?;
|
||||||
|
let span_end = env.lxr.offset();
|
||||||
|
env.lxr.expect(Tok::SemiColon, PeCtx::Record)?;
|
||||||
|
fields.push(((span_start..span_end).into(), name, expr));
|
||||||
|
}
|
||||||
|
|
||||||
|
env.lxr.expect(Tok::RBrace, PeCtx::Record)?;
|
||||||
|
Ok(Some(Record(fields)))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
let option = import "./option.xtw2";
|
let option = import "./option.xtw2";
|
||||||
λ $inner : Type → μ $stack → .{
|
λ $lay : Layout → λ $inner : lay → μ $stack → .{
|
||||||
t = ^{
|
t = ^{
|
||||||
peek : $inner;
|
peek : $inner;
|
||||||
pop : option stack.t;
|
pop : option stack.t;
|
||||||
|
|
Loading…
Reference in a new issue