
require:
   "./location" ->
      <<:, ++:
   "./util" ->
      Body

provide:
   OperatorGroups
   SimplePriority
   parse
   ;; order
   oparse
   finalize
   DONE
   NONE
   LEFT
   RIGHT
   BOTH



transform{expr, cb} =
   tr{x} = transform{x, cb}
   result = cb.call{tr, expr} !! e ->
      match expr:
         #void{} -> #void{}
         #symbol{s} -> expr
         #value{v} -> expr
         {name, *args} ->
            {name, *args.map{tr}}
   result <<: expr



class OperatorGroups:

   constructor{groups} =

      itg = items{groups}
      @gnames = itg each {name, _} -> name
      @groups = itg each {name, descrs} ->
         [++][descrs each descr -> parse_op_description{descr}]
      @fns = {}
      @to_gid = {
         IFX = {wide = {=}, short = {=}}
         PFX = {wide = {=}, short = {=}}
         SFX = {wide = {=}, short = {=}}
      }
      enumerate{@groups} each {i, group} ->
         group each
            {fixity, width, name} ->
               @to_gid[fixity][width][name] = i
            f ->
               @fns.push with {f, i}

   get_name{o} =
      @gnames[@get{o}]

   "get"{o and {fixity, width, name}} =
      attempt = @to_gid[fixity][width][name]
      if [undefined? attempt]:
         then:
            @fns each {f, i} ->
               if f{o}:
                  return i
            throw E.syntax.unknown_operator{
               "Unknown operator: " + [String! o]
               {operator = o}
            }
         else:
            attempt


parse_op_description{match} =
   do: rx = R`[start, {?"X"}, {? in " _"}, {* not in " _Y"}
               {? in " _"}, {? "Y"}, end]`
   Function? f -> f
   rx! {_, x, w1, op, w2, y} ->
      {fixity, short} =
         match:
            when x === "" ->
               {.PFX, w2 === ""}
            when y === "" ->
               {.SFX, w1 === ""}
            otherwise ->
               {.IFX, w1 === "" or w2 === ""}
      if [w1 === "_" or w2 === "_"]:
         then: {{fixity, .short, op}, {fixity, .wide, op}}
         else: {{fixity, if{short, .short, .wide}, op}}
   other ->
      throw E.invalid_op_description with
         "Invalid operator description: " + other


