literal_builder.pony

use ast = "../ast"
use ".."

class LiteralBuilder
  let _context: Context
  let _trivia: TriviaBuilder
  let _token: TokenBuilder
  let _keyword: KeywordBuilder

  let literal: NamedRule = NamedRule("a literal" where memoize' = true)
  let bool: NamedRule = NamedRule("a literal Bool")
  let integer: NamedRule = NamedRule("an integer literal")
  let integer_dec: NamedRule = NamedRule("a decimal integer literal")
  let integer_hex: NamedRule = NamedRule("a hexadecimal integer literal")
  let integer_bin: NamedRule = NamedRule("a binary integer literal")
  let float: NamedRule = NamedRule("a floating-point literal")
  let char: NamedRule = NamedRule("a character literal")
  let char_escape: NamedRule = NamedRule("an escaped character")
  let char_unicode: NamedRule = NamedRule("a unicode character")
  let string: NamedRule = NamedRule("a string literal")
  let string_regular: NamedRule = NamedRule("a regular string literal")
  let string_triple: NamedRule = NamedRule("a triple-quoted string literal")

  new create(
    context: Context,
    trivia: TriviaBuilder,
    token: TokenBuilder,
    keyword: KeywordBuilder)
  =>
    _context = context
    _trivia = trivia
    _token = token
    _keyword = keyword

    _build_literal()
    _build_bool()
    _build_integer()
    _build_integer_dec()
    _build_integer_hex()
    _build_integer_bin()
    _build_float()
    _build_char()
    _build_char_escape()
    _build_char_unicode()
    _build_string()
    _build_string_regular()
    _build_string_triple()

  fun ref _build_literal() =>
    literal.set_body(
      Disj(
        [ string
          char
          float
          integer
          bool
        ]))

  fun ref _build_bool() =>
    bool.set_body(
      _Build.with_post[ast.Trivia](
        Disj(
          [ _keyword(ast.Keywords.kwd_false())
            _keyword(ast.Keywords.kwd_true()) ]),
        _trivia.trivia,
        _LiteralActions~_bool()))

  fun ref _build_integer() =>
    let hex = Variable("hex")
    let bin = Variable("bin")
    let dec = Variable("dec")

    integer.set_body(
      _Build.with_post[ast.Trivia](
        Disj(
          [ Bind(hex, integer_hex)
            Bind(bin, integer_bin)
            Bind(dec, integer_dec)
          ]),
        _trivia.trivia,
        _LiteralActions~_integer(hex, bin, dec)))

  fun ref _build_integer_dec() =>
    integer_dec.set_body(
      Conj(
        [ Single(_Digits())
          Star(Single(_Digits.with_underscore()))
        ]))

  fun ref _build_integer_hex() =>
    integer_hex.set_body(
      Conj(
        [ Single("0")
          Single("xX")
          Disj(
            [ Plus(Single(_Hex.with_underscore()))
              Error(ErrorMsg.literal_integer_hex_empty())
            ])
        ]))

  fun ref _build_integer_bin() =>
    integer_bin.set_body(
      Conj(
        [ Single("0")
          Single("bB")
          Disj(
            [ Plus(Single(_Binary.with_underscore()))
              Error(ErrorMsg.literal_integer_bin_empty())
            ])
        ]))

  fun ref _build_float() =>
    let int_part = Variable("int_part")
    let frac_part = Variable("frac_part")
    let exp_sign = Variable("exp_sign")
    let exponent = Variable("exponent")

    float.set_body(
      _Build.with_post[ast.Trivia](
        Conj(
          [ Bind(int_part, integer_dec)
            Ques(
              Conj(
                [ Single(ast.Tokens.decimal_point())
                  Bind(frac_part, integer_dec) ]))
            Ques(
              Conj(
                [ Single("eE")
                  Bind(exp_sign, Ques(Single("-+")))
                  Bind(exponent, integer_dec) ])) ]),
        _trivia.trivia,
        _LiteralActions~_float(int_part, frac_part, exp_sign, exponent)))

  fun ref _build_char() =>
    let esc = Variable("esc")
    let uni = Variable("uni")
    let bod = Variable("bod")

    char.set_body(
      _Build.with_post[ast.Trivia](
        Conj(
          [ Single(ast.Tokens.single_quote())
            Disj(
              [ Bind(
                  bod,
                  Star(
                    Conj(
                      [ Neg(Single(ast.Tokens.single_quote()))
                        Disj(
                          [ Bind(uni, char_unicode)
                            Bind(esc, char_escape)
                            Single("")
                          ])
                      ])
                    where min' = 1))
                Error(ErrorMsg.literal_char_empty())
              ])
            Disj(
              [ Single(ast.Tokens.single_quote())
                Error(ErrorMsg.literal_char_unterminated())
              ])
          ]),
        _trivia.trivia,
        _LiteralActions~_char(bod, uni, esc)))

  fun ref _build_char_escape() =>
    char_escape.set_body(
      Conj(
        [ Single(ast.Tokens.backslash())
          Disj(
            [ Disj(
                [ Conj(
                    [ Single("xX")
                      Single(_Hex())
                      Single(_Hex())
                    ])
                  Single("abefnrtv\\0'\"")
                ])
              Error(ErrorMsg.literal_char_escape_invalid())
            ])
        ]))

  fun ref _build_char_unicode() =>
    char_unicode.set_body(
      Conj(
        [ Single(ast.Tokens.backslash())
          Disj(
            [ Conj(
                [ Single("u")
                  Disj(
                    [ Star(Single(_Hex()), 4, None, 4)
                      Error(ErrorMsg.literal_char_unicode_invalid())
                    ])
                ])
              Conj(
                [ Single("U")
                  Disj(
                    [ Star(Single(_Hex()), 6, None, 6)
                      Error(ErrorMsg.literal_char_unicode_invalid())
                    ])
                ])
            ])
        ]))

  fun ref _build_string() =>
    let tri = Variable("tri")
    let reg = Variable("reg")
    string.set_body(
      _Build.with_post[ast.Trivia](
        Disj(
          [ Bind(tri, string_triple)
            Bind(reg, string_regular) ]),
        _trivia.trivia,
        _LiteralActions~_string(tri, reg)))

  fun ref _build_string_regular() =>
    // we don't want to include post trivia
    string_regular.set_body(
      _string_delim(
        Literal(
          ast.Tokens.double_quote(),
          {(d, r, c, b) =>
            let string =
              recover val
                String .> concat(r.start.values(r.next))
              end
            let span = ast.NodeWith[ast.Span](_Build.info(d, r), [], ast.Span)
            ast.NodeWith[ast.Token](
              _Build.info(d, r), [ span ], ast.Token(string))
          })))

  fun ref _build_string_triple() =>
    // we don't want to include post trivia
    string_triple.set_body(
      _string_delim(
        Literal(
          ast.Tokens.triple_double_quote(),
          {(d, r, c, b) =>
            let string =
              recover val
                String .> concat(r.start.values(r.next))
              end
            let span = ast.NodeWith[ast.Span](_Build.info(d, r), [], ast.Span)
            ast.NodeWith[ast.Token](
              _Build.info(d, r), [ span ], ast.Token(string))
          })))

  fun ref _string_delim(delim: RuleNode): RuleNode =>
    Conj(
      [ delim
        Star(
          Conj(
            [ Neg(delim)
              Disj(
                [ _trivia.eol
                  _string_char_uni()
                  _string_char_esc()
                  Star(
                    Conj(
                      [ Neg(delim)
                        Neg(_trivia.eol)
                        Neg(Single(ast.Tokens.backslash()))
                        Single()
                      ]),
                    1,
                    {(d, r, c, b) =>
                      ast.NodeWith[ast.Span](
                        _Build.info(d, r), c, ast.Span)
                    })
                ])
            ]))
        Disj(
          [ delim
            Error(ErrorMsg.literal_string_unterminated())
          ])
      ])

  fun ref _string_char_uni(): RuleNode =>
    Conj(
      [ char_unicode ],
      {(d, r, c, b) => _LiteralActions._char_uni(d, r, r, c, []) })

  fun ref _string_char_esc(): RuleNode =>
    Conj(
      [ char_escape ],
      {(d, r, c, b) => _LiteralActions._char_esc(d, r, r, c, []) })