Skip to content

Commit

Permalink
Update README.md
Browse files Browse the repository at this point in the history
  • Loading branch information
0x2a-42 authored Dec 13, 2023
1 parent 0eda79b commit 06f29a8
Showing 1 changed file with 72 additions and 76 deletions.
148 changes: 72 additions & 76 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,40 @@
[![Crates.io](https://img.shields.io/crates/d/lelwel)](https://crates.io/crates/lelwel)
[![Rust](https://img.shields.io/github/actions/workflow/status/0x2a-42/lelwel/rust.yml)](https://github.com/0x2a-42/lelwel/actions)

## Table of Contents
* [Introduction](#introduction)
* [Grammar Examples](#grammar-examples)
* [Quickstart](#quickstart)
* [Documentation](#documentation)
* [License](#license)

## Introduction

[Lelwel](https://en.wikipedia.org/wiki/Lelwel_hartebeest) generates recursive descent parsers for Rust using [LL(1) grammars](https://en.wikipedia.org/wiki/LL_grammar).
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](https://github.com/0x2a-42/nvim-lelwel) and Visual Studio Code (not published yet) that use the language server.
There is a plugin for [Neovim](https://github.com/0x2a-42/nvim-lelwel) that uses the language server.

## Why Yet Another Parser Generator?
#### 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](src/frontend/lelwel.llw) (\*.llw) is itself generated by lelwel.
The following examples show grammars for a [basic calculator](examples/calc) and [JSON](examples/json).
There is also an example for a [JSON parser](examples/json).
The following example shows a grammar for a [basic calculator](examples/calc).

### Calculator
```antlr
// token definitions
// calculator
token Num{f64}='number';
token Add='+' Sub='-' Mul='*' Div='/';
token LPar='(' RPar=')';
// grammar rules
start{f64}:
expr #1
;
Expand All @@ -55,8 +58,6 @@ atomic{f64}:
| '(' expr ')' #2
;
// semantic actions
start#1 { Ok(expr) }
expr#1 { let mut res = term; }
Expand All @@ -73,71 +74,66 @@ atomic#1 { Ok(Num.0) }
atomic#2 { Ok(expr) }
```

### JSON
```antlr
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); }
```
## Quickstart
1. Add the following to your `Cargo.toml` and `build.rs` files.
```toml
[dependencies]
logos = "0.13.0"
codespan-reporting = "0.11.1"

[build-dependencies]
lelwel = "0.5.0"
```
```rust
fn main() {
lelwel::build("src/your_grammar.llw");
}
```
2. Start a build. This will create a minimal grammar file at the specified path and a `parser.rs` file next to it.
The `parser.rs` file is supposed to be manually edited to implement the lexer and it includes the actual parser, which is written to the Cargo `OUT_DIR`.
3. Optionally you can install the CLI or language server to validate your grammar file: `cargo install --features="cli","lsp" lelwel`.
4. Use the parser module.
```rust
use codespan_reporting::diagnostic::Severity;
use codespan_reporting::files::SimpleFile;
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
use codespan_reporting::term::{self, Config};
use logos::Logos;
use parser::*;

mod parser;

fn main() -> std::io::Result<()> {
let args: Vec<String> = std::env::args().collect();
if args.len() != 2 {
std::process::exit(1);
}
let contents = std::fs::read_to_string(&args[1])?;

let mut tokens = TokenStream::new(Token::lexer(&contents));
let mut diags = vec![];
if let Some(result) = Parser::parse(&mut tokens, &mut diags) {
println!("{result:?}");
}

let writer = StandardStream::stderr(ColorChoice::Auto);
let config = Config::default();
let file = SimpleFile::new(&args[1], &contents);
for diag in diags.iter() {
term::emit(&mut writer.lock(), &config, &file, &diag).unwrap();
}
if diags.iter().any(|d| d.severity == Severity::Error) {
std::process::exit(1);
}
Ok(())
}
```

## Documentation

TODO

## License

Lelwel and its generated code is licensed under either of

* Apache License, Version 2.0
Expand Down

0 comments on commit 06f29a8

Please sign in to comment.