
require:
   "./util" ->
      gensym, Body
   "./pp" ->
      <>
   "./expand" ->
      jsKeywords

provide:
   Translator


join{things, sep} =
   enumerate{things} each {match, x} ->
      0 -> x
      _ -> {",", x}


js_op_table2 = {
   "+" => "+"
   "-" => "-"
   "*" => "*"
   "/" => "/"
   "mod" => "%"
   "&+" => "&"
   "|+" => "|"
   "^+" => "^"
   "~" => "~"
   "and" => "&&"
   "or" => "||"
   "not" => "!"
   "==" => "==="
   "!=" => "!=="
   "===" => "==="
   "!==" => "!=="
   "<" => "<"
   ">" => ">"
   "<=" => "<="
   ">=" => ">="
   "<<" => "<<"
   ">>" => ">>"
   ">>>" => ">>>"
   ;; "in" => " in "
   "instanceof" => " instanceof "
   "++" => "++"
   "--" => "--"
}


js_op_table = {
   ___plus = "+"
   ___minus = "-"
   ___times = "*"
   ___div = "/"
   ___mod = "%"
   ___binxor = "^"
   ___binand = "&"
   ___binor = "|"
   ___binnot = "~"
   ___and = "&&"
   ___or = "||"
   ___not = "!"
   ___is = "==="
   ___isnt = "!=="
   ___eq = "==="
   ___neq = "!=="
   ___lt = "<"
   ___gt = ">"
   ___lte = "<="
   ___gte = ">="
   ___shl = "<<"
   ___shr = ">>"
   ___shr2 = ">>>"
   ___in = " in "
   ___instanceof = " instanceof "
   ___plusplus = "++"
   ___minusminus = "--"
}



