(.require
 [library
  [lux (.except Symbol Alias Global Declaration global function type_of undefined)
   [abstract
    ["[0]" monad (.only do)]]
   [control
    ["<>" parser (.use "[1]#[0]" monad)]
    ["[0]" io]
    ["[0]" maybe (.use "[1]#[0]" functor)]
    ["[0]" try]]
   [data
    ["[0]" product]
    ["[0]" text (.use "[1]#[0]" equivalence)
     ["%" \\format]]
    [collection
     ["[0]" list (.use "[1]#[0]" monoid monad mix)]]]
   ["[0]" meta (.only)
    ["[0]" code (.only)
     ["<[1]>" \\parser (.only Parser)]]
    ["[0]" macro (.only with_symbols)
     [syntax (.only syntax)]
     ["[0]" template]]
    [type
     ["[0]" nominal (.except #name def)]]
    ["@" target (.only)
     ["[0]" js]]]]])

... These extensions must be defined this way because importing any of the modules
... normally used when writing extensions would introduce a circular dependency
... because the Archive type depends on Binary, and that module depends on this ffi module.
(def extension_name
  (syntax (_ [])
    (do meta.monad
      [module meta.current_module_name
       unique_id meta.seed]
      (in (list (code.text (%.format module " " (%.nat unique_id))))))))

(def extension_analysis
  (template (_ <name> <parameter>)
    [{5 #1 [<name> <parameter>]}]))

(def text_analysis
  (template (_ <it>)
    [{0 #0 {5 #1 <it>}}]))

(def analysis
  (template (_ <name> <bindings> <parser> <inputs> <body>)
    [("lux def analysis" <name>
      (.function (_ name phase archive inputs)
        (.function (_ state)
          (let [<bindings> [name phase archive state]]
            (when (<code>.result <parser> inputs)
              {try.#Failure error}
              {try.#Failure (%.format "Invalid inputs for extension: " (%.text name)
                                      text.\n error)}

              {try.#Success <inputs>}
              <body>)))))]))

(def translation
  (template (_ <name> <bindings> <inputs> <body>)
    [("lux def translation" <name>
      (.function (_ name phase archive inputs)
        (.function (_ state)
          (let [<bindings> [name phase archive state]]
            (when inputs
              <inputs>
              <body>

              _
              {try.#Failure (%.format "Invalid inputs for extension: " (%.text name))})))))]))

(for @.js (with_expansions [<undefined> (..extension_name)
                            <undefined?> (..extension_name)
                            <object> (..extension_name)
                            <set> (..extension_name)]
            (these (analysis <undefined>
                     [name phase archive state]
                     <code>.end
                     _
                     {try.#Success [state (extension_analysis name (list))]})

                   (translation <undefined>
                     [name phase archive state]
                     (list)
                     {try.#Success [state js.undefined]})

                   (def .public undefined
                     (template (undefined)
                       [(.is ..Undefined (<undefined>))]))

                   (analysis <undefined?>
                     [name phase archive state]
                     <code>.any
                     it
                     (do try.monad
                       [[state it] (phase archive (` (.is .Any (, it))) state)]
                       (in [state (extension_analysis name (list it))])))

                   (translation <undefined?>
                     [name phase archive state]
                     (list it)
                     (do try.monad
                       [[state it] (phase archive it state)]
                       (in [state (js.= js.undefined it)])))

                   (def .public undefined?
                     (template (undefined? <it>)
                       [(.as .Bit (.is .Any (<undefined?> <it>)))]))

                   (analysis <object>
                     [name phase archive state]
                     (<>.some (<>.and <code>.text <code>.any))
                     it
                     (do [! try.monad]
                       [[state output] (monad.mix ! (.function (_ [key value] [state output])
                                                      (do !
                                                        [[state value] (phase archive (` (.is .Any (, value))) state)]
                                                        (in [state (list.partial value (text_analysis key) output)])))
                                                  [state (list)]
                                                  it)]
                       (in [state (extension_analysis name (list.reversed output))])))

                   (def text_synthesis
                     (template (_ <it>)
                       [{0 #0 {2 #1 <it>}}]))

                   (def (pairs it)
                     (All (_ a) (-> (List a) (List [a a])))
                     (when it
                       (list.partial left right tail)
                       (list.partial [left right] (pairs tail))
                       
                       (list)
                       (list)

                       _
                       (.undefined)))

                   (translation <object>
                     [name phase archive state]
                     (list.partial head_key head_value tail)
                     (do [! try.monad]
                       [[state output] (monad.mix !
                                                  (.function (_ [key value] [state output])
                                                    (when key
                                                      (text_synthesis key)
                                                      (do try.monad
                                                        [[state value] (phase archive value state)]
                                                        (in [state (list.partial [key value] output)]))
                                                      
                                                      _
                                                      (.undefined)))
                                                  [state (list)]
                                                  (pairs (list.partial head_key head_value tail)))]
                       (in [state (js.object (list.reversed output))])))

                   (def .public object
                     (syntax (_ [it (<>.some <code>.any)])
                       (in (list (` (.as (..Object .Any)
                                         (<object> (,* it))))))))

                   (analysis <set>
                     [name phase archive state]
                     (all <>.and <code>.text <code>.any <code>.any)
                     [field value object]
                     (do try.monad
                       [[state value] (phase archive (` (.is .Any (, value))) state)
                        [state object] (phase archive (` (.is (..Object .Any) (, object))) state)]
                       (in [state (extension_analysis name (list (text_analysis field) value object))])))

                   (translation <set>
                     [name phase archive state]
                     (list (text_synthesis field) value object)
                     (do try.monad
                       [[state value] (phase archive value state)
                        [state object] (phase archive object state)]
                       (in [state (js.set (js.the field object) value)])))

                   (def .public set
                     (syntax (_ [field <code>.any
                                 value <code>.any
                                 object <code>.any])
                       (in (list (` (.as .Any (<set> (, field) (, value) (, object))))))))
                   ))
     ... else
     (these))

(with_expansions [<constant> (for @.js "js constant"
                                  @.python .python_constant#
                                  @.lua .lua_constant#
                                  @.ruby "ruby constant")
                  <apply> (for @.js "js apply"
                               @.python .python_apply#
                               @.lua .lua_apply#
                               @.ruby "ruby apply")
                  <new> (for @.js "js object new"
                             @.python .python_apply#
                             (these))
                  <do> (for @.js "js object do"
                            @.python .python_object_do#
                            @.lua .lua_object_do#
                            @.ruby "ruby object do")
                  <get> (for @.js "js object get"
                             @.python .python_object_get#
                             @.lua .lua_object_get#
                             @.ruby "ruby object get"
                             (these))
                  <set> (for @.lua .lua_object_set#
                             @.ruby "ruby object set"
                             (these))
                  <import> (for @.python .python_import#
                                @.lua .lua_import#
                                @.ruby "ruby import"
                                (these))
                  <function> (for @.js "js function"
                                  @.python .python_function#
                                  @.lua .lua_function#
                                  (these))]
  (nominal.def .public (Object brand) Any)

  (with_expansions [<un_common> (for @.js (these [Symbol]
                                                 [Null]
                                                 [Undefined])
                                     @.python (these [None]
                                                     [Dict])
                                     @.lua (these [Nil]
                                                  [Table])
                                     @.ruby (these [Nil]))
                    <un_common> <un_common>]
    (with_template [<name>]
      [(with_expansions [<brand> (template.symbol [<name> "'"])]
         (nominal.def <brand>
           Any
           
           (type .public <name>
             (Object <brand>))))]

      [Function]
      <un_common>
      ))

  (with_expansions [<un_common> (for @.js (these [Number  Frac])
                                     @.python (these [Integer Int]
                                                     [Float   Frac])
                                     @.lua (these [Integer Int]
                                                  [Float   Frac])
                                     @.ruby (these [Integer Int]
                                                   [Float   Frac]))
                    <un_common> <un_common>]
    (with_template [<name> <type>]
      [(type .public <name>
         <type>)]

      [Boolean Bit]
      [String  Text]
      <un_common>
      ))

  (type Alias
    (Maybe Text))

  (def alias
    (Parser Alias)
    (<>.maybe (<>.after (<code>.this (' "as")) <code>.local)))

  (type Optional
    (Record
     [#optional? Bit
      #mandatory Code]))

  (def optional
    (Parser Optional)
    (let [token "?"]
      (<| (<>.and (<>.parses? (<code>.this_text token)))
          (<>.after (<>.not (<code>.this_text token)))
          <code>.any)))

  (type (Named a)
    (Record
     [#name Text
      #alias Alias
      #anonymous a]))

  (with_template [<case> <name>]
    [(def <case>
       (All (_ a) (-> (Parser a) (Parser (Named a))))
       (|>> (all <>.and
                 <name>
                 ..alias
                 )))]

    [named <code>.local]
    [anonymous (<>#in "")]
    )

  (type Output
    Optional)

  (def output
    (Parser Output)
    ..optional)

  (type Global
    (Named Output))

  (def variables
    (Parser (List Text))
    (<>.else (list) (<code>.tuple (<>.some <code>.local))))

  (def (generalized $ it)
    (All (_ a)
      (-> (-> (List Text) a a)
          (-> (Parser a) (Parser a))))
    (do <>.monad
      [variables ..variables
       it it]
      (in ($ variables it))))

  (type Input
    (Record
     [#variables (List Text)
      #parameters (List Optional)
      #io? Bit
      #try? Bit]))

  (def input
    (Parser Input)
    (all <>.and
         (<>#in (list))
         (<code>.tuple (<>.some ..optional))
         (<>.parses? (<code>.this_text "io"))
         (<>.parses? (<code>.this_text "try"))))

  (type Constructor
    (Named Input))

  (def constructor
    (Parser Constructor)
    (<| <code>.form
        (..generalized (has [#anonymous #variables]))
        (<>.after (<code>.this (' new)))
        (..anonymous ..input)))

  (type (Member a)
    (Record
     [#static? Bit
      #member a]))

  (def static!
    (Parser Any)
    (<code>.this_text "static"))

  (def (member it)
    (All (_ a)
      (-> (Parser a)
          (Parser (Member a))))
    (do [! <>.monad]
      [static? (<>.parses? ..static!)]
      (at ! each
          (|>> [#static? static?
                #member])
          it)))

  (type Field
    (Member (Named Output)))

  (def field
    (Parser Field)
    (<| <code>.form
        ..member
        ..named
        ..output))

  (type Procedure
    (Record
     [#input Input
      #output Optional]))

  (def procedure
    (Parser (Named Procedure))
    (<| (..generalized (has [#anonymous #input #variables]))
        ..named
        (all <>.and
             ..input
             ..optional
             )))

  (type Method
    (Member (Named Procedure)))

  (def method
    (Parser Method)
    (<| <code>.form
        ..member
        ..procedure))

  (`` (`` (type Sub
            (Variant
             (,, (for @.lua (,, (these))
                      @.ruby (,, (these))
                      {#Constructor Constructor}))
             {#Field Field}
             {#Method Method}))))

  (`` (`` (def sub
            (Parser Sub)
            (all <>.or
                 (,, (for @.lua (,, (these))
                          @.ruby (,, (these))
                          ..constructor))
                 ..field
                 ..method
                 ))))

  (def parameters
    (-> (List Optional) (List Optional))
    (|>> list.enumeration
         (list#each (.function (_ [idx [optional? type]])
                      [#optional? optional?
                       #mandatory (|> idx %.nat code.local)]))))

  (def (output_type it)
    (-> Optional Code)
    (if (the #optional? it)
      (` (.Maybe (, (the #mandatory it))))
      (the #mandatory it)))

  (`` (with_template [<lux_it> <host_it>
                      <lux_?> <host_?>]
        [(def .public (<lux_it> _)
           (-> Any Nothing)
           (as_expected (<host_it>)))

         (def .public <lux_?>
           (-> Any Bit)
           (|>> <host_?>))

         (template.with_locals [g!it]
           (these (def g!it' (' g!it))
                  (def (host_optional it)
                    (-> Optional Code)
                    (.if (.the #optional? it)
                      (` (.when (, (the #mandatory it))
                           {.#Some (, g!it')}
                           (, g!it')

                           {.#None}
                           (<host_it>)))
                      (the #mandatory it)))

                  (def (lux_optional it output)
                    (-> Optional Code Code)
                    (` (.let [(, g!it') (, output)]
                         (, (if (the #optional? it)
                              (` (.if (<host_?> (, g!it'))
                                   {.#None}
                                   {.#Some (, g!it')}))
                              (` (.if (.not (<host_?> (, g!it')))
                                   (, g!it')
                                   (.panic! "Invalid output."))))))))))]

        (,, (for @.js [null "js object null"
                       null? "js object null?"]
                 @.python [none .python_object_none#
                           none? .python_object_none?#]
                 @.lua [nil .lua_object_nil#
                        nil? .lua_object_nil?#]
                 @.ruby [nil "ruby object nil"
                         nil? "ruby object nil?"]))
        ))

  (type Declaration
    [Text (List Text)])

  (type Namespace
    Text)

  (type Class
    (Record
     [#declaration Declaration
      #class_alias Alias
      #namespace Namespace
      #members (List Sub)]))

  (def class
    (Parser Class)
    (all <>.and
         (<>.either (<>.and <code>.local
                            (<>#in (list)))
                    (<code>.form (<>.and <code>.local
                                         (<>.some <code>.local))))
         ..alias
         <code>.text
         (<>.some ..sub)))

  (type Import
    (Variant
     {#Class Class}
     {#Procedure (Named Procedure)}
     {#Global Global}))

  (def importP
    (Parser Import)
    (all <>.or
         ..class
         (<code>.form ..procedure)
         (<code>.form (..named ..output))))

  (def (input_type input :it:)
    (-> Input Code Code)
    (let [:it: (if (the #try? input)
                 (` (.Either .Text (, :it:)))
                 :it:)]
      (if (the #io? input)
        (` (io.IO (, :it:)))
        :it:)))

  (def (input_term input term)
    (-> Input Code Code)
    (let [term (if (the #try? input)
                 (` (.try (, term)))
                 term)]
      (if (the #io? input)
        (` (io.io (, term)))
        term)))

  (def (procedure_definition import! source it)
    (-> (List Code) Code (Named Procedure) Code)
    (let [g!it (|> (the #alias it)
                   (maybe.else (the #name it))
                   code.local)
          g!variables (list#each code.local (the [#anonymous #input #variables] it))
          input (the [#anonymous #input] it)
          :parameters: (the #parameters input)
          g!parameters (..parameters :parameters:)
          :output: (the [#anonymous #output] it)
          :input:/* (when :parameters:
                      {.#End}
                      (list (` []))
                      
                      parameters
                      (list#each ..output_type :parameters:))]
      (` (.def ((, g!it) (,* (when g!parameters
                               {.#End} (list g!it)
                               _ (list#each (the #mandatory) g!parameters))))
           (.All ((, g!it) (,* g!variables))
             (-> (,* :input:/*)
                 (, (|> :output:
                        ..output_type
                        (..input_type input)))))
           (.exec
             (,* import!)
             (.as_expected
              (, (<| (..input_term input)
                     (..lux_optional :output:)
                     (` (<apply> (.as_expected (, source))
                                 [(,* (list#each ..host_optional g!parameters))]))))))))))

  (def (namespaced namespace class alias member)
    (-> Namespace Text Alias Text Text)
    (|> namespace
        (text.replaced "[1]" (maybe.else class alias))
        (text.replaced "[0]" member)))

  (def class_separator ".")

  (def host_path
    (text.replaced .module_separator ..class_separator))

  (for @.js (these)
       (def (imported class)
         (-> Text Code)
         (when (text.all_split_by .module_separator class)
           {.#Item head tail}
           (list#mix (.function (_ sub super)
                       (` (<get> (, (code.text sub))
                                 (.as (..Object .Any)
                                      (, super)))))
                     (` (<import> (, (code.text head))))
                     tail)
           
           {.#End}
           (` (<import> (, (code.text class)))))))

  (def (global_definition import! it)
    (-> (List Code) Global Code)
    (let [g!name (|> (the #alias it)
                     (maybe.else (the #name it))
                     code.local)
          :output: (the #anonymous it)]
      (` (.def (, g!name)
           (, (..output_type :output:))
           (.exec
             (,* import!)
             (.as_expected
              (, (<| (lux_optional :output:)
                     (` (<constant> (, (code.text (..host_path (the #name it))))))))))))))
  
  (for @.lua (these)
       @.ruby (these)
       (def (constructor_definition [class_name class_parameters] alias namespace it)
         (-> Declaration Alias Namespace Constructor Code)
         (let [g!it (|> it
                        (the #alias)
                        (maybe.else "new")
                        (..namespaced namespace class_name alias)
                        code.local)
               input (the #anonymous it)
               g!input_variables (list#each code.local (the #variables input))
               :parameters: (the #parameters input)
               g!parameters (..parameters :parameters:)
               g!class_variables (list#each code.local class_parameters)
               g!class (` ((, (code.local (maybe.else class_name alias))) (,* g!class_variables)))
               :output: [#optional? false #mandatory g!class]
               unquantified_type (` (.-> (,* (when :parameters:
                                               (list)
                                               (list (` .Any))
                                               
                                               _
                                               (list#each ..output_type :parameters:)))
                                         (, (|> :output:
                                                ..output_type
                                                (..input_type input)))))
               quantified_type (when (list#composite g!class_variables g!input_variables)
                                 (list)
                                 unquantified_type

                                 _
                                 (` (.All ((, g!it) (,* g!class_variables) (,* g!input_variables))
                                      (, unquantified_type))))]
           (` (.def ((, g!it) (,* (when g!parameters
                                    {.#End} (list g!it)
                                    _ (list#each (the #mandatory) g!parameters))))
                (, quantified_type)
                (.as_expected
                 (, (<| (..input_term input)
                        (..lux_optional :output:)
                        (` (<new> (, (for @.js (` (<constant> (, (code.text (..host_path class_name)))))
                                          @.python (` (.as ..Function
                                                           (, (..imported class_name))))))
                                  [(,* (list#each ..host_optional g!parameters))]))))))))))

  (def (optional_value type value)
    (-> Optional Code Optional)
    [#optional? (the #optional? type)
     #mandatory value])
  
  (def (static_field_definition import! [class_name class_parameters] alias namespace it)
    (-> (List Code) Declaration Alias Namespace (Named Output) Code)
    (let [field (the #name it)
          g!it (|> (the #alias it)
                   (maybe.else field)
                   (..namespaced namespace class_name alias)
                   code.local)
          :field: (the #anonymous it)
          get (` (.as (io.IO (, (..output_type :field:)))
                      (io.io (, (<| (lux_optional :field:)
                                    (for @.js (` (<constant> (, (code.text (%.format (..host_path class_name) "." field)))))
                                         @.ruby (` (<constant> (, (code.text (%.format (..host_path class_name) "::" field)))))
                                         ... else
                                         (` (<get> (, (code.text field))
                                                   (, (..imported class_name))))))))))
          set (` (.as (io.IO .Any)
                      (io.io (, (for @.js (` (..set (, (code.text field))
                                                    (, (host_optional (optional_value :field: (` ((,' ,) (, g!it))))))
                                                    (.as (..Object .Any)
                                                         (<constant> (, (code.text (..host_path class_name)))))))
                                     @.ruby (` (<set> (, (code.text field))
                                                      (, (host_optional (optional_value :field: (` ((,' ,) (, g!it))))))
                                                      (<constant> (, (code.text (..host_path class_name))))))
                                     @.python (` (<apply> (<constant> "setattr")
                                                          [(, (..imported class_name))
                                                           (, (code.text field))
                                                           (, (host_optional (optional_value :field: (` ((,' ,) (, g!it))))))]))
                                     ... else
                                     (` (<set> (, (code.text field))
                                               (, (host_optional (optional_value :field: (` ((,' ,) (, g!it))))))
                                               (, (..imported class_name)))))))))]
      (` (def (, g!it)
           (syntax ((, g!it) [(, g!it) (<>.maybe <code>.any)])
             (.at meta.monad (,' in)
                  (.list (`' (.exec
                               (,* import!)
                               ((,' ,) (when (, g!it)
                                         {.#None}
                                         (`' (, get))

                                         {.#Some (, g!it)}
                                         (`' (, set)))))))))))))

  (def (virtual_field_definition [class_name class_parameters] alias namespace it)
    (-> Declaration Alias Namespace (Named Output) Code)
    (let [name (the #name it)
          g!it (|> (the #alias it)
                   (maybe.else name)
                   (..namespaced namespace class_name alias)
                   code.local)
          path (%.format (..host_path class_name) "." name)
          :field: (the #anonymous it)
          g!variables (list#each code.local class_parameters)
          g!class (` ((, (code.local (maybe.else class_name alias))) (,* g!variables)))]
      (` (.def ((, g!it) (, g!it))
           (.All ((, g!it) (,* g!variables))
             (.-> (, g!class)
                  (, (..output_type :field:))))
           (.as_expected
            (, (<| (lux_optional :field:)
                   (` (<get> (, (code.text name)) (, g!it))))))))))

  (def (field_definition import! class alias namespace it)
    (-> (List Code) Declaration Alias Namespace Field Code)
    (if (the #static? it)
      (..static_field_definition import! class alias namespace (the #member it))
      (..virtual_field_definition class alias namespace (the #member it))))

  (def (static_method_definition import! [class_name class_parameters] alias namespace it)
    (-> (List Code) Declaration Alias Namespace (Named Procedure) Code)
    (let [method (the #name it)
          name (|> (the #alias it)
                   (maybe.else (the #name it))
                   (..namespaced namespace class_name alias))]
      (|> it
          (has #alias {.#Some name})
          (..procedure_definition import!
                                  (for @.js (` (<constant> (, (code.text (%.format (..host_path class_name) "." method)))))
                                       @.ruby (` (<constant> (, (code.text (%.format (..host_path class_name) "::" method)))))
                                       (` (<get> (, (code.text method))
                                                 (.as (..Object .Any)
                                                      (, (..imported class_name))))))))))

  (def (virtual_method_definition [class_name class_parameters] alias namespace it)
    (-> Declaration Alias Namespace (Named Procedure) Code)
    (let [method (the #name it)
          g!it (|> (the #alias it)
                   (maybe.else method)
                   (..namespaced namespace class_name alias)
                   code.local)
          procedure (the #anonymous it)
          input (the #input procedure)
          g!input_variables (list#each code.local (the #variables input))
          :parameters: (the #parameters input)
          g!parameters (..parameters :parameters:)
          g!class_variables (list#each code.local class_parameters)
          g!class (` ((, (code.local (maybe.else class_name alias))) (,* g!class_variables)))
          :output: (the #output procedure)]
      (` (.def ((, g!it) (,* (list#each (the #mandatory) g!parameters)) (, g!it))
           (.All ((, g!it) (,* g!class_variables) (,* g!input_variables))
             (.-> (,* (list#each ..output_type :parameters:))
                  (, g!class)
                  (, (|> :output:
                         ..output_type
                         (..input_type input)))))
           (.as_expected
            (, (<| (..input_term input)
                   (..lux_optional :output:)
                   (` (<do> (, (code.text method))
                            (, g!it)
                            [(,* (list#each ..host_optional g!parameters))])))))))))

  (def (method_definition import! class alias namespace it)
    (-> (List Code) Declaration Alias Namespace Method Code)
    (if (the #static? it)
      (static_method_definition import! class alias namespace (the #member it))
      (virtual_method_definition class alias namespace (the #member it))))

  (def .public import
    (syntax (_ [host_module (<>.maybe <code>.text)
                it ..importP])
      (let [host_module_import! (is (List Code)
                                    (when host_module
                                      {.#Some host_module}
                                      (list (` (<import> (, (code.text host_module)))))

                                      {.#None}
                                      (list)))]
        (when it
          {#Global it}
          (in (list (..global_definition host_module_import! it)))

          {#Procedure it}
          (in (list (..procedure_definition host_module_import!
                                            (` (<constant> (, (code.text (..host_path (the #name it))))))
                                            it)))

          {#Class it}
          (let [class (the #declaration it)
                alias (the #class_alias it)
                [class_name class_parameters] class
                namespace (the #namespace it)
                g!class_variables (list#each code.local class_parameters)
                declaration (` ((, (code.local (maybe.else class_name alias)))
                                (,* g!class_variables)))]
            (in (list.partial (` (.type (, declaration)
                                   (..Object (.Nominal (, (code.text (..host_path class_name)))
                                                       [(,* g!class_variables)]))))
                              (list#each (.function (_ member)
                                           (`` (`` (when member
                                                     (,, (for @.lua (,, (these))
                                                              @.ruby (,, (these))
                                                              (,, (these {#Constructor it}
                                                                         (..constructor_definition class alias namespace it)))))
                                                     
                                                     {#Field it}
                                                     (..field_definition host_module_import! class alias namespace it)
                                                     
                                                     {#Method it}
                                                     (..method_definition host_module_import! class alias namespace it)))))
                                         (the #members it)))))
          ))))

  (for @.ruby (these)
       (def .public function
         (syntax (_ [[self inputs] (<code>.form
                                    (all <>.and
                                         <code>.local
                                         (<code>.tuple (<>.some (<>.and <code>.any <code>.any)))))
                     type <code>.any
                     term <code>.any])
           (in (list (` (.<| (.as ..Function)
                             (<function> (, (code.nat (list.size inputs))))
                             (.as (.-> [(,* (list.repeated (list.size inputs) (` .Any)))]
                                       .Any))
                             (.is (.-> [(,* (list#each product.right inputs))]
                                       (, type)))
                             (.function ((, (code.local self)) [(,* (list#each product.left inputs))])
                               (, term)))))))))

  (for @.js (these (def .public type_of
                     (template (type_of object)
                       [("js type-of" object)]))

                   (def .public global
                     (syntax (_ [type <code>.any
                                 [head tail] (<code>.tuple (<>.and <code>.local (<>.some <code>.local)))])
                       (with_symbols [g!_]
                         (let [global (` ("js constant" (, (code.text head))))]
                           (when tail
                             {.#End}
                             (in (list (` (is (.Maybe (, type))
                                              (when (..type_of (, global))
                                                "undefined"
                                                {.#None}

                                                (, g!_)
                                                {.#Some (as (, type) (, global))})))))
                             
                             {.#Item [next tail]}
                             (let [separator "."]
                               (in (list (` (is (.Maybe (, type))
                                                (when (..type_of (, global))
                                                  "undefined"
                                                  {.#None}

                                                  (, g!_)
                                                  (..global (, type) [(, (code.local (%.format head "." next)))
                                                                      (,* (list#each code.local tail))]))))))))))))

                   (def !defined?
                     (template (_ <global>)
                       [(.when (..global Any <global>)
                          {.#None}
                          .false

                          {.#Some _}
                          .true)]))

                   (with_template [<name> <global>]
                     [(def .public <name>
                        Bit
                        (!defined? <global>))]

                     [on_browser? [window]]
                     [on_nashorn? [java lang Object]]
                     )

                   (def .public on_node_js?
                     Bit
                     (|> (..global (Object Any) [process])
                         (maybe#each (|>> []
                                          ("js apply" ("js constant" "Object.prototype.toString.call"))
                                          (as Text)
                                          (text#= "[object process]")))
                         (maybe.else false))))
       (these))
  )