mirror of
https://github.com/exoticorn/curlywas.git
synced 2026-01-20 11:46:43 +01:00
1036 lines
36 KiB
Rust
1036 lines
36 KiB
Rust
use crate::ast;
|
|
use anyhow::Result;
|
|
use ariadne::{Color, Fmt, Label, Report, ReportKind};
|
|
use chumsky::prelude::*;
|
|
use std::{
|
|
fmt,
|
|
fs::File,
|
|
io::Read,
|
|
ops::Range,
|
|
path::{Path, PathBuf},
|
|
};
|
|
|
|
pub type Span = (usize, Range<usize>);
|
|
|
|
pub struct SourceFile {
|
|
source: ariadne::Source,
|
|
path: PathBuf,
|
|
}
|
|
|
|
pub struct Sources(Vec<SourceFile>);
|
|
|
|
impl Sources {
|
|
pub fn new() -> Sources {
|
|
Sources(Vec::new())
|
|
}
|
|
|
|
pub fn add(&mut self, path: &Path) -> Result<(usize, bool)> {
|
|
let canonical = path.canonicalize()?;
|
|
for (index, source) in self.0.iter().enumerate() {
|
|
if source.path.canonicalize()? == canonical {
|
|
return Ok((index, false));
|
|
}
|
|
}
|
|
let mut source = String::new();
|
|
File::open(path)?.read_to_string(&mut source)?;
|
|
self.0.push(SourceFile {
|
|
source: ariadne::Source::from(source),
|
|
path: path.to_path_buf(),
|
|
});
|
|
Ok((self.0.len() - 1, true))
|
|
}
|
|
}
|
|
|
|
impl std::ops::Index<usize> for Sources {
|
|
type Output = SourceFile;
|
|
fn index(&self, idx: usize) -> &SourceFile {
|
|
&self.0[idx]
|
|
}
|
|
}
|
|
|
|
impl ariadne::Cache<usize> for &Sources {
|
|
fn fetch(&mut self, id: &usize) -> Result<&ariadne::Source, Box<dyn std::fmt::Debug + '_>> {
|
|
Ok(&self.0[*id].source)
|
|
}
|
|
|
|
fn display<'a>(&self, id: &'a usize) -> Option<Box<dyn std::fmt::Display + 'a>> {
|
|
Some(Box::new(self.0[*id].path.clone().display().to_string()))
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
|
enum Token {
|
|
Fn,
|
|
Let,
|
|
Global,
|
|
Mut,
|
|
Loop,
|
|
Block,
|
|
Branch,
|
|
BranchIf,
|
|
Lazy,
|
|
Inline,
|
|
As,
|
|
Select,
|
|
If,
|
|
Else,
|
|
Return,
|
|
Ident(String),
|
|
Str(String),
|
|
Int(i32),
|
|
Int64(i64),
|
|
Float(String),
|
|
Float64(String),
|
|
Op(String),
|
|
Ctrl(char),
|
|
}
|
|
|
|
impl fmt::Display for Token {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
Token::Fn => write!(f, "fn"),
|
|
Token::Let => write!(f, "let"),
|
|
Token::Global => write!(f, "global"),
|
|
Token::Mut => write!(f, "mut"),
|
|
Token::Loop => write!(f, "loop"),
|
|
Token::Block => write!(f, "block"),
|
|
Token::Branch => write!(f, "branch"),
|
|
Token::BranchIf => write!(f, "branch_if"),
|
|
Token::Lazy => write!(f, "lazy"),
|
|
Token::Inline => write!(f, "inline"),
|
|
Token::As => write!(f, "as"),
|
|
Token::Select => write!(f, "select"),
|
|
Token::If => write!(f, "if"),
|
|
Token::Else => write!(f, "else"),
|
|
Token::Return => write!(f, "return"),
|
|
Token::Ident(s) => write!(f, "{}", s),
|
|
Token::Str(s) => write!(f, "{:?}", s),
|
|
Token::Int(v) => write!(f, "{}", v),
|
|
Token::Int64(v) => write!(f, "{}", v),
|
|
Token::Float(v) => write!(f, "{}", v),
|
|
Token::Float64(v) => write!(f, "{}", v),
|
|
Token::Op(s) => write!(f, "{}", s),
|
|
Token::Ctrl(c) => write!(f, "{}", c),
|
|
}
|
|
}
|
|
}
|
|
|
|
type SourceStream<It> = chumsky::Stream<'static, char, Span, It>;
|
|
type TokenStream<It> = chumsky::Stream<'static, Token, Span, It>;
|
|
|
|
pub fn parse(sources: &Sources, source_id: usize) -> Result<ast::Script, ()> {
|
|
let source = &sources[source_id].source;
|
|
let source_stream = SourceStream::from_iter(
|
|
(source_id, source.len()..source.len() + 1),
|
|
Box::new(
|
|
// this is a bit of an ugly hack
|
|
// once the next version of ariadne is released
|
|
// use the source string chars as input
|
|
// and only create the ariadne::Source lazily when printing errors
|
|
source
|
|
.lines()
|
|
.flat_map(|l| {
|
|
let mut chars: Vec<char> = l.chars().collect();
|
|
while chars.len() + 1 < l.len() {
|
|
chars.push(' ');
|
|
}
|
|
if chars.len() < l.len() {
|
|
chars.push('\n');
|
|
}
|
|
chars.into_iter()
|
|
})
|
|
.enumerate()
|
|
.map(|(index, char)| (char, (source_id, index..(index + 1)))),
|
|
),
|
|
);
|
|
|
|
let tokens = match lexer().parse(source_stream) {
|
|
Ok(tokens) => tokens,
|
|
Err(errors) => {
|
|
report_errors(
|
|
errors
|
|
.into_iter()
|
|
.map(|e| e.map(|c| c.to_string()))
|
|
.collect(),
|
|
sources,
|
|
);
|
|
return Err(());
|
|
}
|
|
};
|
|
|
|
let script = match script_parser().parse(TokenStream::from_iter(
|
|
(source_id, source.len()..source.len() + 1),
|
|
tokens.into_iter(),
|
|
)) {
|
|
Ok(script) => script,
|
|
Err(errors) => {
|
|
report_errors(
|
|
errors
|
|
.into_iter()
|
|
.map(|e| e.map(|t| t.to_string()))
|
|
.collect(),
|
|
sources,
|
|
);
|
|
return Err(());
|
|
}
|
|
};
|
|
Ok(script)
|
|
}
|
|
|
|
fn report_errors(errors: Vec<Simple<String, Span>>, sources: &Sources) {
|
|
for error in errors {
|
|
let report = Report::build(ReportKind::Error, error.span().0, error.span().1.start());
|
|
|
|
let report = match error.reason() {
|
|
chumsky::error::SimpleReason::Unclosed { span, delimiter } => report
|
|
.with_message(format!(
|
|
"Unclosed delimiter {}",
|
|
delimiter.fg(Color::Yellow)
|
|
))
|
|
.with_label(
|
|
Label::new(span.clone())
|
|
.with_message(format!(
|
|
"Unclosed delimiter {}",
|
|
delimiter.fg(Color::Yellow)
|
|
))
|
|
.with_color(Color::Yellow),
|
|
)
|
|
.with_label(
|
|
Label::new(error.span())
|
|
.with_message(format!(
|
|
"Must be closed before this {}",
|
|
error
|
|
.found()
|
|
.unwrap_or(&"end of file".to_string())
|
|
.fg(Color::Red)
|
|
))
|
|
.with_color(Color::Red),
|
|
),
|
|
chumsky::error::SimpleReason::Unexpected => report
|
|
.with_message(format!(
|
|
"{}, expected one of {}",
|
|
if error.found().is_some() {
|
|
"Unexpected token in input"
|
|
} else {
|
|
"Unexpted end of input"
|
|
},
|
|
if error.expected().len() == 0 {
|
|
"end of input".to_string()
|
|
} else {
|
|
error
|
|
.expected()
|
|
.map(|x| x.as_deref().unwrap_or("EOF"))
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
}
|
|
))
|
|
.with_label(
|
|
Label::new(error.span())
|
|
.with_message(format!(
|
|
"Unexpected token {}",
|
|
error
|
|
.found()
|
|
.unwrap_or(&"end of file".to_string())
|
|
.fg(Color::Red)
|
|
))
|
|
.with_color(Color::Red),
|
|
),
|
|
chumsky::error::SimpleReason::Custom(msg) => report.with_message(msg).with_label(
|
|
Label::new(error.span())
|
|
.with_message(format!("{}", msg.fg(Color::Red)))
|
|
.with_color(Color::Red),
|
|
),
|
|
};
|
|
|
|
report.finish().eprint(sources).unwrap();
|
|
}
|
|
}
|
|
|
|
type LexerError = Simple<char, Span>;
|
|
fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = LexerError> {
|
|
let float64 = text::int(10)
|
|
.chain::<char, _, _>(just('.').chain(text::digits(10)))
|
|
.then_ignore(just("f64"))
|
|
.collect::<String>()
|
|
.map(Token::Float64);
|
|
|
|
let float = text::int(10)
|
|
.chain::<char, _, _>(just('.').chain(text::digits(10)))
|
|
.collect::<String>()
|
|
.map(Token::Float);
|
|
|
|
let integer = just::<_, _, LexerError>("0x")
|
|
.ignore_then(text::int(16))
|
|
.try_map(|n, span| {
|
|
u64::from_str_radix(&n, 16).map_err(|err| LexerError::custom(span, err.to_string()))
|
|
})
|
|
.or(text::int(10).try_map(|n: String, span: Span| {
|
|
n.parse::<u64>()
|
|
.map_err(|err| LexerError::custom(span, err.to_string()))
|
|
}))
|
|
.boxed();
|
|
|
|
let int64 = integer
|
|
.clone()
|
|
.then_ignore(just("i64"))
|
|
.map(|n| Token::Int64(n as i64));
|
|
|
|
let int = integer.try_map(|n, span| {
|
|
u32::try_from(n)
|
|
.map(|n| Token::Int(n as i32))
|
|
.map_err(|err| LexerError::custom(span, err.to_string()))
|
|
});
|
|
|
|
let str_ = just('"')
|
|
.ignore_then(filter(|c| *c != '"').repeated())
|
|
.then_ignore(just('"'))
|
|
.collect::<String>()
|
|
.map(Token::Str);
|
|
|
|
let op = one_of("+-*/%&^|<=>#")
|
|
.repeated()
|
|
.at_least(1)
|
|
.or(just(':').chain(just('=')))
|
|
.collect::<String>()
|
|
.map(Token::Op);
|
|
|
|
let ctrl = one_of("(){};,:?!$").map(Token::Ctrl);
|
|
|
|
fn ident() -> impl Parser<char, String, Error = LexerError> + Copy + Clone {
|
|
filter(|c: &char| c.is_ascii_alphabetic() || *c == '_')
|
|
.map(Some)
|
|
.chain::<char, Vec<_>, _>(
|
|
filter(|c: &char| c.is_ascii_alphanumeric() || *c == '_' || *c == '.').repeated(),
|
|
)
|
|
.collect()
|
|
}
|
|
|
|
let ident = ident().map(|ident: String| match ident.as_str() {
|
|
"fn" => Token::Fn,
|
|
"let" => Token::Let,
|
|
"global" => Token::Global,
|
|
"mut" => Token::Mut,
|
|
"loop" => Token::Loop,
|
|
"block" => Token::Block,
|
|
"branch" => Token::Branch,
|
|
"branch_if" => Token::BranchIf,
|
|
"lazy" => Token::Lazy,
|
|
"inline" => Token::Inline,
|
|
"as" => Token::As,
|
|
"select" => Token::Select,
|
|
"if" => Token::If,
|
|
"else" => Token::Else,
|
|
"return" => Token::Return,
|
|
_ => Token::Ident(ident),
|
|
});
|
|
|
|
let single_line = just("//").then_ignore(take_until(text::newline()));
|
|
|
|
let multi_line = just("/*").then_ignore(take_until(just("*/")));
|
|
|
|
let comment = single_line.or(multi_line);
|
|
|
|
let token = float
|
|
.or(float64)
|
|
.or(int64)
|
|
.or(int)
|
|
.or(str_)
|
|
.or(op)
|
|
.or(ctrl)
|
|
.or(ident)
|
|
.recover_with(skip_then_retry_until([]));
|
|
|
|
token
|
|
.map_with_span(|tok, span| (tok, span))
|
|
.padded()
|
|
.padded_by(comment.padded().repeated())
|
|
.repeated()
|
|
}
|
|
|
|
fn map_token<O>(
|
|
f: impl Fn(&Token) -> Option<O> + 'static + Clone,
|
|
) -> impl Parser<Token, O, Error = ScriptError> + Clone {
|
|
filter_map(move |span, tok: Token| {
|
|
if let Some(output) = f(&tok) {
|
|
Ok(output)
|
|
} else {
|
|
Err(ScriptError::expected_input_found(
|
|
span,
|
|
Vec::new(),
|
|
Some(tok),
|
|
))
|
|
}
|
|
})
|
|
}
|
|
|
|
type ScriptError = Simple<Token, Span>;
|
|
fn script_parser() -> impl Parser<Token, ast::Script, Error = ScriptError> + Clone {
|
|
let identifier = filter_map(|span, tok| match tok {
|
|
Token::Ident(id) => Ok(id),
|
|
_ => Err(ScriptError::expected_input_found(
|
|
span,
|
|
Vec::new(),
|
|
Some(tok),
|
|
)),
|
|
})
|
|
.labelled("identifier");
|
|
|
|
let integer = map_token(|tok| match tok {
|
|
Token::Int(v) => Some(*v),
|
|
_ => None,
|
|
});
|
|
|
|
let string = map_token(|tok| match tok {
|
|
Token::Str(s) => Some(s.clone()),
|
|
_ => None,
|
|
});
|
|
|
|
let mut expression_out = None;
|
|
let block = recursive(|block| {
|
|
let mut block_expression = None;
|
|
let expression = recursive(|expression| {
|
|
let val = map_token(|tok| match tok {
|
|
Token::Int(v) => Some(ast::Expr::I32Const(*v)),
|
|
Token::Int64(v) => Some(ast::Expr::I64Const(*v)),
|
|
Token::Float(v) => Some(ast::Expr::F32Const(v.parse().unwrap())),
|
|
Token::Float64(v) => Some(ast::Expr::F64Const(v.parse().unwrap())),
|
|
_ => None,
|
|
})
|
|
.labelled("value");
|
|
|
|
let variable = filter_map(|span, tok| match tok {
|
|
Token::Ident(id) => Ok(ast::Expr::Variable {
|
|
name: id,
|
|
local_id: None,
|
|
}),
|
|
_ => Err(ScriptError::expected_input_found(
|
|
span,
|
|
Vec::new(),
|
|
Some(tok),
|
|
)),
|
|
})
|
|
.labelled("variable");
|
|
|
|
let local_tee = identifier
|
|
.then(just(Token::Op(":=".to_string())).ignore_then(expression.clone()))
|
|
.map(|(name, expr)| ast::Expr::LocalTee {
|
|
name,
|
|
value: Box::new(expr),
|
|
local_id: None,
|
|
})
|
|
.boxed();
|
|
|
|
let loop_expr = just(Token::Loop)
|
|
.ignore_then(identifier)
|
|
.then(block.clone())
|
|
.map(|(label, block)| ast::Expr::Loop {
|
|
label,
|
|
block: Box::new(block),
|
|
});
|
|
|
|
let label_block_expr = just(Token::Block)
|
|
.ignore_then(identifier)
|
|
.then(block.clone())
|
|
.map(|(label, block)| ast::Expr::LabelBlock {
|
|
label,
|
|
block: Box::new(block),
|
|
});
|
|
|
|
let if_expr = just(Token::If)
|
|
.ignore_then(expression.clone())
|
|
.then(block.clone())
|
|
.then(just(Token::Else).ignore_then(block.clone()).or_not())
|
|
.map(|((condition, if_true), if_false)| ast::Expr::If {
|
|
condition: Box::new(condition),
|
|
if_true: Box::new(if_true),
|
|
if_false: if_false.map(Box::new),
|
|
});
|
|
|
|
let block_expr = loop_expr.or(label_block_expr).or(if_expr).boxed();
|
|
|
|
block_expression = Some(block_expr.clone());
|
|
|
|
let branch = just(Token::Branch)
|
|
.ignore_then(identifier)
|
|
.map(ast::Expr::Branch);
|
|
|
|
let branch_if = just(Token::BranchIf)
|
|
.ignore_then(expression.clone())
|
|
.then_ignore(just(Token::Ctrl(':')))
|
|
.then(identifier)
|
|
.map(|(condition, label)| ast::Expr::BranchIf {
|
|
condition: Box::new(condition),
|
|
label,
|
|
})
|
|
.boxed();
|
|
|
|
let let_ = just(Token::Let)
|
|
.ignore_then(
|
|
(just(Token::Lazy)
|
|
.to(ast::LetType::Lazy)
|
|
.or(just(Token::Inline).to(ast::LetType::Inline)))
|
|
.or_not(),
|
|
)
|
|
.then(identifier)
|
|
.then(just(Token::Ctrl(':')).ignore_then(type_parser()).or_not())
|
|
.then(
|
|
just(Token::Op("=".to_string()))
|
|
.ignore_then(expression.clone())
|
|
.or_not(),
|
|
)
|
|
.map(|(((let_type, name), type_), value)| ast::Expr::Let {
|
|
name,
|
|
type_,
|
|
value: value.map(Box::new),
|
|
let_type: let_type.unwrap_or(ast::LetType::Normal),
|
|
local_id: None,
|
|
})
|
|
.boxed();
|
|
|
|
let assign = identifier
|
|
.then_ignore(just(Token::Op("=".to_string())))
|
|
.then(expression.clone())
|
|
.map(|(name, value)| ast::Expr::Assign {
|
|
name,
|
|
value: Box::new(value),
|
|
local_id: None,
|
|
})
|
|
.boxed();
|
|
|
|
let select = just(Token::Select)
|
|
.ignore_then(
|
|
expression
|
|
.clone()
|
|
.then_ignore(just(Token::Ctrl(',')))
|
|
.then(expression.clone())
|
|
.then_ignore(just(Token::Ctrl(',')))
|
|
.then(expression.clone())
|
|
.delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))),
|
|
)
|
|
.map(|((condition, if_true), if_false)| ast::Expr::Select {
|
|
condition: Box::new(condition),
|
|
if_true: Box::new(if_true),
|
|
if_false: Box::new(if_false),
|
|
})
|
|
.boxed();
|
|
|
|
let function_call = identifier
|
|
.then(
|
|
expression
|
|
.clone()
|
|
.separated_by(just(Token::Ctrl(',')))
|
|
.delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))),
|
|
)
|
|
.map(|(name, params)| ast::Expr::FuncCall { name, params })
|
|
.boxed();
|
|
|
|
let return_ = just(Token::Return)
|
|
.ignore_then(expression.clone().or_not())
|
|
.map(|value| ast::Expr::Return {
|
|
value: value.map(Box::new),
|
|
});
|
|
|
|
let atom = val
|
|
.or(function_call)
|
|
.or(assign)
|
|
.or(local_tee)
|
|
.or(variable)
|
|
.or(block_expr)
|
|
.or(branch)
|
|
.or(branch_if)
|
|
.or(let_)
|
|
.or(select)
|
|
.or(return_)
|
|
.map_with_span(|expr, span| expr.with_span(span))
|
|
.or(expression
|
|
.clone()
|
|
.delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))))
|
|
.or(block)
|
|
.recover_with(nested_delimiters(
|
|
Token::Ctrl('('),
|
|
Token::Ctrl(')'),
|
|
[(Token::Ctrl('{'), Token::Ctrl('}'))],
|
|
|span| ast::Expr::Error.with_span(span),
|
|
))
|
|
.boxed();
|
|
|
|
let unary_op = just(Token::Op("-".to_string()))
|
|
.to(ast::UnaryOp::Negate)
|
|
.or(just(Token::Ctrl('!')).to(ast::UnaryOp::Not))
|
|
.map_with_span(|op, span| (op, span))
|
|
.repeated()
|
|
.then(atom)
|
|
.map(|(ops, value)| {
|
|
ops.into_iter().rev().fold(value, |acc, (op, span)| {
|
|
let span = (span.0, span.1.start..acc.span.1.end);
|
|
ast::Expr::UnaryOp {
|
|
op,
|
|
value: Box::new(acc),
|
|
}
|
|
.with_span(span)
|
|
})
|
|
})
|
|
.boxed();
|
|
|
|
let op_cast = unary_op
|
|
.clone()
|
|
.then(
|
|
just(Token::As)
|
|
.ignore_then(type_parser())
|
|
.map_with_span(|type_, span| (type_, span))
|
|
.repeated(),
|
|
)
|
|
.foldl(|value, (type_, span)| {
|
|
ast::Expr::Cast {
|
|
value: Box::new(value),
|
|
type_,
|
|
}
|
|
.with_span(span)
|
|
})
|
|
.boxed();
|
|
|
|
let mem_size = just(Token::Ctrl('?'))
|
|
.to(ast::MemSize::Byte)
|
|
.or(just(Token::Ctrl('!')).to(ast::MemSize::Word))
|
|
.or(just(Token::Ctrl('$')).to(ast::MemSize::Float));
|
|
|
|
let mem_op = mem_size.then(op_cast.clone());
|
|
|
|
fn make_memory_op(
|
|
left: ast::Expression,
|
|
peek_ops: Vec<(ast::MemSize, ast::Expression)>,
|
|
poke_op: Option<((ast::MemSize, ast::Expression), ast::Expression)>,
|
|
) -> ast::Expression {
|
|
let left = peek_ops.into_iter().fold(left, |left, (size, right)| {
|
|
let span = (left.span.0, left.span.1.start..right.span.1.end);
|
|
ast::Expr::Peek(ast::MemoryLocation {
|
|
span: span.clone(),
|
|
left: Box::new(left),
|
|
size,
|
|
right: Box::new(right),
|
|
})
|
|
.with_span(span)
|
|
});
|
|
if let Some(((size, right), value)) = poke_op {
|
|
let span = (left.span.0, left.span.1.start..value.span.1.end);
|
|
ast::Expr::Poke {
|
|
mem_location: ast::MemoryLocation {
|
|
span: span.clone(),
|
|
left: Box::new(left),
|
|
size,
|
|
right: Box::new(right),
|
|
},
|
|
value: Box::new(value),
|
|
}
|
|
.with_span(span)
|
|
} else {
|
|
left
|
|
}
|
|
}
|
|
|
|
let memory_op = op_cast
|
|
.clone()
|
|
.then(
|
|
mem_op
|
|
.clone()
|
|
.repeated()
|
|
.at_least(1)
|
|
.then(
|
|
just(Token::Op("=".to_string()))
|
|
.ignore_then(expression.clone())
|
|
.or_not(),
|
|
)
|
|
.or_not(),
|
|
)
|
|
.map(|(left, ops)| {
|
|
if let Some((mut peek_ops, poke_op)) = ops {
|
|
if let Some(value) = poke_op {
|
|
let poke_op = Some((peek_ops.pop().unwrap(), value));
|
|
make_memory_op(left, peek_ops, poke_op)
|
|
} else {
|
|
make_memory_op(left, peek_ops, None)
|
|
}
|
|
} else {
|
|
left
|
|
}
|
|
})
|
|
.boxed();
|
|
|
|
let op_product = memory_op
|
|
.clone()
|
|
.then(
|
|
just(Token::Op("*".to_string()))
|
|
.to(ast::BinOp::Mul)
|
|
.or(just(Token::Op("/".to_string())).to(ast::BinOp::Div))
|
|
.or(just(Token::Op("#/".to_string())).to(ast::BinOp::DivU))
|
|
.or(just(Token::Op("%".to_string())).to(ast::BinOp::Rem))
|
|
.or(just(Token::Op("#%".to_string())).to(ast::BinOp::RemU))
|
|
.then(memory_op.clone())
|
|
.repeated(),
|
|
)
|
|
.foldl(|left, (op, right)| {
|
|
let span = (left.span.0, left.span.1.start..right.span.1.end);
|
|
ast::Expr::BinOp {
|
|
op,
|
|
left: Box::new(left),
|
|
right: Box::new(right),
|
|
}
|
|
.with_span(span)
|
|
})
|
|
.boxed();
|
|
|
|
let op_sum = op_product
|
|
.clone()
|
|
.then(
|
|
just(Token::Op("+".to_string()))
|
|
.to(ast::BinOp::Add)
|
|
.or(just(Token::Op("-".to_string())).to(ast::BinOp::Sub))
|
|
.then(op_product.clone())
|
|
.repeated(),
|
|
)
|
|
.foldl(|left, (op, right)| {
|
|
let span = (left.span.0, left.span.1.start..right.span.1.end);
|
|
ast::Expr::BinOp {
|
|
op,
|
|
left: Box::new(left),
|
|
right: Box::new(right),
|
|
}
|
|
.with_span(span)
|
|
})
|
|
.boxed();
|
|
|
|
let op_shift = op_sum
|
|
.clone()
|
|
.then(
|
|
just(Token::Op("<<".to_string()))
|
|
.to(ast::BinOp::Shl)
|
|
.or(just(Token::Op("#>>".to_string())).to(ast::BinOp::ShrU))
|
|
.or(just(Token::Op(">>".to_string())).to(ast::BinOp::ShrS))
|
|
.then(op_sum.clone())
|
|
.repeated(),
|
|
)
|
|
.foldl(|left, (op, right)| {
|
|
let span = (left.span.0, left.span.1.start..right.span.1.end);
|
|
ast::Expr::BinOp {
|
|
op,
|
|
left: Box::new(left),
|
|
right: Box::new(right),
|
|
}
|
|
.with_span(span)
|
|
})
|
|
.boxed();
|
|
|
|
let op_cmp = op_shift
|
|
.clone()
|
|
.then(
|
|
just(Token::Op("==".to_string()))
|
|
.to(ast::BinOp::Eq)
|
|
.or(just(Token::Op("!=".to_string())).to(ast::BinOp::Ne))
|
|
.or(just(Token::Op("<".to_string())).to(ast::BinOp::Lt))
|
|
.or(just(Token::Op("#<".to_string())).to(ast::BinOp::LtU))
|
|
.or(just(Token::Op("<=".to_string())).to(ast::BinOp::Le))
|
|
.or(just(Token::Op("#<=".to_string())).to(ast::BinOp::LeU))
|
|
.or(just(Token::Op(">".to_string())).to(ast::BinOp::Gt))
|
|
.or(just(Token::Op("#>".to_string())).to(ast::BinOp::GtU))
|
|
.or(just(Token::Op(">=".to_string())).to(ast::BinOp::Ge))
|
|
.or(just(Token::Op("#>=".to_string())).to(ast::BinOp::GeU))
|
|
.then(op_shift.clone())
|
|
.repeated(),
|
|
)
|
|
.foldl(|left, (op, right)| {
|
|
let span = (left.span.0, left.span.1.start..right.span.1.end);
|
|
ast::Expr::BinOp {
|
|
op,
|
|
left: Box::new(left),
|
|
right: Box::new(right),
|
|
}
|
|
.with_span(span)
|
|
})
|
|
.boxed();
|
|
|
|
let op_bit = op_cmp
|
|
.clone()
|
|
.then(
|
|
just(Token::Op("&".to_string()))
|
|
.to(ast::BinOp::And)
|
|
.or(just(Token::Op("|".to_string())).to(ast::BinOp::Or))
|
|
.or(just(Token::Op("^".to_string())).to(ast::BinOp::Xor))
|
|
.then(op_cmp.clone())
|
|
.repeated(),
|
|
)
|
|
.foldl(|left, (op, right)| {
|
|
let span = (left.span.0, left.span.1.start..right.span.1.end);
|
|
ast::Expr::BinOp {
|
|
op,
|
|
left: Box::new(left),
|
|
right: Box::new(right),
|
|
}
|
|
.with_span(span)
|
|
})
|
|
.boxed();
|
|
|
|
op_bit
|
|
.clone()
|
|
.then(
|
|
just(Token::Op("<|".to_string()))
|
|
.ignore_then(op_bit)
|
|
.repeated(),
|
|
)
|
|
.foldl(|left, right| {
|
|
let span = (left.span.0, left.span.1.start..right.span.1.end);
|
|
ast::Expr::First {
|
|
value: Box::new(left),
|
|
drop: Box::new(right),
|
|
}
|
|
.with_span(span)
|
|
})
|
|
.boxed()
|
|
});
|
|
|
|
expression_out = Some(expression.clone());
|
|
|
|
let block_expression = block_expression.unwrap();
|
|
|
|
block_expression
|
|
.clone()
|
|
.then(just(Token::Ctrl(';')).or_not())
|
|
.map_with_span(|(expr, semi), span| (expr.with_span(span), semi.is_none()))
|
|
.or(expression.clone().then(just(Token::Ctrl(';')).to(false)))
|
|
.repeated()
|
|
.then(expression.clone().or_not())
|
|
.map_with_span(|(mut statements, mut final_expression), span| {
|
|
if final_expression.is_none()
|
|
&& statements
|
|
.last()
|
|
.map(|(_, block_expr)| *block_expr)
|
|
.unwrap_or(false)
|
|
{
|
|
final_expression = statements.pop().map(|(expr, _)| expr);
|
|
}
|
|
ast::Expr::Block {
|
|
statements: statements.into_iter().map(|(expr, _)| expr).collect(),
|
|
final_expression: final_expression.map(Box::new),
|
|
}
|
|
.with_span(span)
|
|
})
|
|
.delimited_by(just(Token::Ctrl('{')), just(Token::Ctrl('}')))
|
|
.boxed()
|
|
});
|
|
|
|
let expression = expression_out.unwrap();
|
|
|
|
let top_level_item = {
|
|
let import_memory = just(Token::Ident("memory".to_string()))
|
|
.ignore_then(
|
|
integer
|
|
.clone()
|
|
.delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))),
|
|
)
|
|
.map(|min_size| ast::ImportType::Memory(min_size as u32))
|
|
.boxed();
|
|
|
|
let import_global = just(Token::Global)
|
|
.ignore_then(just(Token::Mut).or_not())
|
|
.then(identifier)
|
|
.then_ignore(just(Token::Ctrl(':')))
|
|
.then(type_parser())
|
|
.map(|((mut_opt, name), type_)| ast::ImportType::Variable {
|
|
mutable: mut_opt.is_some(),
|
|
name,
|
|
type_,
|
|
})
|
|
.boxed();
|
|
|
|
let import_function = just(Token::Fn)
|
|
.ignore_then(identifier)
|
|
.then(
|
|
type_parser()
|
|
.separated_by(just(Token::Ctrl(',')))
|
|
.delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))),
|
|
)
|
|
.then(
|
|
just(Token::Op("->".to_string()))
|
|
.ignore_then(type_parser())
|
|
.or_not(),
|
|
)
|
|
.map(|((name, params), result)| ast::ImportType::Function {
|
|
name,
|
|
params,
|
|
result,
|
|
})
|
|
.boxed();
|
|
|
|
let import = just(Token::Ident("import".to_string()))
|
|
.ignore_then(string.clone())
|
|
.then(import_memory.or(import_global).or(import_function))
|
|
.then_ignore(just(Token::Ctrl(';')))
|
|
.map_with_span(|(import, type_), span| {
|
|
ast::TopLevelItem::Import(ast::Import {
|
|
span,
|
|
import,
|
|
type_,
|
|
})
|
|
})
|
|
.boxed();
|
|
|
|
let parameter = identifier
|
|
.then_ignore(just(Token::Ctrl(':')))
|
|
.then(type_parser())
|
|
.boxed();
|
|
|
|
let function = just(Token::Ident("export".to_string()))
|
|
.or_not()
|
|
.then(just(Token::Ident("start".to_string())).or_not())
|
|
.then_ignore(just(Token::Fn))
|
|
.then(identifier)
|
|
.then(
|
|
parameter
|
|
.separated_by(just(Token::Ctrl(',')))
|
|
.delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))),
|
|
)
|
|
.then(
|
|
just(Token::Op("->".to_string()))
|
|
.ignore_then(type_parser())
|
|
.or_not(),
|
|
)
|
|
.then(block.clone())
|
|
.map_with_span(|(((((export, start), name), params), type_), body), span| {
|
|
ast::TopLevelItem::Function(ast::Function {
|
|
span,
|
|
params,
|
|
export: export.is_some(),
|
|
start: start.is_some(),
|
|
name,
|
|
type_,
|
|
body,
|
|
locals: ast::Locals::default(),
|
|
})
|
|
})
|
|
.boxed();
|
|
|
|
let global = just(Token::Global)
|
|
.ignore_then(just(Token::Mut).or_not())
|
|
.then(identifier)
|
|
.then(just(Token::Ctrl(':')).ignore_then(type_parser()).or_not())
|
|
.then(just(Token::Op("=".to_string())).ignore_then(expression.clone()))
|
|
.then_ignore(just(Token::Ctrl(';')))
|
|
.map_with_span(|(((mutable, name), type_), value), span| {
|
|
ast::TopLevelItem::GlobalVar(ast::GlobalVar {
|
|
name,
|
|
type_,
|
|
value,
|
|
mutable: mutable.is_some(),
|
|
span,
|
|
})
|
|
})
|
|
.boxed();
|
|
|
|
let global_const = just(Token::Ident("const".to_string()))
|
|
.ignore_then(identifier)
|
|
.then(just(Token::Ctrl(':')).ignore_then(type_parser()).or_not())
|
|
.then(just(Token::Op("=".to_string())).ignore_then(expression.clone()))
|
|
.then_ignore(just(Token::Ctrl(';')))
|
|
.map_with_span(|((name, type_), value), span| {
|
|
ast::TopLevelItem::Const(ast::GlobalConst {
|
|
name,
|
|
type_,
|
|
value,
|
|
span,
|
|
})
|
|
})
|
|
.boxed();
|
|
|
|
let data_i8 = just(Token::Ident("i8".to_string()))
|
|
.to(ast::DataType::I8)
|
|
.or(just(Token::Ident("i16".to_string())).to(ast::DataType::I16))
|
|
.or(just(Token::Ident("i32".to_string())).to(ast::DataType::I32))
|
|
.or(just(Token::Ident("i64".to_string())).to(ast::DataType::I64))
|
|
.or(just(Token::Ident("f32".to_string())).to(ast::DataType::F32))
|
|
.or(just(Token::Ident("f64".to_string())).to(ast::DataType::F64))
|
|
.then(
|
|
expression
|
|
.clone()
|
|
.separated_by(just(Token::Ctrl(',')))
|
|
.delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))),
|
|
)
|
|
.map(|(type_, values)| ast::DataValues::Array { type_, values });
|
|
|
|
let data_string = string.clone().map(ast::DataValues::String);
|
|
|
|
let data_file = just(Token::Ident("file".to_string()))
|
|
.ignore_then(
|
|
string
|
|
.clone()
|
|
.delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))),
|
|
)
|
|
.map(|s| ast::DataValues::File {
|
|
path: s.into(),
|
|
data: vec![],
|
|
});
|
|
|
|
let data = just(Token::Ident("data".to_string()))
|
|
.ignore_then(expression.clone())
|
|
.then(
|
|
data_i8
|
|
.or(data_string)
|
|
.or(data_file)
|
|
.repeated()
|
|
.delimited_by(just(Token::Ctrl('{')), just(Token::Ctrl('}'))),
|
|
)
|
|
.map(|(offset, data)| {
|
|
ast::TopLevelItem::Data(ast::Data {
|
|
offset: Box::new(offset),
|
|
data,
|
|
})
|
|
})
|
|
.boxed();
|
|
|
|
let include =
|
|
just(Token::Ident("include".to_string())).ignore_then(string.clone().map_with_span(
|
|
|path, span| ast::TopLevelItem::Include(ast::Include { span, path }),
|
|
));
|
|
|
|
import
|
|
.or(function)
|
|
.or(global)
|
|
.or(data)
|
|
.or(include)
|
|
.or(global_const)
|
|
.boxed()
|
|
};
|
|
|
|
top_level_item.repeated().then_ignore(end()).map(|items| {
|
|
let mut script = ast::Script::default();
|
|
for item in items {
|
|
match item {
|
|
ast::TopLevelItem::Import(i) => script.imports.push(i),
|
|
ast::TopLevelItem::GlobalVar(v) => script.global_vars.push(v),
|
|
ast::TopLevelItem::Function(f) => script.functions.push(f),
|
|
ast::TopLevelItem::Data(d) => script.data.push(d),
|
|
ast::TopLevelItem::Include(i) => script.includes.push(i),
|
|
ast::TopLevelItem::Const(c) => script.consts.push(c),
|
|
}
|
|
}
|
|
script
|
|
})
|
|
}
|
|
|
|
fn type_parser() -> impl Parser<Token, ast::Type, Error = ScriptError> + Clone {
|
|
filter_map(|span, tok| match tok {
|
|
Token::Ident(id) if id == "i32" => Ok(ast::Type::I32),
|
|
Token::Ident(id) if id == "i64" => Ok(ast::Type::I64),
|
|
Token::Ident(id) if id == "f32" => Ok(ast::Type::F32),
|
|
Token::Ident(id) if id == "f64" => Ok(ast::Type::F64),
|
|
_ => Err(ScriptError::expected_input_found(
|
|
span,
|
|
vec![
|
|
Some(Token::Ident("i32".into())),
|
|
Some(Token::Ident("i64".into())),
|
|
Some(Token::Ident("f32".into())),
|
|
Some(Token::Ident("f64".into())),
|
|
],
|
|
Some(tok),
|
|
)),
|
|
})
|
|
}
|