Skip to content

Documentation and examples are quite sparse #19

Description

@JonasHedEng

Hi!
This seems like a really nice lib and I've tried to use it for a quite simple expression parser, inspired by clarkmcc/cel-rust.

My attempt so far (trying to keep things minimal until I get something working) is the pasted snippet at the bottom.
When running it, it seems to get into some infinite loop but I'm not really sure how to address it? I'm assuming I'm infinitely recursing down the lhs but I'm also not sure how to express it otherwise?

I've tried looking at the documentation and the examples but I can't really figure out how to map them to my use case. I'd appreciate any kind of help and I'll gladly contribute such a lexer/parser as example/testing back upstream if I get something representable working!

import gleam/io
import gleam/list
import gleam/option.{None, Some}
import gleam/set
import nibble.{type Parser}
import nibble/lexer

type TokenT {
  // Arithmetic
  AddT
  SubT
  MulT
  DivT
  ModT

  // Relation
  LessThanT
  LessThanEqT
  GreaterThanT
  GreaterThanEqT
  EqualsT
  NotEqualsT
  InT

  // Atom
  IntT(Int)
  // AUInt(Int)
  FloatT(Float)
  StringT(String)
  BoolT(Bool)
  NullT

  // Ident
  IdentT(String)

  LParen
  RParen
}

pub type ArithmeticOp {
  Add
  Sub
  Mul
  Div
  Mod
}

pub type RelationOp {
  LessThan
  LessThanEq
  GreaterThan
  GreaterThanEq
  Equals
  NotEquals
  In
}

pub type Atom {
  Int(Int)
  Float(Float)
  // String(String)
  Bool(Bool)
  Null
}

pub type Expression {
  Arithmetic(Expression, ArithmeticOp, Expression)
  Relation(Expression, RelationOp, Expression)
  Atom(Atom)
  Ident(String)
}

type Context {
  // InList
  // InMap
  // InTernary
  InSubExpr
}

fn lexer() {
  let reserved_literals = set.from_list(["false", "in", "null", "true"])
  let reserved_host =
    set.from_list([
      "as", "break", "const", "continue", "else", "for", "function", "if",
      "import", "let", "loop", "package", "namespace", "return", "var", "void",
      "while",
    ])

  let reserved = set.intersection(reserved_literals, reserved_host)

  let grouping = [lexer.token("(", LParen), lexer.token(")", RParen)]
  let arithmetic_op = [
    lexer.token("+", AddT),
    lexer.token("-", SubT),
    lexer.token("*", MulT),
    lexer.token("/", DivT),
    lexer.token("%", ModT),
  ]

  let relation_op = [
    // Relation
    lexer.token("<", LessThanT),
    lexer.token(">", GreaterThanT),
    lexer.token("<=", LessThanEqT),
    lexer.token(">=", GreaterThanEqT),
    lexer.token("==", EqualsT),
    lexer.token("!=", NotEqualsT),
    lexer.token("in", InT),
  ]
  let value = [
    // Value
    lexer.token("true", BoolT(True)),
    lexer.token("false", BoolT(False)),
    lexer.number(IntT, FloatT),
    // lexer.then -> UInt
    lexer.string("\"", StringT),
    lexer.string("'", StringT),
    lexer.token("null", NullT),
  ]

  lexer.simple(
    list.flatten([
      grouping,
      arithmetic_op,
      relation_op,
      value,
      [
        // Identifier
        lexer.identifier("[_a-zA-Z][_a-zA-Z0-9]*", "[^\\s=]", reserved, IdentT),
        // Comments
        lexer.comment("//", fn(_) { Nil }) |> lexer.ignore(),
        // Whitespace
        lexer.whitespace(Nil)
          |> lexer.ignore(),
      ],
    ]),
  )
}

fn parser() -> Parser(Expression, TokenT, Context) {
  use expr <- nibble.do(arith_add_sub_full_parser())
  nibble.succeed(expr)
}

fn arith_add_sub_full_parser() -> Parser(Expression, TokenT, Context) {
  nibble.one_of([arith_add_sub_inner_parser(), arith_mul_div_mod_full_parser()])
}

fn arith_add_sub_inner_parser() -> Parser(Expression, TokenT, Context) {
  use lhs <- nibble.do(nibble.lazy(arith_add_sub_full_parser))
  use op <- nibble.do(op_add_sub_parser())
  use rhs <- nibble.do(arith_mul_div_mod_full_parser())

  nibble.return(Arithmetic(lhs, op, rhs))
}

fn arith_mul_div_mod_full_parser() -> Parser(Expression, TokenT, Context) {
  nibble.one_of([arith_mul_div_mod_inner_parser(), atom_parser()])
}

fn arith_mul_div_mod_inner_parser() -> Parser(Expression, TokenT, Context) {
  use lhs <- nibble.do(nibble.lazy(arith_mul_div_mod_full_parser))
  use op <- nibble.do(op_mul_div_mod_parser())
  use rhs <- nibble.do(atom_parser())

  nibble.return(Arithmetic(lhs, op, rhs))
}

fn op_mul_div_mod_parser() -> Parser(ArithmeticOp, TokenT, Context) {
  use tok <- nibble.take_map("*,/,%")

  case tok {
    MulT -> Some(Mul)
    DivT -> Some(Div)
    ModT -> Some(Mod)
    _ -> None
  }
}

fn op_add_sub_parser() -> Parser(ArithmeticOp, TokenT, Context) {
  use tok <- nibble.take_map("+,-")

  case tok {
    AddT -> Some(Add)
    SubT -> Some(Sub)
    _ -> None
  }
}

fn atom_parser() -> Parser(Expression, TokenT, Context) {
  nibble.backtrackable({
    use t <- nibble.do(nibble.any())

    case t {
      IntT(n) -> nibble.return(Atom(Int(n)))
      FloatT(n) -> nibble.return(Atom(Float(n)))
      BoolT(n) -> nibble.return(Atom(Bool(n)))
      NullT -> nibble.return(Atom(Null))
      _ -> nibble.fail("Expected a literal value")
    }
  })
}

pub fn main() {
  let assert Ok(lexed) = lexer.run("5 + a", lexer())

  let _ =
    nibble.run(lexed, parser())
    |> io.debug

  "done"
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions