synplexum/src/parser.rs

816 lines
28 KiB
Rust

use crate::l1_ir::*;
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub struct ParseError {
pub message: String,
pub line: usize,
pub col: usize,
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "parse error at {}:{}: {}", self.line, self.col, self.message)
}
}
impl std::error::Error for ParseError {}
struct Lexer<'a> {
input: &'a str,
pos: usize,
line: usize,
col: usize,
}
#[derive(Debug, Clone, PartialEq)]
enum Token {
Ident(String),
Number(u64),
LBrace,
RBrace,
LParen,
RParen,
LBracket,
RBracket,
Colon,
Comma,
Eq,
Plus,
Minus,
Star,
Arrow, // ->
Dot,
Eof,
}
impl fmt::Display for Token {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Token::Ident(s) => write!(f, "'{}'", s),
Token::Number(n) => write!(f, "{}", n),
Token::LBrace => write!(f, "'{{'"),
Token::RBrace => write!(f, "'}}'"),
Token::LParen => write!(f, "'('"),
Token::RParen => write!(f, "')'"),
Token::LBracket => write!(f, "'['"),
Token::RBracket => write!(f, "']'"),
Token::Colon => write!(f, "':'"),
Token::Comma => write!(f, "','"),
Token::Eq => write!(f, "'='"),
Token::Plus => write!(f, "'+'"),
Token::Minus => write!(f, "'-'"),
Token::Star => write!(f, "'*'"),
Token::Arrow => write!(f, "'->'"),
Token::Dot => write!(f, "'.'"),
Token::Eof => write!(f, "EOF"),
}
}
}
impl<'a> Lexer<'a> {
fn new(input: &'a str) -> Self {
Lexer { input, pos: 0, line: 1, col: 1 }
}
fn skip_whitespace_and_comments(&mut self) {
let bytes = self.input.as_bytes();
while self.pos < bytes.len() {
if bytes[self.pos] == b'\n' {
self.pos += 1;
self.line += 1;
self.col = 1;
} else if bytes[self.pos].is_ascii_whitespace() {
self.pos += 1;
self.col += 1;
} else if self.pos + 1 < bytes.len() && bytes[self.pos] == b'/' && bytes[self.pos + 1] == b'/' {
// Line comment
while self.pos < bytes.len() && bytes[self.pos] != b'\n' {
self.pos += 1;
}
} else {
break;
}
}
}
fn error(&self, msg: &str) -> ParseError {
ParseError { message: msg.to_string(), line: self.line, col: self.col }
}
fn next_token(&mut self) -> Result<Token, ParseError> {
self.skip_whitespace_and_comments();
let bytes = self.input.as_bytes();
if self.pos >= bytes.len() {
return Ok(Token::Eof);
}
let ch = bytes[self.pos];
// Identifiers and keywords
if ch.is_ascii_alphabetic() || ch == b'_' {
let start = self.pos;
while self.pos < bytes.len() && (bytes[self.pos].is_ascii_alphanumeric() || bytes[self.pos] == b'_') {
self.pos += 1;
self.col += 1;
}
let word = &self.input[start..self.pos];
return Ok(Token::Ident(word.to_string()));
}
// Numbers
if ch.is_ascii_digit() {
let start = self.pos;
while self.pos < bytes.len() && bytes[self.pos].is_ascii_digit() {
self.pos += 1;
self.col += 1;
}
let num_str = &self.input[start..self.pos];
let value = num_str.parse::<u64>()
.map_err(|_| self.error(&format!("invalid number: {}", num_str)))?;
return Ok(Token::Number(value));
}
self.pos += 1;
self.col += 1;
match ch {
b'{' => Ok(Token::LBrace),
b'}' => Ok(Token::RBrace),
b'(' => Ok(Token::LParen),
b')' => Ok(Token::RParen),
b'[' => Ok(Token::LBracket),
b']' => Ok(Token::RBracket),
b':' => Ok(Token::Colon),
b',' => Ok(Token::Comma),
b'+' => Ok(Token::Plus),
b'*' => Ok(Token::Star),
b'.' => Ok(Token::Dot),
b'=' => Ok(Token::Eq),
b'-' => {
if self.pos < bytes.len() && bytes[self.pos] == b'>' {
self.pos += 1;
self.col += 1;
Ok(Token::Arrow)
} else {
Ok(Token::Minus)
}
}
_ => Err(self.error(&format!("unexpected character: '{}'", ch as char))),
}
}
fn peek_token(&mut self) -> Result<Token, ParseError> {
let saved_pos = self.pos;
let saved_line = self.line;
let saved_col = self.col;
let tok = self.next_token()?;
self.pos = saved_pos;
self.line = saved_line;
self.col = saved_col;
Ok(tok)
}
}
pub struct Parser<'a> {
lexer: Lexer<'a>,
}
impl<'a> Parser<'a> {
pub fn new(input: &'a str) -> Self {
Parser { lexer: Lexer::new(input) }
}
fn error(&self, msg: &str) -> ParseError {
self.lexer.error(msg)
}
fn expect_ident(&mut self) -> Result<String, ParseError> {
match self.lexer.next_token()? {
Token::Ident(s) => Ok(s),
other => Err(self.error(&format!("expected identifier, got {}", other))),
}
}
fn expect_token(&mut self, expected: &Token) -> Result<(), ParseError> {
let tok = self.lexer.next_token()?;
if std::mem::discriminant(&tok) == std::mem::discriminant(expected) {
Ok(())
} else {
Err(self.error(&format!("expected {}, got {}", expected, tok)))
}
}
fn expect_number(&mut self) -> Result<u64, ParseError> {
match self.lexer.next_token()? {
Token::Number(n) => Ok(n),
other => Err(self.error(&format!("expected number, got {}", other))),
}
}
fn parse_field_type(&mut self) -> Result<FieldType, ParseError> {
let name = self.expect_ident()?;
match name.as_str() {
"u64" => Ok(FieldType::U64),
"i64" => Ok(FieldType::I64),
"f64" => Ok(FieldType::F64),
_ => Err(self.error(&format!("unknown type: {}", name))),
}
}
fn parse_state_field(&mut self) -> Result<StateField, ParseError> {
let name = self.expect_ident()?;
self.expect_token(&Token::Colon)?;
let ty = self.parse_field_type()?;
self.expect_token(&Token::Eq)?;
let init = self.expect_number()?;
Ok(StateField { name, ty, init })
}
fn parse_state_block(&mut self) -> Result<Vec<StateField>, ParseError> {
// "state" keyword already consumed
self.expect_token(&Token::LBrace)?;
let mut fields = Vec::new();
loop {
match self.lexer.peek_token()? {
Token::RBrace => {
self.lexer.next_token()?;
break;
}
_ => fields.push(self.parse_state_field()?),
}
}
Ok(fields)
}
fn parse_window_decl(&mut self) -> Result<WindowDecl, ParseError> {
// "window" keyword already consumed
let name = self.expect_ident()?;
self.expect_token(&Token::Colon)?;
self.expect_token(&Token::LParen)?;
let mut fields = Vec::new();
loop {
match self.lexer.peek_token()? {
Token::RParen => {
self.lexer.next_token()?;
break;
}
Token::Comma => {
self.lexer.next_token()?;
}
_ => fields.push(self.expect_ident()?),
}
}
// readers(...)
let readers_kw = self.expect_ident()?;
if readers_kw != "readers" {
return Err(self.error(&format!("expected 'readers', got '{}'", readers_kw)));
}
self.expect_token(&Token::LParen)?;
let mut readers = Vec::new();
loop {
match self.lexer.peek_token()? {
Token::RParen => {
self.lexer.next_token()?;
break;
}
Token::Comma => {
self.lexer.next_token()?;
}
_ => readers.push(self.expect_ident()?),
}
}
Ok(WindowDecl { name, fields, readers })
}
fn parse_expr(&mut self) -> Result<Expr, ParseError> {
let lhs = self.parse_expr_atom()?;
// Check for assignment: ident = expr
// or arithmetic: expr op expr
match self.lexer.peek_token()? {
Token::Eq => {
// Assignment
if let Expr::Var(name) = lhs {
self.lexer.next_token()?;
let rhs = self.parse_expr()?;
Ok(Expr::Assign(name, Box::new(rhs)))
} else {
Err(self.error("left side of assignment must be an identifier"))
}
}
Token::Plus | Token::Minus | Token::Star => {
let op_tok = self.lexer.next_token()?;
let op = match op_tok {
Token::Plus => ArithOp::Add,
Token::Minus => ArithOp::Sub,
Token::Star => ArithOp::Mul,
_ => unreachable!(),
};
let rhs = self.parse_expr_atom()?;
Ok(Expr::Arith(op, Box::new(lhs), Box::new(rhs)))
}
_ => Ok(lhs),
}
}
fn parse_expr_atom(&mut self) -> Result<Expr, ParseError> {
match self.lexer.peek_token()? {
Token::Number(_) => {
let n = self.expect_number()?;
Ok(Expr::Lit(n))
}
Token::Ident(_) => {
let name = self.expect_ident()?;
Ok(Expr::Var(name))
}
Token::LParen => {
self.lexer.next_token()?;
let expr = self.parse_expr()?;
self.expect_token(&Token::RParen)?;
Ok(expr)
}
other => Err(self.error(&format!("expected expression, got {}", other))),
}
}
fn parse_handler(&mut self) -> Result<MessageHandler, ParseError> {
// "on" keyword already consumed
let message_type = self.expect_ident()?;
self.expect_token(&Token::LParen)?;
let mut args = Vec::new();
loop {
match self.lexer.peek_token()? {
Token::RParen => {
self.lexer.next_token()?;
break;
}
Token::Comma => {
self.lexer.next_token()?;
}
_ => {
let arg_name = self.expect_ident()?;
self.expect_token(&Token::Colon)?;
let arg_ty = self.parse_field_type()?;
args.push((arg_name, arg_ty));
}
}
}
self.expect_token(&Token::LBrace)?;
let mut exprs = Vec::new();
loop {
match self.lexer.peek_token()? {
Token::RBrace => {
self.lexer.next_token()?;
break;
}
_ => exprs.push(self.parse_expr()?),
}
}
let body = if exprs.len() == 1 {
exprs.into_iter().next().unwrap()
} else {
Expr::Block(exprs)
};
Ok(MessageHandler { message_type, args, body })
}
fn parse_actor(&mut self) -> Result<ActorDecl, ParseError> {
// "actor" keyword already consumed
let name = self.expect_ident()?;
self.expect_token(&Token::LBrace)?;
let mut state = Vec::new();
let mut windows = Vec::new();
let mut handlers = Vec::new();
loop {
match self.lexer.peek_token()? {
Token::RBrace => {
self.lexer.next_token()?;
break;
}
Token::Ident(ref kw) => {
let kw = kw.clone();
match kw.as_str() {
"state" => {
self.lexer.next_token()?;
state = self.parse_state_block()?;
}
"window" => {
self.lexer.next_token()?;
windows.push(self.parse_window_decl()?);
}
"on" => {
self.lexer.next_token()?;
handlers.push(self.parse_handler()?);
}
_ => return Err(self.error(&format!(
"unexpected keyword '{}' in actor body", kw
))),
}
}
other => return Err(self.error(&format!(
"unexpected token {} in actor body", other
))),
}
}
Ok(ActorDecl { name, state, windows, handlers })
}
fn parse_leaf_actions(&mut self) -> Result<Vec<LeafAction>, ParseError> {
// "process" keyword already consumed
self.expect_token(&Token::LBrace)?;
let mut actions = Vec::new();
loop {
match self.lexer.peek_token()? {
Token::RBrace => {
self.lexer.next_token()?;
break;
}
Token::Ident(ref kw) => {
let kw = kw.clone();
match kw.as_str() {
"forward" => {
self.lexer.next_token()?;
self.expect_token(&Token::LParen)?;
let target = self.expect_ident()?;
self.expect_token(&Token::Comma)?;
let msg_type = self.expect_ident()?;
self.expect_token(&Token::LParen)?;
let mut args = Vec::new();
loop {
match self.lexer.peek_token()? {
Token::RParen => {
self.lexer.next_token()?;
break;
}
Token::Comma => {
self.lexer.next_token()?;
}
_ => args.push(self.parse_expr()?),
}
}
self.expect_token(&Token::RParen)?; // outer close
actions.push(LeafAction::Forward {
target,
message_type: msg_type,
args,
});
}
"read" => {
self.lexer.next_token()?;
self.expect_token(&Token::LParen)?;
let actor = self.expect_ident()?;
self.expect_token(&Token::Dot)?;
let window = self.expect_ident()?;
self.expect_token(&Token::Dot)?;
let field = self.expect_ident()?;
self.expect_token(&Token::RParen)?;
actions.push(LeafAction::ReadWindow {
actor,
window,
field,
});
}
"emit" => {
self.lexer.next_token()?;
self.expect_token(&Token::LParen)?;
let expr = self.parse_expr()?;
self.expect_token(&Token::RParen)?;
actions.push(LeafAction::Emit(expr));
}
_ => return Err(self.error(&format!(
"unexpected action '{}' in process body", kw
))),
}
}
other => return Err(self.error(&format!(
"unexpected token {} in process body", other
))),
}
}
Ok(actions)
}
fn parse_leaf(&mut self) -> Result<LeafDecl, ParseError> {
// "leaf" keyword already consumed
let name = self.expect_ident()?;
self.expect_token(&Token::LBrace)?;
let mut reads = None;
let mut actions = Vec::new();
loop {
match self.lexer.peek_token()? {
Token::RBrace => {
self.lexer.next_token()?;
break;
}
Token::Ident(ref kw) => {
let kw = kw.clone();
match kw.as_str() {
"reads" => {
self.lexer.next_token()?;
let actor = self.expect_ident()?;
self.expect_token(&Token::Dot)?;
let window = self.expect_ident()?;
reads = Some(ReadClause { actor, window });
}
"process" => {
self.lexer.next_token()?;
actions = self.parse_leaf_actions()?;
}
_ => return Err(self.error(&format!(
"unexpected keyword '{}' in leaf body", kw
))),
}
}
other => return Err(self.error(&format!(
"unexpected token {} in leaf body", other
))),
}
}
Ok(LeafDecl { name, reads, actions })
}
fn parse_pipeline(&mut self) -> Result<PipelineDecl, ParseError> {
// "pipeline" keyword already consumed
let name = self.expect_ident()?;
self.expect_token(&Token::LBrace)?;
let mut stages = Vec::new();
stages.push(self.expect_ident()?);
loop {
match self.lexer.peek_token()? {
Token::Arrow => {
self.lexer.next_token()?;
stages.push(self.expect_ident()?);
}
Token::RBrace => {
self.lexer.next_token()?;
break;
}
other => return Err(self.error(&format!(
"expected '->' or '}}' in pipeline, got {}", other
))),
}
}
Ok(PipelineDecl { name, stages })
}
fn parse_ident_list(&mut self) -> Result<Vec<String>, ParseError> {
self.expect_token(&Token::LBracket)?;
let mut items = Vec::new();
loop {
match self.lexer.peek_token()? {
Token::RBracket => {
self.lexer.next_token()?;
break;
}
Token::Comma => {
self.lexer.next_token()?;
}
_ => items.push(self.expect_ident()?),
}
}
Ok(items)
}
fn parse_core(&mut self) -> Result<CoreDecl, ParseError> {
// "core" keyword already consumed
let name = self.expect_ident()?;
self.expect_token(&Token::LBrace)?;
let mut actors = Vec::new();
let mut leaves = Vec::new();
let mut pipelines = Vec::new();
let mut steps = 0u64;
loop {
match self.lexer.peek_token()? {
Token::RBrace => {
self.lexer.next_token()?;
break;
}
Token::Ident(ref kw) => {
let kw = kw.clone();
match kw.as_str() {
"actors" => {
self.lexer.next_token()?;
self.expect_token(&Token::Colon)?;
actors = self.parse_ident_list()?;
}
"leaves" => {
self.lexer.next_token()?;
self.expect_token(&Token::Colon)?;
leaves = self.parse_ident_list()?;
}
"pipelines" => {
self.lexer.next_token()?;
self.expect_token(&Token::Colon)?;
pipelines = self.parse_ident_list()?;
}
"steps" => {
self.lexer.next_token()?;
self.expect_token(&Token::Colon)?;
steps = self.expect_number()?;
}
_ => return Err(self.error(&format!(
"unexpected field '{}' in core body", kw
))),
}
}
other => return Err(self.error(&format!(
"unexpected token {} in core body", other
))),
}
}
Ok(CoreDecl { name, actors, leaves, pipelines, steps })
}
pub fn parse_program(&mut self) -> Result<L1Program, ParseError> {
let mut actors = Vec::new();
let mut leaves = Vec::new();
let mut pipelines = Vec::new();
let mut cores = Vec::new();
loop {
match self.lexer.peek_token()? {
Token::Eof => break,
Token::Ident(ref kw) => {
let kw = kw.clone();
match kw.as_str() {
"actor" => {
self.lexer.next_token()?;
actors.push(self.parse_actor()?);
}
"leaf" => {
self.lexer.next_token()?;
leaves.push(self.parse_leaf()?);
}
"pipeline" => {
self.lexer.next_token()?;
pipelines.push(self.parse_pipeline()?);
}
"core" => {
self.lexer.next_token()?;
cores.push(self.parse_core()?);
}
_ => return Err(self.error(&format!(
"unexpected top-level keyword '{}'", kw
))),
}
}
other => return Err(self.error(&format!(
"unexpected token {} at top level", other
))),
}
}
Ok(L1Program { actors, leaves, pipelines, cores })
}
}
/// Parse L1 DSL text into an L1 program.
pub fn parse(input: &str) -> Result<L1Program, ParseError> {
Parser::new(input).parse_program()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_counter_example() {
let input = include_str!("../examples/counter.l1");
let program = parse(input).expect("counter.l1 should parse");
assert_eq!(program.actors.len(), 1);
assert_eq!(program.actors[0].name, "counter");
assert_eq!(program.actors[0].state.len(), 1);
assert_eq!(program.actors[0].state[0].name, "count");
assert_eq!(program.actors[0].state[0].ty, FieldType::U64);
assert_eq!(program.actors[0].state[0].init, 0);
assert_eq!(program.actors[0].windows.len(), 1);
assert_eq!(program.actors[0].windows[0].name, "count_view");
assert_eq!(program.actors[0].windows[0].fields, vec!["count"]);
assert_eq!(program.actors[0].windows[0].readers, vec!["display"]);
assert_eq!(program.actors[0].handlers.len(), 1);
assert_eq!(program.actors[0].handlers[0].message_type, "Increment");
assert_eq!(program.actors[0].handlers[0].args.len(), 1);
assert_eq!(program.actors[0].handlers[0].args[0].0, "amount");
assert_eq!(program.leaves.len(), 2);
assert_eq!(program.leaves[0].name, "ticker");
assert_eq!(program.leaves[1].name, "display");
assert!(program.leaves[1].reads.is_some());
assert_eq!(program.pipelines.len(), 1);
assert_eq!(program.pipelines[0].stages, vec!["ticker", "counter", "display"]);
assert_eq!(program.cores.len(), 1);
assert_eq!(program.cores[0].steps, 5);
}
#[test]
fn parse_window_example() {
let input = include_str!("../examples/window.l1");
let program = parse(input).expect("window.l1 should parse");
assert_eq!(program.actors.len(), 1);
assert_eq!(program.actors[0].name, "accumulator");
assert_eq!(program.actors[0].state[0].name, "total");
assert_eq!(program.actors[0].state[0].init, 0);
assert_eq!(program.actors[0].windows[0].name, "total_view");
assert_eq!(program.actors[0].windows[0].readers, vec!["observe"]);
assert_eq!(program.leaves.len(), 2);
assert_eq!(program.leaves[0].name, "source");
assert_eq!(program.leaves[1].name, "observe");
assert!(program.leaves[1].reads.is_some());
assert_eq!(program.leaves[1].actions.len(), 2);
assert_eq!(program.cores[0].steps, 3);
}
#[test]
fn parse_error_unexpected_top_level() {
let input = "foobar { }";
let err = parse(input).unwrap_err();
assert!(err.message.contains("unexpected top-level keyword 'foobar'"));
}
#[test]
fn parse_error_missing_brace() {
let input = "actor counter state { count: u64 = 0 } }";
let err = parse(input).unwrap_err();
assert!(err.message.contains("expected '{'"));
}
#[test]
fn parse_error_unknown_type() {
let input = "actor a { state { x: bool = 0 } }";
let err = parse(input).unwrap_err();
assert!(err.message.contains("unknown type: bool"));
}
#[test]
fn parse_error_malformed_pipeline() {
let input = "pipeline p { a -> }";
let err = parse(input).unwrap_err();
assert!(err.message.contains("expected identifier"));
}
#[test]
fn parsed_matches_hand_built_counter() {
let input = include_str!("../examples/counter.l1");
let parsed = parse(input).expect("should parse");
let hand_built = crate::l1_ir::tests::build_counter_program();
assert_eq!(parsed.actors.len(), hand_built.actors.len());
assert_eq!(parsed.actors[0].name, hand_built.actors[0].name);
assert_eq!(parsed.actors[0].state, hand_built.actors[0].state);
assert_eq!(parsed.actors[0].windows, hand_built.actors[0].windows);
assert_eq!(parsed.actors[0].handlers[0].message_type, hand_built.actors[0].handlers[0].message_type);
assert_eq!(parsed.leaves.len(), hand_built.leaves.len());
assert_eq!(parsed.pipelines, hand_built.pipelines);
assert_eq!(parsed.cores, hand_built.cores);
}
#[test]
fn parsed_matches_hand_built_window() {
let input = include_str!("../examples/window.l1");
let parsed = parse(input).expect("should parse");
let hand_built = crate::l1_ir::tests::build_window_program();
assert_eq!(parsed.actors[0].name, hand_built.actors[0].name);
assert_eq!(parsed.actors[0].state, hand_built.actors[0].state);
assert_eq!(parsed.actors[0].windows, hand_built.actors[0].windows);
assert_eq!(parsed.leaves.len(), hand_built.leaves.len());
assert_eq!(parsed.pipelines, hand_built.pipelines);
assert_eq!(parsed.cores, hand_built.cores);
}
}