class Translator:

   constructor{prelude = null, @globvar = null} =
      @cache = {=}
      @prepend = {}
      match prelude:
         null? -> pass
         String? -> @prepend.push{prelude}
         else ->
            @prepend.push with
               @translate{___serialize_ast{prelude}, .stmt}

   register_value{v, id} =
      match @cache[id]:
         undefined? ->
            temp = #symbol{gensym{}}
            @cache[id] = temp ;; {temp, ___serialize_ast{v}}
            @prepend.push{@translate{x, .stmt}} where x =
               #declare{temp, ___serialize_ast{v}}
            temp
         name ->
            name

   register_raw{raw, id} =
      match @cache[id]:
         undefined? ->
            temp = #symbol{gensym{}}
            @cache[id] = temp
            @prepend.push{@translate{x, .stmt}} where x =
               #declare{temp, #raw{raw}}
            temp
         name ->
            name


   dump_store{} =
      ;; decls = items{@store} each {id, {name, serial}} ->
      ;;    #declare{name, serial}
      ;; @store := {=}
      ;; @locked := true
      ;; v = @translate{#multi{*decls}, .stmt}
      ;; @locked := false
      ;; v
      rval = @prepend.join{"\n"}
      @prepend = {}
      rval

   mangle{name} =
      tr = {
         "+" => "__plus__"
         "-" => "__minus__"
         "*" => "__asterisk__"
         "/" => "__slash__"
         "%" => "__percent__"
         "^" => "__caret__"
         "#" => "__hash__"
         "&" => "__amp__"
         "|" => "__pipe__"
         "@" => "__at__"
         "!" => "__bang__"
         "?" => "__qmark__"
         "=" => "__equal__"
         "<" => "__lt__"
         ">" => "__gt__"
         "~" => "__tilde__"
         "." => "__dot__"
         ":" => "__colon__"
         "'" => "__quote__"
      }
      r = {}
      for [i = 0, i < name.length, ++i]:
         c = name[i]
         r.push{tr[c] or c}
      r.join{""}

   body{orig, mode} =
      Body! {*b} = orig
      trst{stmt} = @translate{stmt, .stmt}
      match mode:
         .expr ->
            @translate{x, mode} where
               x = #send{#lambda{{}, orig}, #array{}}
         .return ->
            stmts = b.slice{0, -1}
            ret = b[b.length - 1]
            splice %
               stmts.map{trst}
               @translate{ret, .return}
         .stmt ->
            splice %
               b each x -> trst{x}
         #return{variable} ->
            stmts = b.slice{0, -1}
            ret = b[b.length - 1]
            splice %
               stmts.map{trst}
               @translate{ret, mode}
         other ->
            throw E.syntax.mode{"Unknown translate mode", {mode = mode}}

   expr{x, match mode} =
      .expr -> x
      .stmt -> splice % [x, ";"]
      .return -> splice % ["return ", x, ";"]
      #return{variable} ->
         splice %
            variable, "=", x, ";"
      other -> throw E.syntax.mode{"Unknown translate mode", {mode = mode}}

   op{op, a, b} =
      e = match {a, b}:
             {#void{}, _} ->
                splice % [op, @translate{b, .expr}]
             {_, #void{}} ->
                splice % [@translate{a, .expr}, op]
             else ->
                splice % [@translate{a, .expr}, op, @translate{b, .expr}]
      splice % ["(", e, ")"]

   translate{expr, mode, called = if{expr.called, .send, false}} =

      rval = match expr:

         #symbol{s} ->
            @expr{@mangle{s}, mode}

         #void{} ->
            @expr{"null", mode}

         #value{var v} ->
            r = match v:
               String? ->
                  repl = {
                     "\"" => "\\\""
                     "\n" => "\\n"
                     "\r" => "\\r"
                     "\b" => "\\b"
                     "\\" => "\\\\"
                  }
                  set-var v = v.replace{
                     R.g`{"\"" or "\\" or "\n" or "\r" or "\b"}`
                     {m} -> repl[m]
                  }
                  "\"" + v + "\""
               undefined? ->
                  "(void 0)"
               RegExp? ->
                  chain String{v}:
                     @slice{1, -1}
                     @replace{R.g"/", "\\/"}
                     @replace{R.g"\n", "\\n"}
                     ["/" ++ [@]] ++ "/"
               [Number? or [=== true] or [=== false] or null?] ->
                  ;; "(" + String{v} + ")"
                  String{v}
               {"::id" => id} when id ->
                  @translate{@register_value{v, id}, mode}                     
               other ->
                  throw E.cannot_serialize{
                     "Cannot serialize value"
                     {value = v}
                  }
            @expr{r, mode}

         #send{#variable{"___node"}, #value{f}} ->
            f

         #send{#symbol{"___js_fetch"} or #variable{"___js_fetch"}, #array{f, msg}} ->
            @expr{x, mode} where x =
               splice %
                  @translate{f, .expr}
                  "[", @translate{msg, .expr}, "]"

         #send{f, msg and #value{s}} when called != .send ->
            match:
               when [[typeof{s} === "string"]
                     \ and s.match{R`[start, + in "a-zA-Z_$", end]`}] ->
                  @expr{x, mode} where x =
                     splice %
                        match @translate{f, .expr}:
                           R"^\d+$"? x -> "(" + x + ")"
                           x -> x
                        "."
                        @translate{#symbol{s}, .expr}
               otherwise ->
                  @expr{x, mode} where x =
                     splice %
                        @translate{f, .expr}
                        "[", @translate{msg, .expr}, "]"

         #send{f, #array{*args}} ->
            op =
               match f:
                  #symbol{x} -> js_op_table[x]
                  #variable{x} -> js_op_table2[x]
                  _ -> null
            if op:
               @expr{@op{op, args[0], args[1]}, mode}
            else:
               @expr{x, mode} where x =
                  splice %
                     @translate{f, .expr, true}
                     "("
                     join{[args each x -> @translate{x, .expr}], ","}
                     ")"

         #send{f, msg} ->
            codevar = "send"
            @expr{x, mode} where x =
               splice %
                  codevar, "("
                  @translate{f, .expr, .send}
                  ","
                  @translate{msg, .expr}
                  if{called, ", true", ""}
                  ")"

         #array{*args} ->
            @expr{r, mode} where r =
               splice %
                  "[",
                  join{[args each x -> @translate{x, .expr}], ","}
                  "]"

         #object{*args} ->
            all_strings = args.every with {#array{match, y}} ->
               #value{v} -> true
               other -> false
            r =
               if all_strings:
                  splice %
                     "({"
                     join{args.map{f}, ","} where
                        f{#array{x, y}} =
                           a = @translate{x, .expr}
                           b = @translate{y, .expr}
                           splice % [a, ":", b]
                     "})"
               else:
                  v = gensym{}
                  splice %
                     "(function(){let ", v, "={};"
                     args each #array{x, y} ->
                        splice %
                           v, "[", @translate{x, .expr}, "]="
                           @translate{y, .expr}, ";"
                     "return ", v, "})()"
            @expr{r, mode}

         #lambda{bindings, body, #value{generator} = #value{false}} ->
            name = match expr.name:
               in jsKeywords ->
                  ""
               R"^[$_a-zA-Z0-9]*$"? ->
                  " " + expr.name
               else ->
                  ""
            @expr{r, mode} where r =
               splice %
                  "(function"
                  if{generator, "*", ""}
                  name
                  "("
                  join{[bindings each x -> @translate{x, .expr}], ","}
                  "){"
                  @body{body, .return}
                  "})"

         #if{test, pos, #value{undefined?}} and match is mode ->
               .expr ->
                  @expr{x, .expr} where x =
                     splice %
                        "(", @translate{test, .expr}
                        "?", @translate{pos, .expr}
                        ":undefined)"
               other ->
                  splice %
                     "if(", @translate{test, .expr}, "){"
                     @translate{pos, mode}, "}"

         #if{test, pos, neg} and match is mode ->
            .expr ->
               @expr{x, .expr} where x =
                  splice %
                     "(", @translate{test, .expr}
                     "?", @translate{pos, .expr}
                     ":", @translate{neg, .expr}, ")"
            other ->
               splice %
                  "if(", @translate{test, .expr}, "){"
                  @translate{pos, mode}
                  "}else{", @translate{neg, mode}, "}"


         #declare{binding, #value{undefined?}} and match is mode ->
            [.expr or .return or #return] -> throw "Invalid in expr ctx"
            other ->
               if @globvar and binding.top:
                  ""
               else:
                  splice %
                     "let ", @translate{binding, .expr}, ";"

         #declare{binding, value} and match is mode ->
            [.expr or .return or #return] -> throw "Invalid in expr ctx"
            other ->
               splice %
                  "let ", @translate{binding, .expr}
                  "=", @translate{value, .expr}, ";"

         #assign{#send{obj, msg}, rhs} ->
            @expr{x, mode} where x =
               splice %
                  "(", @translate{obj, .expr}
                  "[", @translate{msg, .expr}, "]="
                  @translate{rhs, .expr}, ")"

         #assign{lhs and #variable{_}, rhs} ->
            match mode:
               .stmt ->
                  @translate{rhs, #return{@translate{lhs, .expr}}}
               else ->
                  core =
                     splice %
                        @translate{lhs, .expr}, "="
                        @translate{rhs, .expr}
                  @expr{x, mode} where x =
                     splice %
                        "(", core, ")"

         #multi{} ->
            if{mode === .stmt, "", "null"}

         #multi{*_args} ->

            all_args = flatten{_args} where
               flatten{args} =
                  var res = {}
                  args each
                     #multi{*more} or #splice{*more} ->
                        res ++= flatten{more}
                     other -> res.push with other
                  res
            args = all_args.slice{0, -1} each match elem ->
               #value or #symbol or #variable -> continue
               x -> x
            if all_args.length > 0:
               args.push{all_args[all_args.length - 1]}

            isdecl{match} =
               #declare{variable, value} -> true
               other -> false
            match mode:
               when args.length === 1 ->
                  @translate{args[0], mode}
               .expr when not args.some{isdecl} ->
                  var xs = args.slice{0, -1} each x ->
                     @translate{x, .expr}
                  xs.push with @translate{args[args.length - 1], mode}
                  set-var xs = xs each x when x !== "" -> x
                  ;; ["(" + xs.join{","} + ")"]
                  splice % ["(", join{xs, ","}, ")"]
               _ -> @body{#multi{*args}, mode}

         #splice{*args} ->
            @translate{#multi{*args}, mode}

         #variable{s} ->
            @expr{x, mode} where x =
               if @globvar and expr.top and not s in {"this", "arguments", "typeof"}:
                  @globvar + "." + @mangle{s}
               else:
                  @mangle{s}

         #scope{vars, body} ->
            @translate{#multi{*decls, body}, mode} where
               decls = vars each v -> #declare{v, #value{undefined}}

         #js_new{value} ->
            @expr{x, mode} where x =
               splice %
                  "(new ", @translate{value, .expr}, ")"

         ;; TODO: some kind of .ignoreexpr mode is needed to
         ;;       spot usage errors for break, continue, return

         #js_break{#value{label} = #value{null}} ->
            splice %
               "break", if{label, " " + label, ""}, ";"

         #js_continue{#value{label} = #value{null}} ->
            splice %
               "continue", if{label, " " + label, ""}, ";"

         #js_return{value} ->
            splice %
               "return ", @translate{value, .expr}, ";"

         #js_yield{value, #value{all}} ->
            @expr{x, mode} where x =
               splice %
                  "(yield", if{all, "*", ""}, " "
                  @translate{value, .expr}, ")"

         #js_delete{match} ->
            #send{x, y} ->
               splice %
                  "delete ", @translate{x, .expr}, "["
                  @translate{y, .expr}, "];"
            else ->
               throw E.translate.delete{"Invalid argument for delete"}

         #js_throw{value} ->
            match mode:
               .expr ->
                  splice %
                     "(function(){throw ", @translate{value, .expr}, ";})()"
               else ->
                  splice %
                     "throw ", @translate{value, .expr}, ";"

         #js_label{#value{label}, body} and match is mode ->
            .expr -> @body{expr, .expr}
            other ->
               splice % [label, ":", @translate{body, other}]

         #js_while{test, body} and match is mode ->
            .expr -> @body{expr, .expr}
            other ->
               splice %
                  "while("
                  @translate{test, .expr}
                  "){"
                  @translate{body, .stmt}
                  "}"

         #js_for{x, y, z, body} and match is mode ->
            .expr -> @body{expr, .expr}
            other ->
               splice %
                  "for(", @translate{x, .expr}, ";"
                  @translate{y, .expr}, ";"
                  @translate{z, .expr}, "){"
                  @translate{body, .stmt}, "}"

         #js_for_in{x, y, body} and match is mode ->
            .expr -> @body{expr, .expr}
            other ->
               splice %
                  "for(", @translate{x, .expr}, " in "
                  @translate{y, .expr}, "){"
                  @translate{body, .stmt}, "}"

         #js_for_of{x, y, body} and match is mode ->
            .expr -> @body{expr, .expr}
            other ->
               splice %
                  "for(", @translate{x, .expr}, " of "
                  @translate{y, .expr}, "){"
                  @translate{body, .stmt}, "}"

         #js_class{#value{name}, super, #array{*mthds}} ->
            is-super = match super:
               #value{null?} -> false
               else -> true
            @expr{r, mode} where r =
               splice %
                  "(class ", name,
                  if is-super:
                     then: {" extends ", @translate{super, .expr}}
                     else: ""
                  "{"
                  mthds each #array{#value{type}, #value{name}, #lambda{args, body, _}} ->
                     argstr = [args each v -> @translate{v, .expr}].join(',')
                     {type, " ", name, "(", argstr, ")",
                      "{", @translate{body, .return}, "}"}
                  "})"

         #js_try{attempt, #lambda{{#symbol{v} or #variable{v}}, body, _}, finally} ->
            r = match mode:
               .expr -> @body{expr, .expr}
               other ->
                  splice %
                     "try{", @translate{attempt, .stmt},
                     "}catch(", v, "){", @translate{body, .stmt}, "}"
            match finally:
               #void{} ->
                  r
               other ->
                  splice %
                     r, "finally{", @translate{other, .stmt}, "}"

         #raw{x} -> x

         other ->
            throw other

      match rval:
         ENode? n ->
            if not n.props.origin:
               n.props.origin = expr
            n
         String? ->
            splice %
               origin = expr
               rval

