discard """
  targets: "c cpp js"
  output: '''
PEG AST traversal output
------------------------
pkNonTerminal: Sum @(2, 3)
  pkSequence: (Product (('+' / '-') Product)*)
    pkNonTerminal: Product @(3, 7)
      pkSequence: (Value (('*' / '/') Value)*)
        pkNonTerminal: Value @(4, 5)
          pkOrderedChoice: (([0-9] [0-9]*) / ('(' Expr ')'))
            pkSequence: ([0-9] [0-9]*)
              pkCharChoice: [0-9]
              pkGreedyRepSet: [0-9]*
            pkSequence: ('(' Expr ')')
              pkChar: '('
              pkNonTerminal: Expr @(1, 4)
                pkNonTerminal: Sum @(2, 3)
              pkChar: ')'
        pkGreedyRep: (('*' / '/') Value)*
          pkSequence: (('*' / '/') Value)
            pkOrderedChoice: ('*' / '/')
              pkChar: '*'
              pkChar: '/'
            pkNonTerminal: Value @(4, 5)
    pkGreedyRep: (('+' / '-') Product)*
      pkSequence: (('+' / '-') Product)
        pkOrderedChoice: ('+' / '-')
          pkChar: '+'
          pkChar: '-'
        pkNonTerminal: Product @(3, 7)

Event parser output
-------------------
@[5.0]
+
@[5.0, 3.0]
@[8.0]

/
@[8.0, 2.0]
@[4.0]

-
@[4.0, 7.0]
-*
@[4.0, 7.0, 22.0]
@[4.0, 154.0]
-
@[-150.0]
'''
"""

when defined(nimHasEffectsOf):
  {.experimental: "strictEffects".}

import std/[strutils, streams, pegs]

const
  indent = "  "

let
  pegAst = """
Expr    <- Sum
Sum     <- Product (('+' / '-')Product)*
Product <- Value (('*' / '/')Value)*
Value   <- [0-9]+ / '(' Expr ')'
  """.peg
  txt = "(5+3)/2-7*22"

block:
  var
    outp = newStringStream()
    processed: seq[string] = @[]

  proc prt(outp: Stream, kind: PegKind, s: string; level: int = 0) =
    outp.writeLine indent.repeat(level) & "$1: $2" % [$kind, s]

  proc recLoop(p: Peg, level: int = 0) =
    case p.kind
    of pkEmpty..pkWhitespace:
      discard
    of pkTerminal, pkTerminalIgnoreCase, pkTerminalIgnoreStyle:
      outp.prt(p.kind, $p, level)
    of pkChar, pkGreedyRepChar:
      outp.prt(p.kind, $p, level)
    of pkCharChoice, pkGreedyRepSet:
      outp.prt(p.kind, $p, level)
    of pkNonTerminal:
      outp.prt(p.kind,
        "$1 @($3, $4)" % [p.nt.name, $p.nt.rule.kind, $p.nt.line, $p.nt.col], level)
      if not(p.nt.name in processed):
        processed.add p.nt.name
        p.nt.rule.recLoop level+1
    of pkBackRef..pkBackRefIgnoreStyle:
      outp.prt(p.kind, $p, level)
    else:
      outp.prt(p.kind, $p, level)
      for s in items(p):
        s.recLoop level+1

  pegAst.recLoop
  echo "PEG AST traversal output"
  echo "------------------------"
  echo outp.data

block:
  var
    pStack: seq[string] = @[]
    valStack: seq[float] = @[]
    opStack = ""
  let
    parseArithExpr = pegAst.eventParser:
      pkNonTerminal:
        enter:
          pStack.add p.nt.name
        leave:
          pStack.setLen pStack.high
          if length > 0:
            let matchStr = s.substr(start, start+length-1)
            case p.nt.name
            of "Value":
              try:
                valStack.add matchStr.parseFloat
                echo valStack
              except ValueError:
                discard
            of "Sum", "Product":
              try:
                let val {.used.} = matchStr.parseFloat
              except ValueError:
                if valStack.len > 1 and opStack.len > 0:
                  valStack[^2] = case opStack[^1]
                  of '+': valStack[^2] + valStack[^1]
                  of '-': valStack[^2] - valStack[^1]
                  of '*': valStack[^2] * valStack[^1]
                  else: valStack[^2] / valStack[^1]
                  valStack.setLen valStack.high
                  echo valStack
                  opStack.setLen opStack.high
                  echo opStack
      pkChar:
        leave:
          if length == 1 and "Value" != pStack[^1]:
            let matchChar = s[start]
            opStack.add matchChar
            echo opStack
  echo "Event parser output"
  echo "-------------------"
  let pLen = parseArithExpr(txt)
  doAssert txt.len == pLen


import std/importutils

