Skip to content

knarkzel/vom

Repository files navigation

vom

GitHub contributors GitHub issues PRs Welcome HitCount

vom is a rewrite of nom, which is a parser combinator library. It is written in V, hence the name.

Example

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)
}

When will it reach 1.0?

There are some features I both need and want working in V before I will complete this library:

Generic return type for closures returned from functions

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.

Generic type aliases

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.

Functions that return closure that captures functions from function parameter

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)
	}
}

Call closure returned from function immediately

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}
}

Install

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.

Why use vom?

  • 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

Resources