typedef_builder.pony

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

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

  let doc_string: NamedRule = NamedRule("a doc string" where memoize' = true)
  let method_params: NamedRule
  let members: NamedRule
  let field: NamedRule = NamedRule("a field" where memoize' = true)
  let method: NamedRule = NamedRule("a method" where memoize' = true)
  let typedef: NamedRule = NamedRule("a type definition" where memoize' = true)
  let typedef_primitive: NamedRule = NamedRule("a primitive type definition")
  let typedef_alias: NamedRule = NamedRule("a type alias")
  let typedef_class: NamedRule = NamedRule("a type definition")

  new create(
    trivia: TriviaBuilder,
    token: TokenBuilder,
    keyword: KeywordBuilder,
    literal: LiteralBuilder,
    type_type: TypeBuilder,
    expression: ExpressionBuilder,
    method_params': NamedRule,
    typedef_members': NamedRule)
  =>
    _trivia = trivia
    _token = token
    _keyword = keyword
    _literal = literal
    _type_type = type_type
    _expression = expression
    method_params = method_params'
    members = typedef_members'

    _build_doc_string()
    _build_method_params()
    _build_typedef_members()
    _build_typedef()
    _build_typedef_primitive()
    _build_typedef_alias()
    _build_typedef_class()

  fun ref error_section(allowed: Array[RuleNode], message: String)
    : RuleNode
  =>
    let eol = _trivia.eol
    let dol = _trivia.dol
    let eof = _trivia.eof

    NamedRule(
      "Error_Section",
      Conj(
        [ Neg(Disj([ Disj(allowed); Look(eof) ]))
          Plus(Conj(
            [ Neg(Disj([ dol; Look(eof) ]))
              Disj(
                [ eol
                  Single(
                    [],
                    {(d, r, c, _) =>
                      ast.NodeWith[ast.Span](_Build.info(d, r), c, ast.Span)
                    })
                ])
            ]))
          Disj([ dol; Look(eof) ])
        ],
        {(d, r, c, b) =>
          let new_children: Array[ast.Node] trn = Array[ast.Node]
          var in_span = false
          var span_start = r.start
          var span_next = r.next
          for child in c.values() do
            match child
            | let span: ast.NodeWith[ast.Span] =>
              if in_span then
                try span_next = span.src_info().next as Loc end
              else
                try
                  span_start = span.src_info().start as Loc
                  span_next = span.src_info().next as Loc
                end
                in_span = true
              end
            else
              if in_span then
                new_children.push(ast.NodeWith[ast.Span](
                  ast.SrcInfo(d.locator, span_start, span_next), [], ast.Span))
              end
              new_children.push(child)
              in_span = false
            end
          end
          if in_span then
            new_children.push(ast.NodeWith[ast.Span](
              ast.SrcInfo(d.locator, span_start, span_next), [], ast.Span))
          end

          ast.NodeWith[ast.ErrorSection](
            _Build.info(d, r), consume new_children, ast.ErrorSection(message))
        }))

  fun ref _build_doc_string() =>
    let s = Variable("s")
    doc_string.set_body(
      Bind(s, _literal.string),
      recover _TypedefActions~_doc_string(s) end)

  fun ref _build_method_params() =>
    let method_param = NamedRule("a method parameter")

    let colon = _token(ast.Tokens.colon())
    let comma = _token(ast.Tokens.comma())
    let equals = _token(ast.Tokens.equals())
    let id = _token.identifier

    // method_params <= (method_param (',' method_param)*)
    method_params.set_body(
      Conj([ method_param; Star(Conj([ comma; method_param ])) ]),
      _TypedefActions~_method_params())

    // method_param <= id (':' type_arrow)? ('=' infix)?
    let method_param_id = Variable("method_param_id")
    let method_param_constraint = Variable("method_param_constraint")
    let method_param_init = Variable("method_param_init")
    method_param.set_body(
      Conj(
        [ Bind(method_param_id, id)
          Ques(Conj(
            [ colon
              Bind(method_param_constraint, _type_type.arrow) ]))
          Ques(Conj([ equals; Bind(method_param_init, _expression.infix)])) ]),
      _TypedefActions~_method_param(
        method_param_id,
        method_param_constraint,
        method_param_init))

  fun ref _build_typedef_members() =>
    let at = _token(ast.Tokens.at())
    let colon = _token(ast.Tokens.colon())
    let cparen = _token(ast.Tokens.close_paren())
    let equal_arrow = _token(ast.Tokens.equal_arrow())
    let equals = _token(ast.Tokens.equals())
    let kwd_be = _keyword(ast.Keywords.kwd_be())
    let kwd_embed = _keyword(ast.Keywords.kwd_embed())
    let kwd_end = _keyword(ast.Keywords.kwd_end())
    let kwd_fun = _keyword(ast.Keywords.kwd_fun())
    let kwd_let = _keyword(ast.Keywords.kwd_let())
    let kwd_new = _keyword(ast.Keywords.kwd_new())
    let kwd_var = _keyword(ast.Keywords.kwd_var())
    let oparen = _token(ast.Tokens.open_paren())
    let ques = _token(ast.Tokens.ques())

    // field <= ('var' / 'let' / 'embed') id ':' type_arrow
    //          ('=' exp_infix)? doc_string?
    let field_kind = Variable("field_kind")
    let field_identifier = Variable("field_identifier")
    let field_type = Variable("field_type")
    let field_value = Variable("field_value")
    let field_doc_string = Variable("field_doc_string")
    field.set_body(
      Conj(
        [ Bind(field_kind, Disj([ kwd_var; kwd_let; kwd_embed ]))
          Bind(field_identifier, _token.identifier)
          Ques(Conj([ colon; Bind(field_type, _type_type.arrow) ]))
          Ques(Conj([ equals; Bind(field_value, _expression.infix) ]))
          Ques(Bind(field_doc_string, doc_string))
        ]),
      _TypedefActions~_field(
        field_kind,
        field_identifier,
        field_type,
        field_value,
        field_doc_string
      ))

    // method <= ('fun' / 'be' / 'new') annotation? (cap / '@')? id type_params?
    //           '(' method_params ')' (':' type_arrow)? '?'?
    //           ( doc_string? / ('=>' doc_string? exp_seq)? )
    let method_kind = Variable("method_kind")
    let method_ann = Variable("method_ann")
    let method_cap = Variable("method_cap")
    let method_raw = Variable("method_raw")
    let method_id = Variable("method_id")
    let method_tparams = Variable("method_tparams")
    let method_mparams = Variable("method_mparams")
    let method_rtype = Variable("method_rtype")
    let method_partial = Variable("method_partial")
    let method_doc_string = Variable("method_doc_string")
    let method_body = Variable("method_body")
    method.set_body(
      Conj(
        [ Bind(method_kind, Disj([ kwd_fun; kwd_be; kwd_new ]))
          Ques(Bind(method_ann, _expression.annotation))
          Ques(Disj(
            [ Bind(method_cap, _keyword.cap)
              Bind(method_raw, at) ]))
          Bind(method_id, _token.identifier)
          Ques(Bind(method_tparams, _type_type.params))
          oparen
          Ques(Bind(method_mparams, method_params))
          cparen
          Ques(Conj([ colon; Bind(method_rtype, _type_type.arrow) ]))
          Ques(Bind(method_partial, ques))
          Ques(
            Disj(
              [ Conj(
                  [ equal_arrow
                    Bind(method_body, _expression.seq) ])
                Bind(method_doc_string, doc_string)
              ]))
        ]),
      _TypedefActions~_method(
        method_kind,
        method_ann,
        method_cap,
        method_raw,
        method_id,
        method_tparams,
        method_mparams,
        method_rtype,
        method_partial,
        method_doc_string,
        method_body
      ))

    // members
    let members_fields = Variable("fields")
    let members_methods = Variable("methods")
    members.set_body(
      Conj(
        [ Look(Disj([ field; method ]))
          Bind(
            members_fields,
            Star(
              Disj(
                [ field
                  error_section(
                    [ field; method; kwd_end; typedef ],
                    ErrorMsg.src_file_expected_field_or_method())
                ])))
          Bind(
            members_methods,
            Star(
              Disj(
                [ method
                  error_section(
                    [ method; kwd_end; typedef ],
                    ErrorMsg.src_file_expected_method())
                ])))
        ]),
      _TypedefActions~_members(members_fields, members_methods))

  fun ref _build_typedef() =>
    typedef.set_body(
      Disj(
        [ typedef_primitive
          typedef_alias
          typedef_class
        ]))

  fun ref _build_typedef_primitive() =>
    let an = Variable("an")
    let id = Variable("id")
    let tp = Variable("tp")
    let cs = Variable("cs")
    let ds = Variable("ds")
    let mm = Variable("mm")

    typedef_primitive.set_body(
      Conj(
        [ _keyword(ast.Keywords.kwd_primitive())
          Ques(Bind(an, _expression.annotation))
          Bind(id, _token.identifier)
          Ques(Bind(tp, _type_type.params))
          Ques(Conj(
            [ _keyword(ast.Keywords.kwd_is())
              Bind(cs, _type_type.arrow) ]))
          Ques(Bind(ds, doc_string))
          Ques(Bind(mm, members))
        ]),
      _TypedefActions~_primitive(an, id, tp, cs, ds, mm))

  fun ref _build_typedef_alias() =>
    let kwd_type = _keyword(ast.Keywords.kwd_type())
    let kwd_is = _keyword(ast.Keywords.kwd_is())

    let alias_ann = Variable("alias_ann")
    let alias_id = Variable("alias_id")
    let alias_tparams = Variable("alias_tparams")
    let alias_type = Variable("alias_type")
    let alias_doc_string = Variable("alias_doc_string")
    typedef_alias.set_body(
      Conj(
        [ kwd_type
          Ques(Bind(alias_ann, _expression.annotation))
          Bind(alias_id, _token.identifier)
          Ques(Bind(alias_tparams, _type_type.params))
          kwd_is
          Bind(alias_type, _type_type.arrow)
          Ques(Bind(alias_doc_string, doc_string))
        ]),
      _TypedefActions~_alias(
        alias_ann, alias_id, alias_tparams, alias_type, alias_doc_string))

  fun ref _build_typedef_class() =>
    let at = _token(ast.Tokens.at())
    let kwd_actor = _keyword(ast.Keywords.kwd_actor())
    let kwd_class = _keyword(ast.Keywords.kwd_class())
    let kwd_interface = _keyword(ast.Keywords.kwd_interface())
    let kwd_is = _keyword(ast.Keywords.kwd_is())
    let kwd_struct = _keyword(ast.Keywords.kwd_struct())
    let kwd_trait = _keyword(ast.Keywords.kwd_trait())

    let class_kind = Variable("class_kind")
    let class_ann = Variable("class_ann")
    let class_raw = Variable("class_raw")
    let class_cap = Variable("class_cap")
    let class_id = Variable("class_id")
    let class_tparams = Variable("class_tparams")
    let class_constraint = Variable("class_constraint")
    let class_doc_string = Variable("class_doc_string")
    let class_members = Variable("class_members")

    typedef_class.set_body(
      Conj(
        [ Bind(
            class_kind,
            Disj(
              [ kwd_interface
                kwd_trait
                kwd_struct
                kwd_class
                kwd_actor
              ]))
          Ques(Bind(class_ann, _expression.annotation))
          Ques(Bind(class_raw, at))
          Ques(Bind(class_cap, _keyword.cap))
          Bind(class_id, _token.identifier)
          Ques(Bind(class_tparams, _type_type.params))
          Ques(Bind(class_constraint, Conj([ kwd_is; _type_type.arrow ])))
          Ques(Bind(class_doc_string, doc_string))
          Ques(Bind(class_members, members))
        ]),
      _TypedefActions~_class(
        class_kind,
        class_ann,
        class_raw,
        class_cap,
        class_id,
        class_tparams,
        class_constraint,
        class_doc_string,
        class_members))