src_file_builder.pony

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

class SrcFileBuilder
  let _trivia: TriviaBuilder
  let _token: TokenBuilder
  let _keyword: KeywordBuilder
  let _literal: LiteralBuilder
  let _type_type: TypeBuilder
  let _expression: ExpressionBuilder
  var _typedef: TypedefBuilder

  let src_file: NamedRule = NamedRule("a Pony source file")
  let using: NamedRule = NamedRule("a using declaration" where memoize' = true)
  let using_pony: NamedRule = NamedRule("a Pony package using declaration")
  let using_ffi: NamedRule = NamedRule("an FFI using declaration")

  new create(
    trivia: TriviaBuilder,
    token: TokenBuilder,
    keyword: KeywordBuilder,
    literal: LiteralBuilder,
    type_type: TypeBuilder,
    expression: ExpressionBuilder,
    typedef: TypedefBuilder)
  =>
    _trivia = trivia
    _token = token
    _keyword = keyword
    _literal = literal
    _type_type = type_type
    _expression = expression
    _typedef = typedef

    _build_src_file()
    _build_using()
    _build_using_pony()
    _build_using_ffi()

  fun ref err_sec(allowed: Array[RuleNode], message: String): RuleNode =>
    _typedef.error_section(allowed, message)

  fun ref _build_src_file() =>
    let t1 = Variable("t1")
    let ds = Variable("ds")
    let us = Variable("us")
    let td = Variable("td")
    let pt = Variable("pt")

    src_file.set_body(
      Conj(
        [ // pre-trivia
          Bind(t1, Ques(_trivia.trivia))

          // zero or more docstrings
          Bind(
            ds,
            Star(
              Disj(
                [ _typedef.doc_string
                  err_sec(
                    [ _typedef.doc_string; using; _typedef.typedef ],
                    ErrorMsg.src_file_expected_docstring_using_or_typedef())
                ])))

          // zero or more usings
          Bind(
            us,
            Star(
            Disj(
              [ using
                err_sec(
                  [ using; _typedef.typedef ],
                  ErrorMsg.src_file_expected_using_or_typedef())
              ])))

          // zero or more type definitions
          Bind(
            td,
            Star(
              Disj(
                [ _typedef.typedef
                  err_sec(
                    [ _typedef.typedef ], ErrorMsg.src_file_expected_typedef())
                ])))

          //
          Bind(pt, _trivia.eof)
        ]),
        recover this~_src_file_action(t1, ds, us, td, pt) end)

  fun tag _src_file_action(
    t1: Variable,
    ds: Variable,
    us: Variable,
    td: Variable,
    pt: Variable,
    d: Data,
    r: Success,
    c: ast.NodeSeq,
    b: Bindings)
    : (ast.Node | None)
  =>
    ( let t1': ast.NodeSeqWith[ast.Trivia],
      let ds': ast.NodeSeqWith[ast.DocString],
      let us': ast.NodeSeqWith[ast.Using],
      let td': ast.NodeSeqWith[ast.Typedef] )
    =
      ( _Build.values_with[ast.Trivia](b, t1),
        _Build.values_with[ast.DocString](b, ds),
        _Build.values_with[ast.Using](b, us),
        _Build.values_with[ast.Typedef](b, td) )

    let pt' = _Build.values_with[ast.Trivia](b, pt)

    ast.NodeWith[ast.SrcFile](
      _Build.info(d, r), c, ast.SrcFile(d.locator, us', td')
      where
        pre_trivia' = t1',
        doc_strings' = ds',
        post_trivia' = pt')

  fun ref _build_using() =>
    using.set_body(
      Disj(
        [ using_ffi
          using_pony
        ]))

  fun ref _build_using_pony() =>
    let id = Variable("id")
    let pt = Variable("pt")
    let fl = Variable("fl")
    let df = Variable("df")

    using_pony.set_body(
      Conj(
        [ _keyword(ast.Keywords.kwd_use())
          Ques(
            Conj(
              [ Bind(id, _token.identifier)
                _token(ast.Tokens.equals())
              ]))
          Bind(pt, _literal.string)
          Ques(
            Conj(
              [ _keyword(ast.Keywords.kwd_if())
                Ques(Bind(fl, _keyword(ast.Keywords.kwd_not())))
                Bind(df, _token.identifier)
              ]))
        ]),
        recover this~_using_pony_action(id, pt, fl, df) end)

  fun tag _using_pony_action(
    id: Variable,
    pt: Variable,
    fl: Variable,
    df: Variable,
    d: Data,
    r: Success,
    c: ast.NodeSeq,
    b: Bindings)
    : (ast.Node | None)
  =>
    let id' = _Build.value_with_or_none[ast.Identifier](b, id)

    let pt' =
      try
        _Build.value_with[ast.LiteralString](b, pt)?
      else
        return _Build.bind_error(d, r, c, b, "SrcFile/UsingPony/LiteralString")
      end

    let def_true = try _Build.result(b, fl)? end is None
    let df' = _Build.value_with_or_none[ast.Identifier](b, df)

    ast.NodeWith[ast.Using](
      _Build.info(d, r), c, ast.UsingPony(id', pt', def_true, df'))

  fun ref _build_using_ffi() =>
    let at = _token(ast.Tokens.at())
    let comma = _token(ast.Tokens.comma())
    let cparen = _token(ast.Tokens.close_paren())
    let ellipsis = _token(ast.Tokens.ellipsis())
    let equals = _token(ast.Tokens.equals())
    let kwd_if = _keyword(ast.Keywords.kwd_if())
    let kwd_not = _keyword(ast.Keywords.kwd_not())
    let kwd_use = _keyword(ast.Keywords.kwd_use())
    let oparen = _token(ast.Tokens.open_paren())
    let ques = _token(ast.Tokens.ques())

    let use_id = Variable("use_id")
    let use_name = Variable("use_name")
    let use_targs = Variable("use_targs")
    let use_params = Variable("use_params")
    let use_ellipsis = Variable("use_ellipsis")
    let use_partial = Variable("use_partial")
    let use_def_not = Variable("use_def_not")
    let use_define = Variable("use_define")

    using_ffi.set_body(
      Conj(
        [ kwd_use
          Ques(Conj([ Bind(use_id, _token.identifier); equals ]))
          at
          Bind(use_name, Disj([ _token.identifier; _literal.string ]))
          Bind(use_targs, _type_type.args)
          oparen
          Ques(
            Disj(
              [ Bind(use_ellipsis, ellipsis)
                Conj(
                  [ Bind(use_params, _typedef.method_params)
                    Ques(Conj([ comma; Bind(use_ellipsis, ellipsis) ]))
                  ])
              ]))
          cparen
          Ques(Bind(use_partial, ques))
          Ques(
            Conj(
              [ kwd_if
                Ques(Bind(use_def_not, kwd_not))
                Bind(use_define, _token.identifier)
              ]))
        ]),
      recover
        this~_using_ffi_action(
          use_id,
          use_name,
          use_targs,
          use_params,
          use_ellipsis,
          use_partial,
          use_def_not,
          use_define)
      end)

  fun tag _using_ffi_action(
    id: Variable,
    name: Variable,
    targs: Variable,
    params: Variable,
    ellipsis: Variable,
    partial: Variable,
    def_not: Variable,
    define: Variable,
    d: Data,
    r: Success,
    c: ast.NodeSeq,
    b: Bindings)
    : (ast.Node | None)
  =>
    let id' = _Build.value_with_or_none[ast.Identifier](b, id)
    let name' =
      try
        _Build.value_with[ast.Identifier](b, name)?
      else
        try
          _Build.value_with[ast.LiteralString](b, name)?
        else
          return _Build.bind_error(d, r, c, b, "SrcFile/UsingFfi/Name")
        end
      end
    let targs' =
      try
        _Build.value_with[ast.TypeArgs](b, targs)?
      else
        return _Build.bind_error(d, r, c, b, "SrcFile/UsingFfi/TypeArgs")
      end
    let params' = _Build.value_with_or_none[ast.MethodParams](b, params)
    let ellipsis' = b.contains(ellipsis)
    let partial' = b.contains(partial)
    let def_not' = b.contains(def_not)
    let define' = _Build.value_with_or_none[ast.Identifier](b, define)

    ast.NodeWith[ast.Using](
      _Build.info(d, r),
      c,
      ast.UsingFFI(
        id',
        name',
        targs',
        params',
        ellipsis',
        partial',
        not def_not',
        define'))