
require:
   "./helpers" ->
      Body, inject-tools
   "../location" -> [<<:]
   "../util" -> camelCase

inject: mac

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; REQUIRE/PROVIDE AND GLOBALS ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

mac{"__require"}! __require_mac{context, info, form, expr} =
   req = #variable{.require} &: {top = true}
   `[^req][^expr]`

mac{"__require-m"}! __requirem_mac{context, info, form, expr} =
   req = #variable{.require} &: {top = true}
   `[^req][^expr]`


getreqs{info, req, expr, prefix = null} =

   stmts = {}
   names = {}
   var curpkg = null
   pkgs = {=}

   w{var x, args = null, wrap = null} =
      if args:
         x = `[^x][^args]`
      if wrap:
         x = wrap{x}
      x

   logName{name and [#symbol{camelCase! n}
                     \ or #variable{n}
                     \ or #value{"'" and n}]} =
      names.push with name
      pkgs[n] = curpkg
      if prefix === null:
         name
      else:
         res = match name:
            #symbol{s} -> #symbol{prefix + s} <<: name
            #variable{s} -> #symbol{prefix + s} <<: name
            #value{s} -> #symbol{prefix + s} <<: name
         res.other = name
         res

   unlogName{name} =
      match names.index-of{name.other or name}:
         === -1 -> pass
         n -> names.splice{n, 1}

   topfetch{pkg, v, args = null, wrap = null} =
      curpkg = pkg
      imp = w{`[^req]{^pkg}`, args, wrap}
      stmts.push with `let ^v = ^imp`
      v

   produce{match expr, fetch} =

      #symbol{s} ->
         fetch{#value{s}, logName{expr}}

      #value{String? s} ->
         fetch{expr, #symbol{info.gensym{}} &: {reqname = s}}

      #multi{*subp} or #data{*subp} ->
         subp each p -> produce{p, fetch}
         null

      #send{#symbol{"/"}, #data{#void{}, sym and #symbol{name}}} ->
         fetch{#value{'earlgrey-runtime/std/{name}'}, sym}

      #send{#symbol{"^"}, #data{#void{}, name}} ->
         fetch{name, #symbol{info.gensym{}} &: {reqname = name}}

      #send{#symbol{"as"}, #data{pkg, s}} ->
         produce{pkg} with {the_pkg, ignore, args = null, wrap = null} ->
            unlogName{ignore}
            fetch{the_pkg, logName{s}, args, wrap}

      #send{#symbol{"->"}, #data{pkg, subp}} ->
         pkgv = produce{pkg, fetch}
         rqn = pkgv.reqname or pkgv[1]
         produce{subp} with {_pkg, v, args = null, wrap = null} ->
            let pkg = match _pkg:
               #value{camelCase! s} -> #value{s}
               x -> x
            imp = w{`getProperty{^pkgv, ^pkg, ^=rqn}`, args, wrap}
            stmts.push{`let ^v = ^imp`}
            v

      #send{#symbol{match operator}, args} when expr.fromop ->
         "!" ->
            #data{projector, subp} = args
            produce{subp} with {the_pkg, name, args = null, var wrap = null} ->
               wrap or= [x -> x]
               fetch{the_pkg, name, args, wrap2} where wrap2{x} =
                  `[^projector]{^wrap{x}}`
         else ->
            s = match info.raw{expr}.trim{}.replace{R"^,+|,+$", ""}:
               R"^\.*/"? x -> x
               R"^(\.+)(.*)"! {_, match pfx, rest} ->
                  "." ->
                     '{pfx}/{rest}'
                  else ->
                     '{dotdots.join{""}}{rest}' where
                        dotdots = 2..pfx.length each _ -> "../"
               text ->
                  text
            name = match s.split{"/"}:
               {*, R"^[a-zA-Z0-9_\-]+$"? name} -> name
               {*, other} ->
                  throw E.syntax.module{
                     '`{other}` is not a valid symbol; use `require: "{s}" as xyz` instead'
                     {node = expr}
                  }
            sym = info.mark{#symbol{name}}
            fetch{#value{s}, logName{sym}}

      #send{name and #symbol{s}, args and #data{*_}} ->
         fetch{#value{s}, logName{name}, args}

   produce{expr, topfetch}
   {stmts, names, pkgs}
   


mac{"require"}! require_mac{context, info, form, match arg} =
   do: req = info.mark{`__require`}

   #void{} ->
      #variable{.require}

   #value{field} ->
      #send{req, arg}

   #data{expr} or expr is arg ->
      {stmts, _, _} = getreqs{info, req, expr}
      #splice{*stmts}


mac{"requireMacros"}! requiremac_mac{context, info, form, match arg} =
   #data{expr} or expr is arg ->
      req = info.mark{`__require-m`}
      {stmts, vars, pkgs} = getreqs{info, req, expr, "$MAC$"}
      body =
         #multi{*stmts, #data{*vs}} where
            vs = vars each v and [#symbol{s} or #variable{s} or #value{s}] ->
               `^v = ^[#symbol{"$MAC$" + s}]`
      body <<: arg
      the_macros = info.go{body, .parse, .eval}
      user-req = info.mark{`require`}
      declarations = #splice{*macs} where macs =
         items{the_macros} each {k, {"macro" => v} or v} when Function? v ->
            var r = #splice{}
            deps = if{v.__deps, clone{v.__deps}, {=}}
            r ++=
               items{v.__deps or {=}} each {name, ename} ->
                  ;; TODO: setting the global seems to make the REPL work, but
                  ;; it would be better not to do that...
                  mangled-name = info.gensym{"_mdep_" + name}
                  mangled = #variable{mangled-name}
                  deps[name] = mangled
                  #splice with
                     `[^user-req]: ^[pkgs[k]] -> ^[#symbol{ename}] as ^mangled`
                        \ &: {env = info.env}
                     `global[^=mangled-name] = ^mangled`
            f{ctx, info, form, expr} =
               info.deps = deps
               v.call{inject-tools{info}, expr}
            r.push with
               #declare_raw{#symbol{k} &: {env = info.env}, #macro{f}}
            r

      #restmacro with {stmts} ->
         {declarations, #multi{*stmts}}

mac{"provide"}! provide_mac{match context, _, form, e} =
   #pattern ->
      match e:
         #void{} -> `module.exports and set-var ^[#variable{.exports}]`
         else    -> `(module.exports and set-var ^[#variable{.exports}])[^e]`
   else ->
      #data{Body! {*expr}} = e
      exp = form.env.mark{`exports`}
      #sink with
         #multi ++
            expr each
               s and #symbol{name} ->
                  `[^exp][^=camelCase{name}] = ^s`
               `^s as ^[#symbol{name} or #value{name}]` ->
                  `[^exp][^=camelCase{name}] = ^s`
               other ->
                  throw E.syntax.provide with
                     "Each clause of provide must be 'sym' or 'sym as name'"

mac{"inject"}! inject_mac{context, _, form, #data{Body! {*expr}}} =
   #restmacro with {stmts} ->
      exp = form.env.mark{`exports`}
      {```
          globals: module
          module.exports{^*expr} =
             var ^exp = {=}
             ^[#multi{*stmts}]
             ^exp
       ```}

mac{"globals"}! globals_mac{context, _, form, #data{Body! {*vars}}} =
   #splice{*globs, `undefined`} where globs =
      vars each variable and #symbol{s} ->
         #splice{
            #declare_raw{variable, #variable{camelCase{s}} &: {top = true, mutable = true}}
            `if typeof{^variable} === "undefined": global[^=s] = undefined`
         }
