mirror of
https://github.com/exoticorn/curlywas.git
synced 2026-01-20 11:46:43 +01:00
initial commit, correctly parses simple example
This commit is contained in:
132
src/ast.rs
Normal file
132
src/ast.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Position(pub usize);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Script<'a> {
|
||||
pub items: Vec<TopLevelItem<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TopLevelItem<'a> {
|
||||
GlobalVar(GlobalVar<'a>),
|
||||
Function(Function<'a>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GlobalVar<'a> {
|
||||
pub position: Position,
|
||||
pub visibility: Visibility,
|
||||
pub name: &'a str,
|
||||
pub type_: Type,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Function<'a> {
|
||||
pub position: Position,
|
||||
pub visibility: Visibility,
|
||||
pub name: &'a str,
|
||||
pub params: Vec<(&'a str, Type)>,
|
||||
pub type_: Option<Type>,
|
||||
pub body: Block<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Block<'a> {
|
||||
pub statements: Vec<Statement<'a>>,
|
||||
pub final_expression: Option<Expression<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Statement<'a> {
|
||||
LocalVariable(LocalVariable<'a>),
|
||||
Poke {
|
||||
mem_location: MemoryLocation<'a>,
|
||||
value: Expression<'a>,
|
||||
},
|
||||
Expression(Expression<'a>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MemoryLocation<'a> {
|
||||
pub position: Position,
|
||||
pub size: MemSize,
|
||||
pub left: Expression<'a>,
|
||||
pub right: Expression<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LocalVariable<'a> {
|
||||
pub position: Position,
|
||||
pub name: &'a str,
|
||||
pub type_: Option<Type>,
|
||||
pub value: Option<Expression<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Expression<'a> {
|
||||
I32Const(i32),
|
||||
Variable {
|
||||
position: Position,
|
||||
name: &'a str,
|
||||
},
|
||||
Loop {
|
||||
position: Position,
|
||||
label: &'a str,
|
||||
block: Box<Block<'a>>,
|
||||
},
|
||||
BranchIf {
|
||||
position: Position,
|
||||
condition: Box<Expression<'a>>,
|
||||
label: &'a str,
|
||||
},
|
||||
BinOp {
|
||||
position: Position,
|
||||
op: BinOp,
|
||||
left: Box<Expression<'a>>,
|
||||
right: Box<Expression<'a>>,
|
||||
},
|
||||
LocalTee {
|
||||
position: Position,
|
||||
name: &'a str,
|
||||
value: Box<Expression<'a>>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BinOp {
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
Rem,
|
||||
And,
|
||||
Or,
|
||||
Xor,
|
||||
Eq,
|
||||
Ne,
|
||||
Gt,
|
||||
Ge,
|
||||
Lt,
|
||||
Le,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum MemSize {
|
||||
Byte,
|
||||
Word,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Visibility {
|
||||
Local,
|
||||
Export,
|
||||
Import,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Type {
|
||||
I32,
|
||||
I64,
|
||||
F32,
|
||||
F64,
|
||||
}
|
||||
11
src/main.rs
Normal file
11
src/main.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
mod parser;
|
||||
mod ast;
|
||||
|
||||
fn main() {
|
||||
let input = include_str!("../test.hw");
|
||||
let result = parser::parse(input);
|
||||
match result {
|
||||
Ok(script) => {dbg!(script);},
|
||||
Err(err) => println!("error: {}", nom::error::convert_error(input, err))
|
||||
}
|
||||
}
|
||||
445
src/parser.rs
Normal file
445
src/parser.rs
Normal file
@@ -0,0 +1,445 @@
|
||||
use crate::ast;
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::complete::tag,
|
||||
character::complete::{alpha1, alphanumeric1, char, digit1, multispace0},
|
||||
combinator::{self, cut, map, map_res, not, opt, peek, recognize, success, value},
|
||||
error::VerboseError,
|
||||
multi::{fold_many0, many0, separated_list0},
|
||||
sequence::{delimited, pair, preceded, separated_pair, terminated},
|
||||
Finish,
|
||||
};
|
||||
|
||||
type IResult<'a, O> = nom::IResult<&'a str, O, VerboseError<&'a str>>;
|
||||
|
||||
pub fn parse(s: &str) -> Result<ast::Script, VerboseError<&str>> {
|
||||
let (_, script) = combinator::all_consuming(terminated(script, multispace0))(s).finish()?;
|
||||
Ok(script)
|
||||
}
|
||||
|
||||
fn script(s: &str) -> IResult<ast::Script> {
|
||||
let (s, items) = many0(top_level_item)(s)?;
|
||||
Ok((s, ast::Script { items }))
|
||||
}
|
||||
|
||||
fn top_level_item(s: &str) -> IResult<ast::TopLevelItem> {
|
||||
alt((
|
||||
map(function, ast::TopLevelItem::Function),
|
||||
map(global_var, ast::TopLevelItem::GlobalVar),
|
||||
))(s)
|
||||
}
|
||||
|
||||
fn global_var(s: &str) -> IResult<ast::GlobalVar> {
|
||||
let (s, vis) = visibility(s)?;
|
||||
let (s, position) = ws(position)(s)?;
|
||||
let (s, name) = identifier(s)?;
|
||||
let (s, type_) = preceded(ws(char(':')), type_)(s)?;
|
||||
let (s, _) = ws(char(';'))(s)?;
|
||||
|
||||
Ok((
|
||||
s,
|
||||
ast::GlobalVar {
|
||||
position,
|
||||
visibility: vis,
|
||||
name: name,
|
||||
type_,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn function(s: &str) -> IResult<ast::Function> {
|
||||
let (s, vis) = visibility(s)?;
|
||||
let (s, _) = ws(tag("fn"))(s)?;
|
||||
cut(move |s| {
|
||||
let (s, position) = ws(position)(s)?;
|
||||
let (s, name) = identifier(s)?;
|
||||
let (s, params) = delimited(
|
||||
ws(char('(')),
|
||||
separated_list0(
|
||||
ws(char(',')),
|
||||
pair(map(identifier, |i| i), preceded(ws(tag(":")), type_)),
|
||||
),
|
||||
ws(char(')')),
|
||||
)(s)?;
|
||||
let (s, type_) = opt(preceded(ws(tag("->")), type_))(s)?;
|
||||
let (s, body) = block(s)?;
|
||||
|
||||
Ok((
|
||||
s,
|
||||
ast::Function {
|
||||
position,
|
||||
visibility: vis,
|
||||
name: name,
|
||||
params,
|
||||
type_,
|
||||
body,
|
||||
},
|
||||
))
|
||||
})(s)
|
||||
}
|
||||
|
||||
fn block(s: &str) -> IResult<ast::Block> {
|
||||
let (s, (statements, final_expression)) = delimited(
|
||||
ws(char('{')),
|
||||
pair(many0(statement), opt(expression)),
|
||||
ws(char('}')),
|
||||
)(s)?;
|
||||
Ok((
|
||||
s,
|
||||
ast::Block {
|
||||
statements,
|
||||
final_expression,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn statement(s: &str) -> IResult<ast::Statement> {
|
||||
alt((
|
||||
map(local_var, ast::Statement::LocalVariable),
|
||||
map(
|
||||
terminated(expression, ws(char(';'))),
|
||||
ast::Statement::Expression,
|
||||
),
|
||||
map(
|
||||
terminated(block_expression, not(peek(ws(char('}'))))),
|
||||
ast::Statement::Expression,
|
||||
),
|
||||
map(
|
||||
terminated(
|
||||
pair(mem_location, preceded(ws(char('=')), expression)),
|
||||
ws(char(';')),
|
||||
),
|
||||
|(mem_location, value)| ast::Statement::Poke {
|
||||
mem_location,
|
||||
value,
|
||||
},
|
||||
),
|
||||
))(s)
|
||||
}
|
||||
|
||||
fn local_var(s: &str) -> IResult<ast::LocalVariable> {
|
||||
let (s, _) = ws(tag("let"))(s)?;
|
||||
let (s, position) = ws(position)(s)?;
|
||||
let (s, name) = identifier(s)?;
|
||||
let (s, type_) = opt(preceded(ws(char(':')), type_))(s)?;
|
||||
let (s, value) = opt(preceded(ws(char('=')), expression))(s)?;
|
||||
let (s, _) = ws(char(';'))(s)?;
|
||||
|
||||
Ok((
|
||||
s,
|
||||
ast::LocalVariable {
|
||||
position,
|
||||
name: name,
|
||||
type_,
|
||||
value,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn mem_location(s: &str) -> IResult<ast::MemoryLocation> {
|
||||
let (s, position) = ws(position)(s)?;
|
||||
let (s, left) = expression(s)?;
|
||||
let (s, size) = map(ws(alt((char('?'), char('!')))), |op| match op {
|
||||
'?' => ast::MemSize::Byte,
|
||||
'!' => ast::MemSize::Word,
|
||||
_ => unreachable!(),
|
||||
})(s)?;
|
||||
let (s, right) = expression(s)?;
|
||||
|
||||
Ok((
|
||||
s,
|
||||
ast::MemoryLocation {
|
||||
position,
|
||||
size,
|
||||
left,
|
||||
right,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn expression(s: &str) -> IResult<ast::Expression> {
|
||||
expression_cmp(s)
|
||||
}
|
||||
|
||||
fn expression_atom(s: &str) -> IResult<ast::Expression> {
|
||||
alt((
|
||||
branch_if,
|
||||
block_expression,
|
||||
map(
|
||||
separated_pair(pair(ws(position), identifier), ws(tag(":=")), expression),
|
||||
|((position, name), value)| ast::Expression::LocalTee {
|
||||
position,
|
||||
name: name,
|
||||
value: Box::new(value),
|
||||
},
|
||||
),
|
||||
map(integer, |v| ast::Expression::I32Const(v)),
|
||||
map(ws(pair(position, identifier)), |(position, name)| {
|
||||
ast::Expression::Variable {
|
||||
position,
|
||||
name: name,
|
||||
}
|
||||
}),
|
||||
delimited(ws(char('(')), cut(expression), ws(char(')'))),
|
||||
))(s)
|
||||
}
|
||||
|
||||
fn branch_if(s: &str) -> IResult<ast::Expression> {
|
||||
let (s, position) = ws(position)(s)?;
|
||||
let (s, _) = tag("branch_if")(s)?;
|
||||
cut(move |s| {
|
||||
let (s, condition) = expression(s)?;
|
||||
let (s, _) = ws(char(':'))(s)?;
|
||||
let (s, label) = identifier(s)?;
|
||||
|
||||
Ok((
|
||||
s,
|
||||
ast::Expression::BranchIf {
|
||||
position,
|
||||
condition: Box::new(condition),
|
||||
label: label,
|
||||
},
|
||||
))
|
||||
})(s)
|
||||
}
|
||||
|
||||
fn expression_product(s: &str) -> IResult<ast::Expression> {
|
||||
let (s, mut init) = map(expression_atom, Some)(s)?;
|
||||
fold_many0(
|
||||
pair(
|
||||
ws(pair(position, alt((char('*'), char('/'), char('%'))))),
|
||||
expression_atom,
|
||||
),
|
||||
move || init.take().unwrap(),
|
||||
|left, ((position, op), right)| {
|
||||
let op = match op {
|
||||
'*' => ast::BinOp::Mul,
|
||||
'/' => ast::BinOp::Div,
|
||||
'%' => ast::BinOp::Rem,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
ast::Expression::BinOp {
|
||||
position,
|
||||
op,
|
||||
left: Box::new(left),
|
||||
right: Box::new(right),
|
||||
}
|
||||
},
|
||||
)(s)
|
||||
}
|
||||
|
||||
fn expression_sum(s: &str) -> IResult<ast::Expression> {
|
||||
let (s, mut init) = map(expression_product, Some)(s)?;
|
||||
fold_many0(
|
||||
pair(
|
||||
ws(pair(position, alt((char('+'), char('-'))))),
|
||||
expression_product,
|
||||
),
|
||||
move || init.take().unwrap(),
|
||||
|left, ((position, op), right)| {
|
||||
let op = if op == '+' {
|
||||
ast::BinOp::Add
|
||||
} else {
|
||||
ast::BinOp::Sub
|
||||
};
|
||||
ast::Expression::BinOp {
|
||||
position,
|
||||
op,
|
||||
left: Box::new(left),
|
||||
right: Box::new(right),
|
||||
}
|
||||
},
|
||||
)(s)
|
||||
}
|
||||
|
||||
fn expression_bit(s: &str) -> IResult<ast::Expression> {
|
||||
let (s, mut init) = map(expression_sum, Some)(s)?;
|
||||
fold_many0(
|
||||
pair(
|
||||
ws(pair(position, alt((char('&'), char('|'), char('^'))))),
|
||||
expression_sum,
|
||||
),
|
||||
move || init.take().unwrap(),
|
||||
|left, ((position, op), right)| {
|
||||
let op = match op {
|
||||
'&' => ast::BinOp::And,
|
||||
'|' => ast::BinOp::Or,
|
||||
'^' => ast::BinOp::Xor,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
ast::Expression::BinOp {
|
||||
position,
|
||||
op,
|
||||
left: Box::new(left),
|
||||
right: Box::new(right),
|
||||
}
|
||||
},
|
||||
)(s)
|
||||
}
|
||||
|
||||
fn expression_cmp(s: &str) -> IResult<ast::Expression> {
|
||||
let (s, mut init) = map(expression_bit, Some)(s)?;
|
||||
fold_many0(
|
||||
pair(
|
||||
ws(pair(
|
||||
position,
|
||||
alt((
|
||||
tag("=="),
|
||||
tag("!="),
|
||||
tag("<="),
|
||||
tag("<"),
|
||||
tag(">="),
|
||||
tag(">"),
|
||||
)),
|
||||
)),
|
||||
expression_bit,
|
||||
),
|
||||
move || init.take().unwrap(),
|
||||
|left, ((position, op), right)| {
|
||||
let op = match op {
|
||||
"==" => ast::BinOp::Eq,
|
||||
"!=" => ast::BinOp::Ne,
|
||||
"<=" => ast::BinOp::Le,
|
||||
"<" => ast::BinOp::Lt,
|
||||
">=" => ast::BinOp::Ge,
|
||||
">" => ast::BinOp::Gt,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
ast::Expression::BinOp {
|
||||
position,
|
||||
op,
|
||||
left: Box::new(left),
|
||||
right: Box::new(right),
|
||||
}
|
||||
},
|
||||
)(s)
|
||||
}
|
||||
|
||||
fn block_expression(s: &str) -> IResult<ast::Expression> {
|
||||
loop_(s)
|
||||
}
|
||||
|
||||
fn loop_(s: &str) -> IResult<ast::Expression> {
|
||||
let (s, position) = ws(position)(s)?;
|
||||
let (s, _) = tag("loop")(s)?;
|
||||
cut(move |s| {
|
||||
let (s, label) = identifier(s)?;
|
||||
let (s, block) = block(s)?;
|
||||
|
||||
Ok((
|
||||
s,
|
||||
ast::Expression::Loop {
|
||||
position,
|
||||
label: label,
|
||||
block: Box::new(block),
|
||||
},
|
||||
))
|
||||
})(s)
|
||||
}
|
||||
|
||||
fn integer(s: &str) -> IResult<i32> {
|
||||
ws(map_res(
|
||||
recognize(pair(opt(char('-')), digit1)),
|
||||
|n: &str| n.parse::<i32>(),
|
||||
))(s)
|
||||
}
|
||||
|
||||
fn visibility(s: &str) -> IResult<ast::Visibility> {
|
||||
ws(alt((
|
||||
value(ast::Visibility::Export, tag("export")),
|
||||
value(ast::Visibility::Import, tag("import")),
|
||||
success(ast::Visibility::Local),
|
||||
)))(s)
|
||||
}
|
||||
|
||||
fn type_(s: &str) -> IResult<ast::Type> {
|
||||
ws(alt((
|
||||
value(ast::Type::I32, tag("i32")),
|
||||
value(ast::Type::I64, tag("i64")),
|
||||
value(ast::Type::F32, tag("f32")),
|
||||
value(ast::Type::F64, tag("f64")),
|
||||
)))(s)
|
||||
}
|
||||
|
||||
fn identifier(s: &str) -> IResult<&str> {
|
||||
ws(recognize(pair(
|
||||
alt((alpha1, tag("_"))),
|
||||
many0(alt((alphanumeric1, tag("_")))),
|
||||
)))(s)
|
||||
}
|
||||
|
||||
fn position(s: &str) -> IResult<ast::Position> {
|
||||
Ok((s, ast::Position(s.len())))
|
||||
}
|
||||
|
||||
fn ws<'a, F: 'a, O>(inner: F) -> impl FnMut(&'a str) -> IResult<O>
|
||||
where
|
||||
F: FnMut(&'a str) -> IResult<'a, O>,
|
||||
{
|
||||
preceded(multispace0, inner)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use nom::combinator::all_consuming;
|
||||
|
||||
#[test]
|
||||
fn identifier() {
|
||||
all_consuming(super::identifier)("_froobaz123").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_() {
|
||||
all_consuming(super::type_)("i32").unwrap();
|
||||
all_consuming(super::type_)("i64").unwrap();
|
||||
all_consuming(super::type_)("f32").unwrap();
|
||||
all_consuming(super::type_)("f64").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn integer() {
|
||||
all_consuming(super::integer)("123").unwrap();
|
||||
all_consuming(super::integer)("-123").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_var() {
|
||||
all_consuming(super::local_var)("let foo: i32;").unwrap();
|
||||
all_consuming(super::local_var)("let bar = 42;").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function() {
|
||||
all_consuming(super::function)("export fn foo(a: i32, b: f32) -> i32 { let x = 42; x }")
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loop_() {
|
||||
all_consuming(super::loop_)("loop foo { 42 }").unwrap();
|
||||
all_consuming(super::loop_)("loop foo { i?64 = (i % 320 + time / 10) ^ (i / 320); }")
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block() {
|
||||
all_consuming(super::block)("{loop frame {}}").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expression() {
|
||||
all_consuming(super::expression)("foo + 2 * (bar ^ 23)").unwrap();
|
||||
all_consuming(super::expression)("i := i + 1").unwrap();
|
||||
all_consuming(super::expression)("(i := i + 1)").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn poke() {
|
||||
all_consuming(super::statement)("i?64 = (i % 320 + time / 10) ^ (i / 320);").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn branch_if() {
|
||||
all_consuming(super::branch_if)("branch_if (i := i + 1) < 10: foo").unwrap();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user