block:
  proc pegsTest() =
    privateAccess(NonTerminal)
    privateAccess(Captures)

    if "test" =~ peg"s <- {{\ident}}": # bug #19104
      doAssert matches[0] == "test"
      doAssert matches[1] == "test", $matches[1]

    doAssert escapePeg("abc''def'") == r"'abc'\x27\x27'def'\x27"
    doAssert match("(a b c)", peg"'(' @ ')'")
    doAssert match("W_HI_Le", peg"\y 'while'")
    doAssert(not match("W_HI_L", peg"\y 'while'"))
    doAssert(not match("W_HI_Le", peg"\y v'while'"))
    doAssert match("W_HI_Le", peg"y'while'")

    doAssert($ +digits == $peg"\d+")
    doAssert "0158787".match(peg"\d+")
    doAssert "ABC 0232".match(peg"\w+\s+\d+")
    doAssert "ABC".match(peg"\d+ / \w+")

    var accum: seq[string] = @[]
    for word in split("00232this02939is39an22example111", peg"\d+"):
      accum.add(word)
    doAssert(accum == @["this", "is", "an", "example"])

    doAssert matchLen("key", ident) == 3

    var pattern = sequence(ident, *whitespace, term('='), *whitespace, ident)
    doAssert matchLen("key1=  cal9", pattern) == 11

    var ws = newNonTerminal("ws", 1, 1)
    ws.rule = *whitespace

    var expr = newNonTerminal("expr", 1, 1)
    expr.rule = sequence(capture(ident), *sequence(
                  nonterminal(ws), term('+'), nonterminal(ws), nonterminal(expr)))

    var c: Captures
    var s = "a+b +  c +d+e+f"
    doAssert rawMatch(s, expr.rule, 0, c) == len(s)
    var a = ""
    for i in 0..c.ml-1:
      a.add(substr(s, c.matches[i][0], c.matches[i][1]))
    doAssert a == "abcdef"
    #echo expr.rule

    #const filename = "lib/devel/peg/grammar.txt"
    #var grammar = parsePeg(newFileStream(filename, fmRead), filename)
    #echo "a <- [abc]*?".match(grammar)
    doAssert find("_____abc_______", term("abc"), 2) == 5
    doAssert match("_______ana", peg"A <- 'ana' / . A")
    doAssert match("abcs%%%", peg"A <- ..A / .A / '%'")

    var matches: array[0..MaxSubpatterns-1, string]
    if "abc" =~ peg"{'a'}'bc' 'xyz' / {\ident}":
      doAssert matches[0] == "abc"
    else:
      doAssert false

    var g2 = peg"""S <- A B / C D
                   A <- 'a'+
                   B <- 'b'+
                   C <- 'c'+
                   D <- 'd'+
                """
    doAssert($g2 == "((A B) / (C D))")
    doAssert match("cccccdddddd", g2)
    doAssert("var1=key; var2=key2".replacef(peg"{\ident}'='{\ident}", "$1<-$2$2") ==
           "var1<-keykey; var2<-key2key2")
    doAssert("var1=key; var2=key2".replace(peg"{\ident}'='{\ident}", "$1<-$2$2") ==
           "$1<-$2$2; $1<-$2$2")
    doAssert "var1=key; var2=key2".endsWith(peg"{\ident}'='{\ident}")

    if "aaaaaa" =~ peg"'aa' !. / ({'a'})+":
      doAssert matches[0] == "a"
    else:
      doAssert false

    if match("abcdefg", peg"c {d} ef {g}", matches, 2):
      doAssert matches[0] == "d"
      doAssert matches[1] == "g"
    else:
      doAssert false

    accum = @[]
    for x in findAll("abcdef", peg".", 3):
      accum.add(x)
    doAssert(accum == @["d", "e", "f"])

    for x in findAll("abcdef", peg"^{.}", 3):
      doAssert x == "d"

    if "f(a, b)" =~ peg"{[0-9]+} / ({\ident} '(' {@} ')')":
      doAssert matches[0] == "f"
      doAssert matches[1] == "a, b"
    else:
      doAssert false

    doAssert match("eine übersicht und außerdem", peg"(\letter \white*)+")
    # ß is not a lower cased letter?!
    doAssert match("eine übersicht und auerdem", peg"(\lower \white*)+")
    doAssert match("EINE ÜBERSICHT UND AUSSERDEM", peg"(\upper \white*)+")
    doAssert(not match("456678", peg"(\letter)+"))

    doAssert("var1 = key; var2 = key2".replacef(
      peg"\skip(\s*) {\ident}'='{\ident}", "$1<-$2$2") ==
           "var1<-keykey;var2<-key2key2")

    doAssert match("prefix/start", peg"^start$", 7)

    if "foo" =~ peg"{'a'}?.*":
      doAssert matches[0].len == 0
    else: doAssert false

    if "foo" =~ peg"{''}.*":
      doAssert matches[0] == ""
    else: doAssert false

    if "foo" =~ peg"{'foo'}":
      doAssert matches[0] == "foo"
    else: doAssert false

    let empty_test = peg"^\d*"
    let str = "XYZ"

    doAssert(str.find(empty_test) == 0)
    doAssert(str.match(empty_test))

    proc handleMatches(m: int, n: int, c: openArray[string]): string =
      result = ""

      if m > 0:
        result.add ", "

      result.add case n:
        of 2: toLowerAscii(c[0]) & ": '" & c[1] & "'"
        of 1: toLowerAscii(c[0]) & ": ''"
        else: ""

    doAssert("Var1=key1;var2=Key2;   VAR3".
      replace(peg"{\ident}('='{\ident})* ';'* \s*",
      handleMatches) == "var1: 'key1', var2: 'Key2', var3: ''")


    doAssert "test1".match(peg"""{@}$""")
    doAssert "test2".match(peg"""{(!$ .)*} $""")

    doAssert "abbb".match(peg"{a} {b} $2 $^1")
    doAssert "abBA".match(peg"{a} {b} i$2 i$^2")

    doAssert "abba".match(peg"{a} {b} $^1 {} $^1")

    block:
      let grammar = peg"""
program <- {''} stmt* $
stmt <- call / block
call <- 'call()' EOL
EOL <- \n / $
block <- 'block:' \n indBody
indBody <- {$^1 ' '+} stmt ($^1 stmt)* {}
"""
      let program = """
call()
block:
  block:
    call()
    call()
  call()
call()
"""
      var c: Captures
      doAssert program.len == program.rawMatch(grammar, 0, c)
      doAssert c.ml == 1

  pegsTest()
  static:
    pegsTest()

