Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Grammar File

Felys grammar file uses customized syntax to generate the parser and lexer. However, due to limitations of the parser generator, the non-terminals ident and eof are still written by hand. Their implementations can be found in the source code.

{
    use crate::ast::*;
    #[allow(unused)]
    use std::rc::Rc;
}

grammar -> { Grammar }:
    / stmts=(stmt=stmt $)* #eof { Grammar(stmts) }
    ;

@(memo)
pat -> { Pat }:
    / @(token)('_' !IB) { Pat::Any }
    / ident=ident { Pat::Ident(ident) }
    / lit=lit { Pat::Lit(lit) }
    / '(' first=pat ',' second=#pat more=(',' pat=#pat)* #')' { {
        let mut elements = more;
        elements.insert(0, second);
        elements.insert(0, first);
        Pat::Tuple(elements)
    } }
    ;

stmt -> { Stmt }:
    / expr=expr ';' { Stmt::Semi(expr) }
    / expr=expr { Stmt::Expr(expr) }
    / ';' { Stmt::Empty }
    ;

block -> { Block }:
    / '{' stmts=stmt* #'}' { Block(stmts) }
    ;

@(memo)
expr -> { Expr }:
    / assignment=assignment
    / disjunction=disjunction
    / block=block { Expr::Block(block) }
    / @(token)('break' !IB) expr=expr? { Expr::Break(expr.map(Rc::new)) }
    / @(token)('continue' !IB) { Expr::Continue }
    / @(token)('for' !IB) pat=#pat @(token)(#'in' !IB) expr=#expr block=#block {
        Expr::For(pat, expr.into(), block)
    }
    / @(token)('if' !IB) expr=#expr block=#block otherwise=otherwise? {
        Expr::If(expr.into(), block, otherwise.map(Rc::new))
    }
    / @(token)('loop' !IB) block=#block { Expr::Loop(block) }
    / @(token)('return' !IB) expr=expr? { Expr::Return(expr.map(Rc::new)) }
    / @(token)('while' !IB) expr=#expr block=#block { Expr::While(expr.into(), block) }
    ;

otherwise -> { Expr }: @(token)('else' !IB) expr=#expr ;

assignment -> { Expr }:
    / pat=pat '=' !'=' expr=#expr { Expr::Assign(pat, AssOp::Eq, expr.into()) }
    / pat=pat '+=' expr=#expr { Expr::Assign(pat, AssOp::AddEq, expr.into()) }
    / pat=pat '-=' expr=#expr { Expr::Assign(pat, AssOp::SubEq, expr.into()) }
    / pat=pat '*=' expr=#expr { Expr::Assign(pat, AssOp::MulEq, expr.into()) }
    / pat=pat '/=' expr=#expr { Expr::Assign(pat, AssOp::DivEq, expr.into()) }
    / pat=pat '%=' expr=#expr { Expr::Assign(pat, AssOp::ModEq, expr.into()) }
    ;

disjunction -> { Expr }:
    / lhs=disjunction @(token)('or' !IB) rhs=#conjunction {
        Expr::Binary(lhs.into(), BinOp::Or, rhs.into())
    }
    / conjunction=conjunction
    ;

conjunction -> { Expr }:
    / lhs=conjunction @(token)('and' !IB) rhs=#inversion {
        Expr::Binary(lhs.into(), BinOp::And, rhs.into())
    }
    / inversion=inversion
    ;

inversion -> { Expr }:
    / @(token)('not' !IB) inversion=#inversion { Expr::Unary(UnaOp::Not, inversion.into()) }
    / equality=equality
    ;

equality -> { Expr }:
    / lhs=equality '==' rhs=#comparison { Expr::Binary(lhs.into(), BinOp::Eq, rhs.into()) }
    / lhs=equality '!=' rhs=#comparison { Expr::Binary(lhs.into(), BinOp::Ne, rhs.into()) }
    / comparison=comparison
    ;