;; ;; POSSIBLE BUG: do all JS engines preserve the order of properties?
;; ;; If they don't, custom might be triggered before pfx/sfx and that
;; ;; would be a problem.
;; eg_groups = OperatorGroups with {
;;     ;; High priority
;;     tokenpfx = {".Y", "#Y", "@Y"}
;;     sjuxt = {"XWHITEY"}
;;     ifx = {{match} -> [#IFX{.short, _} -> true, _ -> false]}
;;     pfx = {{match} -> [#PFX{.short, _} -> true, _ -> false]}
;;     sfx = {{match} -> [#SFX{.short, _} -> true, _ -> false]}

;;     ;; Arithmetic
;;     pow = {"X ** Y"}
;;     mul = {"X * Y", "X / Y", "X // Y", "X mod Y", "+ Y", "- Y"}
;;     add = {"X + Y", "X - Y"}
;;     cmp = {"X < Y", "X <= Y", "X == Y"
;;            "X > Y", "X >= Y", "X != Y"}

;;     ;; Collection
;;     cat = {"X ++ Y"}
;;     union = {"X & Y"}
;;     range = {"X .. Y", "X ..", ".. Y"}
;;     [in] = {"X in Y"}

;;     ;; Other
;;     wjuxt = {"X WHITE Y"}
;;     colon = {"X_:_Y"}

;;     ;; Logic
;;     [and] = ["X and Y"]
;;     [or] = ["X or Y"]
;;     [not] = ["not Y"]

;;     ;; Low priority
;;     "with" = {"X with Y"}
;;     decl = {"X -> Y", "X = Y", "X := Y", "X => Y", "X where Y", "X when Y", "X each Y"}

;;     ;; Brackets
;;     brack = {"X_,_Y"
;;              "(_Y", "[_Y", "{_Y"
;;              "X_)", "X_]", "X_}"}

;;     ;; Custom
;;     custom = {{x} -> true}
;; }


class SimplePriority:

   constructor{groups, priorities} =
      @groups = groups
      _i = 0
      tracks = {=}
      @prio = groups.gnames each name ->
         {{Array! ltracks, lp}, {Array! rtracks, rp}} = priorities[name]
         {lt, rt} = {ltracks, rtracks} each tr ->
            var rval = 0
            tr each
               .all -> rval = 2**31 - 1
               t when not tracks[t] ->
                  rval = rval |+ 2**_i
                  tracks[t] = _i++
               t ->
                  rval = rval |+ 2**tracks[t]
            rval
         {{lt, lp}, {rt, rp}}

   compare{op1, op2} =
      i1 = @groups.get{op1}
      i2 = @groups.get{op2}
      {_, {code1, ord1}} = @prio[i1]
      {{code2, ord2}, _} = @prio[i2]
      match:
         when [[code1 &+ code2] === 0] -> NONE
         when [ord1 > ord2]  -> LEFT
         when [ord1 < ord2]  -> RIGHT
         when [ord1 === ord2] -> BOTH


MAX = 1/0

eg_order = SimplePriority{eg_groups, eg_prio} where

   eg_groups = OperatorGroups with {

      "eachp" = {{match} -> [[#PFX{_, R"^each"?} -> true], [_ -> false]]}
      "each" = {{match} -> [[#IFX{_, R"^each"?} -> true], [_ -> false]]}

      sh_ifx = {{match} -> [[#IFX{.short, _} -> true], [_ -> false]]}
      sh_pfx = {{match} -> [[#PFX{.short, _} -> true], [_ -> false]]}
      sh_sfx = {{match} -> [[#SFX{.short, _} -> true], [_ -> false]]}
      wi_ifx = {{match} -> [[#IFX{.wide, _} -> true], [_ -> false]]}
      wi_pfx = {{match} -> [[#PFX{.wide, _} -> true], [_ -> false]]}
      wi_sfx = {{match} -> [[#SFX{.wide, _} -> true], [_ -> false]]}

      comma = {"X_,_Y"}
      semico = {"X_;_Y"}
      obrack = {"(_Y", "[_Y", "{_Y"}
      cbrack = {"X_)", "X_]", "X_}"}

      "withp" = {"with Y"}
      "with" = {"X with Y"}

      ;; "eachp" = {"each Y", "each* Y"}
      ;; "each" = {"X each Y", "X each* Y"}

      assign = {"X_=>_Y", "X_=_Y"
                "X_:=_Y", "X_+=_Y", "X_-=_Y", "X_*=_Y", "X_/=_Y"
                "X_<<=_Y", "X_>>=_Y", "X_>>>=_Y", "X_++=_Y", "X_?=_Y"
                "X_or=_Y", "X_and=_Y", "X_each=_Y"}
      assignp = {"=_Y", "=>_Y"}

      lbda = {"X_->_Y", "X_*->_Y", "X_@->_Y"}
      lbdap = {"->_Y", "*->_Y", "@->_Y"}

      lowprio = {"X where Y"
                 "X_!!_Y"}
      colonp = {":_Y"}

      build = {"X_%_Y"}
      buildp = {"%_Y"}

      "when" = {"X_when_Y"}
      "as" = {"X_as_Y"}
      "or" = {"X_or_Y"}
      "and" = {"X_and_Y"}
      "not" = {"not_Y"}
      type = {"X_!_Y", "X_?_Y"}
      cmp = {"X_==_Y", "X_!=_Y", "X_is_Y"
             "X_>=_Y", "X_<=_Y"
             "X_>_Y", "X_<_Y"}
      binxor = {"X_^+_Y"}
      binor = {"X_|+_Y"}
      binand = {"X_&+_Y"}
      shift = {"X_<<_Y", "X_>>_Y", "X_>>>_Y"}
      add = {"X_+_Y", "X_-_Y"}
      mul = {"X_*_Y", "X_/_Y", "X_//_Y", "X_mod_Y"}
      exp = {"X_**_Y"}
      sjuxt = {"XWHITEY"}
      wjuxt = {"X WHITE Y"}
      colon = {"X_:_Y"}

      maysend = {"X_??"}
      pfx = {"._Y", "#_Y", "@_Y"}
      when2 = {"when Y"}
      pp = {"<>_Y"}

      pipe = {"X_|>_Y"}

   }

   eg_prio = {
      comma    = {#all{5}, #all{5}}
      semico   = {#all{2}, #all{2}}
      obrack   = {#all{MAX}, #all{1}}
      cbrack   = {#all{1}, #all{MAX}}

      "with"   = {#all{1999}, #all{4}}
      ;; "with"  = {#all{11}, #all{10}}
      lowprio  = {#all{11}, #all{4}}
      lbda     = {#all{11}, #all{10}}
      assign   = {#all{11}, #all{10}}
      build    = {#all{13}, #all{10}}
      "each"   = {#all{135}, #all{10}}
      "when"   = {#all{100}, #all{101}}
      "as"     = {#all{104}, #all{105}}
      "or"     = {#all{110}, #all{111}}
      "and"    = {#all{120}, #all{121}}
      "not"    = {#all{MAX}, #all{131}}
      type     = {#all{141}, #all{140}}
      cmp      = {#all{200}, #all{201}}
      binxor   = {#all{400}, #all{401}}
      binor    = {#all{410}, #all{411}}
      binand   = {#all{420}, #all{421}}
      shift    = {#arith{500}, #arith{501}}
      add      = {#arith{550}, #arith{551}}
      mul      = {#arith{560}, #arith{561}}
      exp      = {#arith{571}, #arith{570}}

      ;; wjuxt   = {#all{1002}, #all{1001}}
      ;; colon   = {#all{1001}, #all{2}}

      wjuxt    = {#all{1000}, #all{12}}
      colon    = {#all{12}, #all{4}}

      sjuxt    = {#all{2000}, #all{2001}}

      pfx      = {#all{MAX}, #all{3000}}
      pp       = {#all{MAX}, #all{5}}
      when2    = {#all{MAX}, #all{101}}
      withp    = {#all{MAX}, #all{4}}
      eachp    = {#all{MAX}, #all{10}}
      lbdap    = {#all{MAX}, #all{10}}
      colonp   = {#all{MAX}, #all{4}}
      assignp  = {#all{MAX}, #all{10}}
      buildp   = {#all{MAX}, #all{10}}

      pipe     = {#pipe{550}, #pipe{551}}

      sh_ifx   = {#all{1800}, #all{1801}}
      maysend  = {#all{1850}, #all{MAX}}
      sh_pfx   = {#all{MAX}, #all{1901}}
      sh_sfx   = {#all{1900}, #all{MAX}}
      wi_ifx   = {#customl{900}, #customr{901}}
      wi_pfx   = {#all{MAX}, #all{901}}
      wi_sfx   = {#all{900}, #all{MAX}}

   }




;; order_map = {
;;    IFX = {
;;       wide = {
;;          ","     => {{2r1111, 1}, {2r1111, 1}}

;;          "with"  => {{2r1111, 1999}, {2r1111, 10}}
;;          "each"  => {{2r1111, 11},  {2r1111, 10}}
;;          "each?" => {{2r1111, 11},  {2r1111, 10}}
;;          "where" => {{2r1111, 11},  {2r1111, 10}}
;;          "!!"    => {{2r1111, 11},  {2r1111, 10}}
;;          "|>"    => {{2r1111, 11},  {2r1111, 10}}
;;          "->"    => {{2r1111, 11},  {2r1111, 10}}
;;          "=>"    => {{2r1111, 11},  {2r1111, 10}}
;;          "="     => {{2r1111, 11},  {2r1111, 10}}
;;          ":="    => {{2r1111, 11},  {2r1111, 10}}
;;          "+="    => {{2r1111, 11},  {2r1111, 10}}
;;          "-="    => {{2r1111, 11},  {2r1111, 10}}
;;          "*="    => {{2r1111, 11},  {2r1111, 10}}
;;          "/="    => {{2r1111, 11},  {2r1111, 10}}
;;          "<<="   => {{2r1111, 11},  {2r1111, 10}}
;;          ">>="   => {{2r1111, 11},  {2r1111, 10}}
;;          ">>>="  => {{2r1111, 11},  {2r1111, 10}}
;;          "++="   => {{2r1111, 11},  {2r1111, 10}}
;;          "?="    => {{2r1111, 11},  {2r1111, 10}}
;;          "or="   => {{2r1111, 11},  {2r1111, 10}}
;;          "and="  => {{2r1111, 11},  {2r1111, 10}}
;;          "each=" => {{2r1111, 11},  {2r1111, 10}}

;;          ;; Misc
;;          "%"     => {{2r1111, 11}, {2r1111, 10}}

;;          ;; Logical
;;          "when" => {{2r1111, 100}, {2r1111, 101}}
;;          ;; "||"   => {{2r1111, 110}, {2r1111, 111}}
;;          ;; "&&"   => {{2r1111, 120}, {2r1111, 121}}
;;          "or"   => {{2r1111, 110}, {2r1111, 111}}
;;          "and"  => {{2r1111, 120}, {2r1111, 121}}
;;          "not"  => {{2r1111, 130}, {2r1111, 131}}

;;          ;; Comparison
;;          "==" => {{2r1111, 200}, {2r1111, 201}}
;;          "!=" => {{2r1111, 200}, {2r1111, 201}}
;;          ">=" => {{2r1111, 200}, {2r1111, 201}}
;;          "<=" => {{2r1111, 200}, {2r1111, 201}}
;;          ">"  => {{2r1111, 200}, {2r1111, 201}}
;;          "<"  => {{2r1111, 200}, {2r1111, 201}}

;;          ;; Binary
;;          "^." => {{2r1111, 400}, {2r1111, 401}}
;;          "|." => {{2r1111, 410}, {2r1111, 411}}
;;          "&." => {{2r1111, 420}, {2r1111, 421}}

;;          ;; Arithmetic
;;          "<<"  => {{2r0001, 500}, {2r0001, 501}}
;;          ">>"  => {{2r0001, 500}, {2r0001, 501}}
;;          ">>>" => {{2r0001, 500}, {2r0001, 501}}
;;          "+"   => {{2r0001, 550}, {2r0001, 551}}
;;          "-"   => {{2r0001, 550}, {2r0001, 551}}
;;          "*"   => {{2r0001, 560}, {2r0001, 561}}
;;          "/"   => {{2r0001, 560}, {2r0001, 561}}
;;          "mod" => {{2r0001, 560}, {2r0001, 561}}
;;          "**"  => {{2r0001, 571}, {2r0001, 570}}

;;          ;; Others
;;          DEFAULT = {{2r1000, 900}, {2r0100, 901}}
;;          WHITE   = {{2r1111, 1002}, {2r1111, 1001}}
;;          ":"    => {{2r1111, 1001}, {2r1111, 2}}
;;       }
;;       short = {
;;          DEFAULT = {{2r1111, 1800}, {2r1111, 1801}}
;;          WHITE   = {{2r1111, 2000}, {2r1111, 2001}}
;;          ","    => {{2r1111, 1},    {2r1111, 1}}
;;          ":"    => {{2r1111, 1001}, {2r1111, 2}}
;;       }
;;    }

;;    PFX = {
;;       wide = {
;;          DEFAULT = {{2r1111, MAX}, {2r1111, 900}}
;;          "when" => {{2r1111, MAX}, {2r1111, 101}}
;;          "not"  => {{2r1111, MAX}, {2r1111, 131}}
;;          "." => {{2r1111, MAX}, {2r1111, 3000}}
;;          "#" => {{2r1111, MAX}, {2r1111, 3000}}
;;          ;; "~" => {{2r1111, MAX}, {2r1111, 3000}}
;;          "@" => {{2r1111, MAX}, {2r1111, 3000}}
;;          "<>" => {{2r1111, MAX}, {2r1111, 5}}
;;          "(" => {{2r1111, MAX}, {2r1111, 1}}
;;          "[" => {{2r1111, MAX}, {2r1111, 1}}
;;          "{" => {{2r1111, MAX}, {2r1111, 1}}
;;       }
;;       short = {
;;          DEFAULT = {{2r1111, MAX}, {2r1111, 1901}}
;;          "." => {{2r1111, MAX}, {2r1111, 3000}}
;;          "#" => {{2r1111, MAX}, {2r1111, 3000}}
;;          ;; "~" => {{2r1111, MAX}, {2r1111, 3000}}
;;          "@" => {{2r1111, MAX}, {2r1111, 3000}}
;;          "(" => {{2r1111, MAX}, {2r1111, 1}}
;;          "[" => {{2r1111, MAX}, {2r1111, 1}}
;;          "{" => {{2r1111, MAX}, {2r1111, 1}}
;;       }
;;    }

;;    SFX = {
;;       wide = {
;;          DEFAULT = {{2r1111, 901}, {2r1111, MAX}}
;;          ")" => {{2r1111, 1}, {2r1111, MAX}}
;;          "]" => {{2r1111, 1}, {2r1111, MAX}}
;;          "}" => {{2r1111, 1}, {2r1111, MAX}}
;;       }
;;       short = {
;;          DEFAULT = {{2r1111, 1900}, {2r1111, MAX}}
;;          ")" => {{2r1111, 1}, {2r1111, MAX}}
;;          "]" => {{2r1111, 1}, {2r1111, MAX}}
;;          "}" => {{2r1111, 1}, {2r1111, MAX}}
;;       }
;;    }
;; }



DONE = -1
NONE = 0
LEFT = 1
RIGHT = 2
BOTH = 3


oparse{next, order, finalize} =
   var between = finalize{next{}}
   var right_op = next{}
   var stack = {}
   var left_op = null
   var current = null
   while true:
      o =
         if [not left_op and not right_op]:
            then: DONE
            else: [not left_op and RIGHT]
                  \ or [not right_op and LEFT]
                  \ or order{left_op, right_op}

      match o:
         [=== DONE] ->
            return between

         [=== LEFT] ->
            current.push{between}
            set-var between = finalize{current}
            v = stack.pop{}
            set-var left_op = v 0
            set-var current = v 1

         [=== RIGHT] ->
            stack.push{{left_op, current}}
            set-var left_op = right_op
            set-var current = {{right_op}, between}
            set-var between = finalize{next{}}
            set-var right_op = next{}

         [=== BOTH] or [=== NONE] ->
            current[0].push{right_op}
            current.push{between}
            set-var left_op = right_op
            set-var between = finalize{next{}}
            set-var right_op = next{}
            if [o === NONE]:
               current.tainted = true

            ;; throw E.syntax.order{...} with
            ;;    "Cannot mix operators in the given order"
            ;;    {left = left_op, right = right_op}

         other ->
            throw E.should_never_happen{
               "undefined priority"
               {left = left_op, right = right_op}
            }


;; consult{{fixity, width, op}} =
;;    map = order_map[fixity][width]
;;    map[op] or map.DEFAULT


;; order{o1, o2} =
;;    {code1, ord1} = consult{o1}[1]
;;    {code2, ord2} = consult{o2}[0]
;;    match:
;;       when [[code1 &. code2] === 0] ->
;;          throw E.syntax.order{...} with
;;             "Cannot mix operators in the given order"
;;             {left = o1, right = o2}
;;       when [ord1 > ord2]  -> LEFT
;;       when [ord1 < ord2]  -> RIGHT
;;       when [ord1 === ord2] -> BOTH

finalize{match token} =

   #ID{value} -> #symbol{value} <<: token
   #ILLEGAL{value} -> #char{value} <<: token
   #NUM{value} -> #value{value} <<: token
   #STR{value} -> #value{value} <<: token
   #QUASI{value} -> #send{#symbol{"`"}, #value{value} <<: token} <<: token
   #QUAINT{value} -> #send{#symbol{"'"}, #value{value} <<: token} <<: token
   #VOID{} -> #void{} <<: token

   {var ops, *[var args]} ->
      var sumloc = ops[0].location
      ops.slice{1} each
         op ->
            set-var sumloc = sumloc ++: op
      args each
         #void{} ->
            pass
         arg ->
            set-var sumloc = sumloc ++: arg

      orig_ops = ops
      width = ops[0][1]
      set-var ops = ops each o -> o[2]
      op = ops[0]

      collapse{args} =
         var accum = {}
         args each match arg ->
            #void{} -> pass
            {brackets => ""} and #multi{*members} ->
               accum ++= members
            else ->
               accum.push with arg
         accum

      multiargs{var args} =
         result = match collapse{args}:
            {} ->
               #multi{}
            {x} ->
               ;; Force x's location to include its surrounding []s
               ;; Highlighted snippets will look strange otherwise when
               ;; then span multiple nodes: for instance, [a] + [b]
               ;;                                          ^^^^^^^
               ;; Note that <<: only sets location if it's not already set
               globals: isNaN
               if [not isNaN{sumloc.start} and not isNaN{sumloc.end}]:
                  x.location = sumloc
               x
            other -> #multi{*other}

         result <<: sumloc


      match {ops, args}:

         {{"WHITE", ":"}, {f, arg, body}} ->
            ;; if body.brackets == "":
            ;;    throw E.syntax.fix{":", expr = body}
            #send{f, #data{arg, body} <<: [arg ++: body]} <<: sumloc

         {{":"}, {f, body}} ->
            ;; if body.brackets == "":
            ;;    throw E.syntax.fix{":", expr = body}
            #send{f, #data{body} <<: body} <<: sumloc

         {{"with"}, {target, _b and Body! {*body}}} ->
            ;; if _b.brackets == "":
            ;;    throw E.syntax.fix{"with", expr = _b}
            var inserted = false
            result = transform{target} with {match expr} ->
               #symbol{"___"} -> [set-var inserted = true, #multi{*body} <<: _b]
               [x when x.fromop] and #send{f, #data{a, b}} ->
                   #send{this{f}, #data{this{a}, this{b}} <<: [a ++: b]}
               #data{*args} ->
                   tr = this
                   var res = #data
                   args each
                      #symbol{"___"} -> [set-var inserted = true, res ++= body]
                      other -> res.push with [tr{other} <<: other]
                   res <<: expr

            if [not inserted]:
               then:
                  match target:
                     #void{} ->
                        #data{*body} <<: sumloc
                     when target.fromop ->
                        #send{target, #data{*body} <<: _b} <<: sumloc
                     #send{f, orig_args and #data{*args}} ->
                        #send{f, #data{*args, *body} <<: [orig_args ++: _b]} <<: sumloc
                     #data{*args} ->
                        [target ++ body] <<: sumloc
                     other ->
                        #send{target, #data{*body} <<: _b} <<: sumloc
               else:
                  result <<: sumloc

         {Array? commas, args} when commas.every{{x} -> x == "," or x == ";"} ->
            multiargs{args} &: {brackets = ""}

         {{"[", "]"}, _} ->
            multiargs{args} &: {brackets = "[]"}

         {{"{", "}"}, _} ->
            args = collapse{args}
            #data{*args} <<: sumloc

         {{"(", ")"}, _} ->
            multiargs{args} &: {brackets = "()"}

         ;; {{.WHITE}, _} ->
         ;;    [#send{*args} &: {width = width}] <<: sumloc

         {{.WHITE}, {f, x}} ->
            res = match x:
               #multi{*args} when x.brackets == "()" ->
                  #send{f, [#data{*args} &: {brackets = "()"}] <<: x} <<: sumloc
               when x.brackets == "()" ->
                  #send{f, [#data{x} &: {brackets = "()"}] <<: x} <<: sumloc
               else ->
                  #send{f, x}
            [res &: {width = width}] <<: sumloc

         {{_}, match} ->
            {#void{}, #void{}} -> #symbol{op} <<: orig_ops[0]
            {a, b} ->
               oloc = orig_ops[0].location
               abloc = [a ++: b]
               oabloc = [orig_ops[0] ++: abloc]
               rval = #send{#symbol{op} <<: oloc
                            #data{a, b} <<: abloc} <<: [oloc ++: abloc]
               rval.fromop = true
               rval.width = width
               rval

         {op_strings, args} ->
            #mismix{[orig_ops each op -> #symbol{op[2]} <<: op], *args} <<: sumloc

            ;; throw E.syntax.mismatch{msg, tokens} where
            ;;    tokens = object with enumerate{orig_ops} each {i, op} ->
            ;;       {"token" + String{i + 1}, op}
            ;;    msg = "Mismatched/missing brackets/tokens"

   other ->
      throw E.should_never_happen{
         "unknown node (B)"
         {node = token}
      }



parse{tokens} =
   next{} = tokens.shift{}
   ;; oparse{next, order, finalize}
   oparse{next, eg_order.compare.bind{eg_order}, finalize}


