A collection of npeg utils.
Install using this command:
nimble install https://github.com/thatrandomperson5/npeg-utils
This tutorial builds a parser for a indented-data file format. This tutorial assumes you know the basics of npeg
First, add the basics:
import npeg, npeg_utils/[indent, astc]
let parser = peg("main"):
# Parser goes here!
Then we can work on the parser, the data file will be a string, key-value, type format:
Key:
IndentedValue
IndentedKey:
OtherValue
Value
So, first, we need to describe the key and value:
Data <- +Alnum # One or more numbers/letters
Key <- Data * ":" # Data and a colon
Value <- Data * !":" # Data, and for saftey, explicit not colon
The next step is indentation! We will be using ib.Line
and ib.Block
.
KeyBlock <- ib.Line(Key) * ib.Block(stmt)
The code above parses a properly indented line and then a further indented block with the main stmt. Now we can move on to the main stmt.
stmt <- +(ib.Line(Value) | KeyBlock)
main <- stmt * !1
This parses a value or a indented key block! This should now parse the example above.
To gather data like this in nim, we need to use a nodetree type. Put this before your parser:
type
MyDataKind = enum Container, Value # The kind enum
MyData = ref object # Needs to be a ref object
case kind: MyDataKind # Variation
of Value:
v: string # If it is the Value kind have a field v of type string
of Container:
key: string # The parsed key
children: seq[MyData] # Its indented children
proc add(parent: MyData, child: MyData) = # Add an "add" proc
parent.children.add child # Add the child to the parent node
And then change this line:
let parser = peg("main"):
to:
let parser = peg("main", ac: AdoptionCenter[MyData]):
We need to collect the data using the ac
var inside the parser,
this part of the tutorial will tell you how!
First we need to add a code block in the value definition:
Value <- >Data * !":": # Capture the data and then handle it
ac.add MyData(kind: Value, v: $1) # Set the data obj made from capture up for adoption
Finally we change the KeyBlock.
Key <- >Data * ":" # Save the data
This captures the key for us, but we still need data. Thats what the astc module is for!
KeyBlock <- astc.hooked(ib.Line(Key) * ib.Block(stmt)):
var container = MyData(kind: Container, key: $1) # Create the container and add the key
adoptCycle(ac, container)
Note: astc.hooked
is required to use this version of the adopt proc. Look at the docs for other versions
You may be wondering what the adoptCycle
does?
Well it adopts the right children (the ones parsed under it) and then puts itself up for adoption!
This system lets you build nodetrees from parsed data!
Now that the parser and data collecter is done, we still need to know how to use it! After you add the lines of code below your project should be ready to run!
proc `$`(d: MyData): string =
if d.kind == Value:
return d.v
else:
result = d.key & "["
for child in d.children:
result &= $child & ", "
result &= "]"
const testStr = """Key:
IndentedValue
IndentedKey:
OtherValue
Value"""
var ac = newAdoptionCenter[MyData]()
doAssert parser.match(testStr, ac).ok
echo $ac
See the final version here