https://github.com/ragmaanir/syntaks
Parser combinators for crystal
https://github.com/ragmaanir/syntaks
crystal ebnf parser-combinators peg
Last synced: 7 months ago
JSON representation
Parser combinators for crystal
- Host: GitHub
- URL: https://github.com/ragmaanir/syntaks
- Owner: Ragmaanir
- License: mit
- Created: 2015-10-21T11:24:28.000Z (almost 10 years ago)
- Default Branch: master
- Last Pushed: 2021-05-04T22:39:47.000Z (over 4 years ago)
- Last Synced: 2025-01-14T01:12:28.383Z (9 months ago)
- Topics: crystal, ebnf, parser-combinators, peg
- Language: C
- Homepage:
- Size: 177 KB
- Stars: 4
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# syntaks [](https://github.com/Ragmaanir/syntaks/actions/workflows/crystal.yml)
### Version 0.3.2
A parser combinator framework.
## Usage
```crystal
require "syntaks"
```## Example
```crystal
class ListParser < Syntaks::Parser
rule(:root, Array(String), _os >> "(" >> _os & item_list & ")") { |v| v[3] }
rule(:item_list, Array(String), item >> ~item_list_tail) do |v|
if tail = v[1]
[v[0]] + tail
else
[v[0]]
end
endrule(:item_list_tail, Array(String), {"," & _os & item >> _os}) do |list|
list.map(&.[2])
endrule(:item, String, id >> _os) do |t|
t[0]
endrule(:id, String, /[_a-z][_a-z0-9]*/, &.content)
ignored(:_os, /\s*/)
endres = ListParser.new.call("(v1,v2)").as(Success)
assert res.value == ["v1", "v2"]
```
## Usage
Rules are specified in a format similar to EBNF:
```crystal
# literals/tokens
ignored(:space, /\s+/) # generates nil as AST-node
rule(:int, String, /[1-9][0-9]*/) { |m| m.content } # generates String as AST-node# sequence
rule(:root, String, "var " >> id) { |m| m[1] } # use the second match (id) as AST-node# sequence with backtracking disabled
rule(:root, String, "var " & id) { |m| m[1] }# alternatives
rule(:root, String, a | b)# repetition
rule(:root, Array(Int32), {"," >> int}) { |m| m.map(&.[1]) }
```When backtracking is disabled an parsing fails, a syntax error is produced:
```crystal
class ErrorParser < Syntaks::Parser
rule(:root, String, "var" >> space & /[a-z]+/) { |m| m[2].content }
ignored(:space, /\s+/)
endres = ErrorParser.new.call("var 1337").as(Error)
assert res.message == <<-MSG
Syntax error at 1:9:1>var 1337
----------^
Rule: "var" >> space & /[a-z]+/MSG
```
A parse log can be printed to inspect the parsing process:
```crystal
source = <<-SRC
def main
if true
x = 1
⬤
end
end
SRCctx = LoggingContext.new(ParseLog.new(Source.new(source)))
res = ExampleParsers::MethodParser.new.call(source, ctx).as(Error)log = ctx.parse_log.to_s
# The syntax error entry marked with ⚡ should be on the line that contains ⬤
assert(/⚡[^\n]+(29-33)[^\n]+⬤/ === log)```
## TODO
- simple token macro
- rules that do not generate ast nodes
- error-recovery productions / synchronization tokens
- optional whitespace skippers
- Simple lexer?
- Packrat parsing / memoization
- remember longest parsed prefix when parsing fails## DONE
- Use ameba
- Generated `README.md` with examples
- LoggingContext with a ParseLog
- ProfilingContext
- EBNF DSL
- Selectively disable backtracking
- Detailed syntax errors: display production that failed and the location where exactly it failed## Contributing
1. Fork it ( https://github.com/ragmaanir/syntaks/fork )
2. Create your feature branch (git checkout -b my-new-feature)
3. Commit your changes (git commit -am 'Add some feature')
4. Push to the branch (git push origin my-new-feature)
5. Create a new Pull Request## Contributors
- [Ragmaanir](https://github.com/ragmaanir) - creator, maintainer