comparison -> { Expr }:
    / lhs=comparison '>=' rhs=#term { Expr::Binary(lhs.into(), BinOp::Ge, rhs.into()) }
    / lhs=comparison '<=' rhs=#term { Expr::Binary(lhs.into(), BinOp::Le, rhs.into()) }
    / lhs=comparison '>' rhs=#term { Expr::Binary(lhs.into(), BinOp::Gt, rhs.into()) }
    / lhs=comparison '<' rhs=#term { Expr::Binary(lhs.into(), BinOp::Lt, rhs.into()) }
    / term=term
    ;

term -> { Expr }:
    / lhs=term '+' rhs=#factor { Expr::Binary(lhs.into(), BinOp::Add, rhs.into()) }
    / lhs=term '-' rhs=#factor { Expr::Binary(lhs.into(), BinOp::Sub, rhs.into()) }
    / factor=factor
    ;

factor -> { Expr }:
    / lhs=factor '*' rhs=#unary { Expr::Binary(lhs.into(), BinOp::Mul, rhs.into()) }
    / lhs=factor '/' rhs=#unary { Expr::Binary(lhs.into(), BinOp::Div, rhs.into()) }
    / lhs=factor '%' rhs=#unary { Expr::Binary(lhs.into(), BinOp::Mod, rhs.into()) }
    / unary=unary
    ;

unary -> { Expr }:
    / '+' unary=#unary { Expr::Unary(UnaOp::Pos, unary.into()) }
    / '-' unary=#unary { Expr::Unary(UnaOp::Neg, unary.into()) }
    / call=call
    ;

call -> { Expr }:
    / call=call '(' args=args? #')' {
        Expr::Call(call.into(), args.unwrap_or_default())
    }
    / primary=primary
    ;

args -> { Vec<Expr> }:
    / first=expr more=(',' expr=#expr)* { {
      let mut elements = more;
      elements.insert(0, first);
      elements
    } }
    ;

primary -> { Expr }:
    / lit=lit { Expr::Lit(lit) }
    / ident=ident { Expr::Ident(ident) }
    / '(' expr=#expr ')' { Expr::Paren(expr.into()) }
    / '(' first=#expr #',' second=#expr more=(',' expr=#expr)* #')' { {
        let mut elements = more;
        elements.insert(0, second);
        elements.insert(0, first);
        Expr::Tuple(elements)
    } }
    / '[' args=args? #']' { Expr::List(args.unwrap_or_default()) }
    / '|' params=params? #'|' expr=#expr {
        Expr::Closure(params.unwrap_or_default(), expr.into())
    }
    ;

params -> { Vec<Ident> }:
    / first=ident more=(',' ident=#ident)* { {
        let mut elements = more;
        elements.insert(0, first);
        elements
    } }
    ;

lit -> { Lit }:
    / float=FLOAT { Lit::Float(float) }
    / int=INT { Lit::Int(int) }
    / str=str { Lit::Str(str) }
    / bool=bool { Lit::Bool(bool) }
    ;

@(token)
str -> { Vec<Chunk> }: '"' chunks=chunk* #'"' ;

@(token)
chunk -> { Chunk }:
    / slice=SLICE { Chunk::Slice(slice) }
    / '\\u{' hex=#HEX #'}' { Chunk::Unicode(hex) }
    / '\\' escape=#ESCAPE { Chunk::Escape(escape) }
    ;

@(token)
bool -> { Bool }:
    / 'true' !IB { Bool::True }
    / 'false' !IB { Bool::False }
    ;

IB: [a-zA-Z0-9_] ;

@(intern) {
    INT: '0' | [1-9][0-9]* ;
    FLOAT: [1-9][0-9]* '.' [0-9]+ | '0.' [0-9]+;
    IDENT: [a-zA-Z_] IB* ;
    SLICE: [^"\\]+ ;
    HEX: [0-9a-f]* ;
    ESCAPE: ['ntr\\] ;
}

@(ws) {
    WS: [\u{20}\n\t\r]+ ;
    COMMENT: '//' [^\n]* ;
}