vom
is a rewrite of nom, which is a parser combinator library.
It is written in V, hence the name.
Hexadecimal color parser:
module main
import vom { is_hex_digit, tag, take_while_m_n, tuple }
struct Color {
red u8
green u8
blue u8
}
fn from_hex(input string) u8 {
return '0x${input}'.u8()
}
fn hex_primary(input string) !(string, string, int) {
parser := take_while_m_n(2, 2, is_hex_digit)
return parser(input)
}
fn hex_color(input string) !(string, Color) {
discard := tag('#')
hex_part, _, _ := discard(input)!
parser := tuple([hex_primary, hex_primary, hex_primary])
rest, output, _ := parser(hex_part)!
red, green, blue := from_hex(output[0]), from_hex(output[1]), from_hex(output[2])
return rest, Color{red, green, blue}
}
fn main() {
_, color := hex_color('#2F14DF')!
assert color == Color{47, 20, 223}
println(color)
}
There are some features I both need and want working in V before I will complete this library:
This is the only feature I absolutely need in order to finish this
library. Without it, we're stuck with returning ?(string, string)
instead of
?(string, T)
from each parser, and thus can't construct an Ast with the
library alone. That's currently something you need to do manually.
Currently this isn't implemented yet. Although it's not required in order to implement the features that are missing, it will make the codebase look horrible without because almost all of the functions depend on following:
type Fn = fn (string) ?(string, string)
type FnMany = fn (string) ?(string, []string)
And I need the last argument to be generic, because parsers could return other types such as int, token, []token etc. Although I could search and replace each entry manually, I'm too lazy to do that.
This is not a necessary issue either, but it would remove lots of boilerplate in the current code, for instance from sequence.v.
Adding this would turn following:
pub fn minimal(cond fn (int) bool) fn (int) bool {
functions := [cond]
return fn [functions] (input int) bool {
cond := functions[0]
return cond(input)
}
}
Into this:
pub fn minimal(cond fn (int) bool) fn (int) bool {
return fn [cond] (input int) bool {
return cond(input)
}
}
This is again not a mandatory feature for this library to work, but would be a nice addition. Instead of following code:
fn operator(input string, location Location) ?(string, Token) {
parser := alt([tag('+'), tag('-'), tag('<')])
rest, output := parser(input) ?
return rest, Token{output, location, .operator}
}
We could write this instead, which is a very common pattern in nom
:
fn operator(input string, location Location) ?(string, Token) {
rest, output := alt([tag('+'), tag('-'), tag('<')])(input) ?
return rest, Token{output, location, .operator}
}
v install --git https://github.com/knarkzel/vom
Then import in your file like so:
import vom
rest, output := vom.digit1('123hello') ?
assert output == '123'
assert rest == 'hello'
There are examples in the examples/
folder.
- The parsers are small and easy to write
- The parsers components are easy to reuse
- The parsers components are easy to test separately
- The parser combination code looks close to the grammar you would have written
- You can build partial parsers, specific to the data you need at the moment, and ignore the rest