Source: src/hql/transpiler/syntax/js-interop.ts, src/hql/transpiler/syntax/data-structure.ts, src/hql/transpiler/pipeline/syntax-transformer.ts, src/hql/transpiler/pipeline/hql-ast-to-hql-ir.ts
HQL compiles to JavaScript and provides forms for interacting with JavaScript APIs:
(obj .method arg) legacy syntax for method chaining(obj.method arg) preferred compact form, identical behavior?.Two forms:
Method call (second arg is a string literal = method name):
(js-call object "method" arg1 arg2)
Compiles to: object["method"](arg1, arg2) (or object.method(arg1, arg2) for valid identifiers).
Direct function call (second arg is not a string literal):
(js-call func arg1 arg2)
Compiles to: func(arg1, arg2).
Spread operators are supported in arguments.
Examples:
(var str "hello world")
(js-call str "toUpperCase") ;; => "HELLO WORLD"
(var arr [1, 2, 3, 4, 5])
(js-call arr "filter" (fn [x] (> x 2))) ;; => [3, 4, 5]
(js-call str "split" ",") ;; => ["hello world"]
;; Static method call
(js-call Array "from" [1, 2, 3])
;; Static method call on JSON
(js-call JSON "stringify" data)
(js-get object "property")
Compiles to: object["property"] (or object.property for valid identifiers).
The property can be a string literal or an expression (for computed access).
Examples:
(var obj {"name": "Alice", "age": 30})
(js-get obj "name") ;; => "Alice"
;; Nested access
(var person {"address": {"city": "NYC"}})
(var addr (js-get person "address"))
(js-get addr "city") ;; => "NYC"
;; Array indexing
(var arr [10, 20, 30])
(js-get arr 1) ;; => 20
;; Undefined properties return undefined
(js-get obj "nonexistent") ;; => undefined
(js-set object "property" value)
Compiles to: object["property"] = value (or object.property = value for valid identifiers).
Examples:
(var obj {"count": 0})
(js-set obj "count" 42)
(js-get obj "count") ;; => 42
(var obj {})
(js-set obj "newProp" "value")
(js-new Constructor (arg1 arg2))
Compiles to: new Constructor(arg1, arg2).
Arguments must be wrapped in a list (parentheses). An empty list () means no arguments.
Examples:
(var date (js-new Date (2023 11 25)))
(js-call date "getFullYear") ;; => 2023
(var arr (js-new Array (5)))
(js-get arr "length") ;; => 5
(var map (js-new Map ()))
(js-call map "set" "key" "value")
(new Constructor arg1 arg2)
Compiles to: new Constructor(arg1, arg2).
Arguments are flat (not wrapped in a list). This is the simpler form.
Examples:
(new Date 2023 11 25)
(new Array 5)
(new Map)
(js-get-invoke object "property")
Generates an IIFE that checks at runtime whether the property is a function (method) or a value, and acts accordingly. Used internally by dot-chain transformations when it is ambiguous whether a chained element is a method call or property access.
(object .method1 arg1 .method2 arg2)
The syntax transformer groups .method symbols and their following arguments into nested method calls. This is the primary way to chain methods in HQL.
Examples:
(var arr [1, 2, 3])
(arr .length) ;; => 3
(var text " hello ")
(text .trim .toUpperCase) ;; => "HELLO"
(var str "hello,world")
(str .split ",") ;; => ["hello", "world"]
;; Chaining with arguments
(var text " Hello World ")
(text .trim .toLowerCase .split " ") ;; => ["hello", "world"]
;; Pipeline style (multiline)
(arr
.filter (fn [x] (> x 3))
.map (fn [x] (* x 2))
.slice 0 3)
Optional chaining in spaced dot notation uses .?:
(obj .?method arg1) ;; => obj?.method(arg1)
(obj.method1.method2 arg)
Dots in the first symbol split it into object and method chain. Both spaced and spaceless generate identical JavaScript.
Examples:
(text.trim.toUpperCase) ;; same as (text .trim .toUpperCase)
(arr.filter (fn [x] (> x 3)).map (fn [x] (* x 2)))
(str.split ",")
Edge cases:
js/ prefix is preserved (not treated as dot notation): (js/console.log "hello")... are not treated as dot notation42.5) are not treated as dot notationarr.length evaluates to the propertySpaceless chaining caveat: When a method argument is a bare variable (not wrapped in parens), spaceless chaining is ambiguous:
;; WRONG — my-fn.filter is parsed as property access on my-fn
(arr.map my-fn.filter big?)
;; CORRECT — use spaced form or threading
(arr .map my-fn .filter big?)
(->> arr (.map my-fn) (.filter big?))
Spaceless chaining works safely when arguments are parenthesized expressions like (fn [x] ...) or (=> ...).
Optional chaining allows safe property access on potentially null/undefined values. It compiles directly to JavaScript optional chaining (?.). This is general-purpose syntax that works anywhere — in bindings, function bodies, arrow lambdas, pipelines, and expressions.
Property access:
user?.name ;; => user?.name
data?.user?.address?.city ;; => data?.user?.address?.city
Method calls:
(obj?.greet "World") ;; => obj?.greet("World")
(arr?.includes 2) ;; => arr?.includes(2)
Mixed with regular access:
company?.ceo.name ;; => company?.ceo.name
In arrow lambdas (with $0):
(items.map (=> $0?.name)) ;; safe access on each element
Combined with nullish coalescing (??):
(?? user?.name "unknown") ;; => user?.name ?? "unknown"
(?? a (?? b c)) ;; nested fallback chain
In function bodies:
(fn safe-name [x] (?? x?.name "anonymous"))
| HQL | JavaScript |
|---|---|
(js-call obj "method" arg1 arg2) | obj.method(arg1, arg2) |
(js-call func arg1) | func(arg1) |
When the method name string is a valid JS identifier, dot notation is used. Otherwise bracket notation is used.
| HQL | JavaScript |
|---|---|
(js-get obj "property") | obj.property |
(js-get obj expr) | obj[expr] |
| HQL | JavaScript |
|---|---|
(js-set obj "key" value) | obj.key = value |
| HQL | JavaScript |
|---|---|
(js-new Constructor (arg1 arg2)) | new Constructor(arg1, arg2) |
(js-new Constructor ()) | new Constructor() |
| HQL | JavaScript |
|---|---|
(new Constructor arg1 arg2) | new Constructor(arg1, arg2) |
| HQL | JavaScript |
|---|---|
(obj .method arg1 arg2) | obj.method(arg1, arg2) |
(obj .prop) | Runtime check IIFE: calls obj.prop() if function, else returns obj.prop |
(obj.method arg) | obj.method(arg) |
| HQL | JavaScript |
|---|---|
user?.name | user?.name |
(obj?.greet "World") | obj?.greet("World") |
(obj .?method arg) | obj?.method(arg) |