14 unstable releases (3 breaking)
0.4.2 | Jan 15, 2022 |
---|---|
0.4.1 | Nov 28, 2021 |
0.4.0 | Oct 27, 2021 |
0.3.1 | Oct 17, 2021 |
0.1.3 | Aug 30, 2021 |
#120 in Parser tooling
76 downloads per month
215KB
5.5K
SLoC
lelwel
Lelwel generates recursive descent parsers for Rust using LL(1) grammars. Conflicts are resolved with semantic predicates. Semantic actions are used for ad hoc syntax-directed translation. Unlike in other parser generators (e.g. Bison, JavaCC, or Coco/R), actions and predicates are not defined inline, which makes it easier to read the grammar.
Lelwel is written as a library, which is used by the CLI tool llw
and the language server lelwel-ls
.
There are plugins for Neovim and Visual Studio Code (not published yet) that use the language server.
Why Yet Another Parser Generator?
- Language Server: Get instant feedback when your grammar contains conflicts or errors.
- Easy to Read: Code for semantic actions and predicates does not clutter the grammar.
- Easy to Debug: The generated parser is easy to understand and can be debugged with standard tools.
Installation
Run cargo install --features="cli","lsp" lelwel
.
Grammar Examples
The parser for lelwel grammar files (*.llw) is itself generated by lelwel. The following examples show grammars for a basic calculator and JSON.
Calculator
// token definitions
token Num{f64}='number';
token Add='+' Sub='-' Mul='*' Div='/';
token LPar='(' RPar=')';
// grammar rules
start{f64}:
expr #1
;
expr{f64}:
term #1 (
'+' term #2
| '-' term #3
)* #4
;
term{f64}:
atomic #1 (
'*' atomic #2
| '/' atomic #3
)* #4
;
atomic{f64}:
Num #1
| '(' expr ')' #2
;
// semantic actions
start#1 { Ok(expr) }
expr#1 { let mut res = term; }
expr#2 { res += term; }
expr#3 { res -= term; }
expr#4 { Ok(res) }
term#1 { let mut res = atomic; }
term#2 { res *= atomic; }
term#3 { res /= atomic; }
term#4 { Ok(res) }
atomic#1 { Ok(Num.0) }
atomic#2 { Ok(expr) }
JSON
token True='true' False='false' Null='null';
token LBrace='{' RBrace='}' LBrak='[' RBrak=']' Comma=',' Colon=':';
token String{String}='<string>' Number{String}='<number>';
start{Value}:
value #1
;
value{Value}:
object #1
| array #2
| String #3
| Number #4
| 'true' #5
| 'false' #6
| 'null' #7
;
object{Value}:
'{' [member #1 (',' member #2)* | !1] '}' #3
;
member{(String, Value)}:
String ':' value #1
;
array{Value}:
'[' [value #1 (',' value #2)* | !1] ']' #3
;
limit 1000;
preamble {
use std::collections::BTreeMap;
use super::diag::*;
use super::Value;
}
parameters { diag: &mut Diag }
error { Code }
start#1 { Ok(value) }
value#1 { Ok(object) }
value#2 { Ok(array) }
value#3 { Ok(Value::String(String.0)) }
value#4 { Ok(Value::Number(Number.0)) }
value#5 { Ok(Value::Bool(true)) }
value#6 { Ok(Value::Bool(false)) }
value#7 { Ok(Value::Null) }
object#0 { let mut members = BTreeMap::new(); }
object#1 { members.insert(member.0, member.1); }
object#2 { members.insert(member.0, member.1); }
object#3 { Ok(Value::Object(members)) }
object!1 { diag.error(error_code, error_range); }
member#1 { Ok((String.0, value)) }
array#0 { let mut values = vec![]; }
array#1 { values.push(value); }
array#2 { values.push(value); }
array#3 { Ok(Value::Array(values)) }
array!1 { diag.error(error_code, error_range); }
License
Lelwel and its generated code is licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Dependencies
~0.2–1.3MB
~28K SLoC