Hi guys!
I'm doing an experiment in creating some small parser combinator to eventually parse JSON. I thought I could create a type that is the function signature of the function to return from jsObj - so it would be a bit better to read. But there seems to be something wrong with my code, because I get the error message "cannot instantiate return:type" when trying to compile. My code is like this:
from std/parseopt import initOptParser, next
from std/sugar import collect, `->`, `=>`
from std/strutils import find
type
  ParseResult = ref object
    index: int
    result: seq[string]
  
  Parser = (string) -> ParseResult | CatchableError
proc jsObj(input: string): Parser =
  return
    proc (c: string): ParseResult | CatchableError =
      let pos = input.find(c)
      if pos == -1:
        return CatchableError()
      else:
        return ParseResult(index: pos, result: @[c])
proc parseJSON*(content: string) =
  let objParser = jsObj("{}")
I've tried several things, including using the the expected function signature explicitly, like this:
proc jsObj(input: string): (proc (x: string): ParseResult | CatchableError) =
But it's giving me the same error. Can someone give me a hint about what the problem is or if this is even the right way to do it? I think I could maybe achieve the same result using templates, couldn't I?
Thanks in advance for all your help!
Ok, my next probably stupid step is: I want to create a parser combinator function that takes a variable amount of other parser combinators and creates a new one. I thought about using a varargs[Parser] parameter, but I think I unfortunately don't understand it correctly at that point. My code is:
type
  ParseResult* = ref object
    case success*: bool
    of true:
      value*: string
      rest*: string
    of false:
      error*: string
  
  Parser* = (string) -> ParseResult
proc character*(c: char): Parser =
  return
    proc (input: string): ParseResult =
      if input[0] == c:
        return ParseResult(success: true, value: $c, rest: input.substr(1))
      else:
        return ParseResult(success: false, error: &"Couldn't find {c}")
proc either*(parsers: varargs[Parser]): Parser =
  return
    proc (input: string): ParseResult =
      for p in parsers:
        let res = p(input)
        if res.success:
          return res
      
      return ParseResult(success: false, error: "Couldn't match either parsers")
But this gives me the error message: "Error: 'parsers' is of type <varargs[Parser]> which cannot be captured as it would violate memory safety"
How does varargs work with a procedural type like Parser?
https://github.com/loloicci/nimly
https://github.com/choltreppe/parlexgen
https://github.com/haxscramper/hparse
https://github.com/erhlee-bird/bnimf
https://github.com/beef331/commander
There are excellent parser generator available. Please study how they work. "Combintators" are a kludge you come up with when your PL lacks decent DSL capabilities.
I think it's:
proc either*(parsers: varargs[Parser]): Parser =
  let parsers = @parsers
  return
    proc (input: string): ParseResult =
      for p in parsers:
        let res = p(input)
        if res.success:
          return res
      
      return ParseResult(success: false, error: "Couldn't match either parsers")
For your case.
I think you could store the varargs in a local seq and then capture that
import std/sequtils
proc either*(parsers: varargs[Parser]): Parser =
  let parsers = parsers.toSeq
  return
    proc (input: string): ParseResult =
      for p in parsers:
        let res = p(input)
        if res.success:
          return res
      
      return ParseResult(success: false, error: "Couldn't match either parsers")