Implementation: Built-in operators (transpiler core) + macros (core.hql)
HQL provides operators in prefix notation (Lisp-style):
+, -, *, /, %, **<, >, <=, >=, ===, ==, !==, !=and, or, not (word-form macros) / &&, ||, ! (symbol-form) / ?? (nullish coalescing)? (conditional expression)&, |, ^, ~, <<, >>, >>>= (simple) / +=, -=, *=, /=, %=, **= (compound) / &=, |=, ^=, <<=, >>=, >>>= (bitwise) / ??=, &&=, ||= (logical)typeof, instanceof, in, delete, void123n syntax for arbitrary-precision integers(+ 10 20) // => 30
(+ 10.5 20.3) // => 30.8
(+ 1 2 3 4 5) // => 15 (variadic)
(- 50 30) // => 20
(* 6 7) // => 42
(/ 100 5) // => 20
(% 17 5) // => 2
(** 2 10) // => 1024
// Unary usage
(+ 5) // => 5 (unary plus)
(- 5) // => -5 (unary negation)
// Nested
(+ (* 2 3) (- 10 5)) // => 11
(< 5 10) // => true
(> 10 5) // => true
(<= 10 10) // => true
(>= 15 10) // => true
(=== 42 42) // => true (strict equality)
(== 42 "42") // => true (loose equality, type coercion)
(!== 10 20) // => true (strict inequality)
(!= 10 20) // => true (loose inequality)
All comparison operators require exactly 2 arguments.
Word-form (macros expanding to symbol-form):
(and true true) // => true (expands to &&)
(and true false) // => false
(or true false) // => true (expands to ||)
(or false false) // => false
(not true) // => false (expands to (if value false true))
(not false) // => true
Symbol-form (direct operators):
(&& true true) // => true
(|| true false) // => true
(! true) // => false
Nullish coalescing:
(?? null "default") // => "default"
(?? undefined "default") // => "default"
(?? 0 "default") // => 0 (0 is not nullish)
(?? "" "default") // => "" (empty string is not nullish)
(?? false "default") // => false (false is not nullish)
&&, ||, ?? support variadic chaining: (&& a b c) compiles to a && b && c.
// Syntax: (? condition then-value else-value)
(? true "yes" "no") // => "yes"
(? (> 5 3) "greater" "lesser") // => "greater"
(+ 10 (? true 5 3)) // => 15
// Nested
(? (< x 0) "negative"
(? (=== x 0) "zero" "positive"))
Requires exactly 3 arguments (condition, then, else). Follows JavaScript truthiness rules.
(& 5 3) // => 1 (0101 & 0011 = 0001)
(| 5 3) // => 7 (0101 | 0011 = 0111)
(^ 5 3) // => 6 (0101 ^ 0011 = 0110)
(~ 5) // => -6 (bitwise NOT, unary)
(<< 5 2) // => 20
(>> 20 2) // => 5
(>>> -1 0) // => 4294967295
~ is unary (1 argument). All others require exactly 2 arguments.
Simple assignment:
(= x 10) // Assigns 10 to x
(= obj.prop 42) // Assigns to member expression
= is assignment only. It cannot be used for equality comparison. Assigning to a literal or expression result is an error.
Compound assignment:
(+= x 5) // x += 5
(-= x 3) // x -= 3
(*= x 2) // x *= 2
(/= x 4) // x /= 4
(%= x 3) // x %= 3
(**= x 2) // x **= 2
Bitwise assignment:
(&= flags 0xFF) // flags &= 0xFF
(|= flags 0x01) // flags |= 0x01
(^= mask 0xAA) // mask ^= 0xAA
(<<= x 1) // x <<= 1
(>>= x 1) // x >>= 1
(>>>= x 1) // x >>>= 1
Logical assignment:
(??= x "default") // x ??= "default"
(&&= x value) // x &&= value
(||= x fallback) // x ||= fallback
All assignment operators support simple identifiers and dot-notation member expressions (e.g., obj.prop, obj.a.b.c).
(typeof 42) // => "number"
(typeof "hello") // => "string"
(typeof true) // => "boolean"
(typeof undefined) // => "undefined"
(instanceof date Date) // => true/false
(in "name" obj) // => true/false
(delete obj.prop) // Removes property
(void 0) // => undefined
typeof, delete, void are unary (1 argument). instanceof, in are binary (2 arguments).
Operators can be used as values -- passed to higher-order functions, stored in variables, returned from functions:
(reduce + 0 [1 2 3 4 5]) // => 15
(reduce * 1 [1 2 3 4 5]) // => 120
(reduce && true [true true false]) // => false
(reduce || false [false false true]) // => true
(let add-fn +)
(add-fn 10 20) // => 30
(let ops [+ - * /])
(map (fn [op] (op 10 5)) ops) // => [15, 5, 50, 2]
Supported first-class operators: +, -, *, /, %, **, ===, ==, !==, !=, <, >, <=, >=, &&, ||, !, ~, &, |, ^, <<, >>, >>>.
When an operator appears in value position (not as first element of a call), it is converted to a function at runtime using __hql_get_op.
42 // Integer
3.14159 // Float
-42 // Negative
"Hello, HQL!" // String
"" // Empty string
true // Boolean
false // Boolean
null // Null
undefined // Undefined
123n // => 123n
9007199254740993n // => 9007199254740993n (beyond MAX_SAFE_INTEGER)
0n // => 0n
The parser recognizes the NNNn suffix and internally converts it to (bigint-literal "NNN"), which compiles to JavaScript NNNn.
(+ "Hello, " "World!") // => "Hello, World!" (string concatenation)
(var str "Hello")
str.length // => 5
(str.charAt 1) // => "e"
(+ 10 20) // => 10 + 20
(< 5 10) // => 5 < 10
(and true false) // => true && false (macro expansion)
(&& true false) // => true && false (direct)
(! x) // => !x
(~ x) // => ~x
(typeof x) // => typeof x
(instanceof a B) // => a instanceof B
(? c t e) // => c ? t : e
(+= x 5) // => x += 5
(??= x 10) // => x ??= 10
123n // => 123n
Arithmetic operators (+, -, *, /, %, **) and logical operators (&&, ||, ??) support variadic arguments by chaining:
(+ 1 2 3 4 5) // => 1 + 2 + 3 + 4 + 5
(&& a b c) // => a && b && c
HQL follows JavaScript semantics:
(+ "5" 5) // => "55" (string concatenation)
(+ 5 5) // => 10 (numeric addition)
| Operator | HQL | JavaScript | Arity |
|---|---|---|---|
+ | (+ a b) | a + b | 1+ |
- | (- a b) | a - b | 1+ |
* | (* a b) | a * b | 1+ |
/ | (/ a b) | a / b | 1+ |
% | (% a b) | a % b | 1+ |
** | (** a b) | a ** b | 1+ |
With 1 argument: + and - produce unary expressions; * and / use identity element (1); % and ** use identity element (0).
| Operator | HQL | JavaScript | Description |
|---|---|---|---|
< | (< a b) | a < b | Less than |
> | (> a b) | a > b | Greater than |
<= | (<= a b) | a <= b | Less or equal |
>= | (>= a b) | a >= b | Greater or equal |
=== | (=== a b) | a === b | Strict equality |
== | (== a b) | a == b | Loose equality |
!== | (!== a b) | a !== b | Strict inequality |
!= | (!= a b) | a != b | Loose inequality |
All require exactly 2 arguments.
| Operator | HQL | JavaScript | Arity |
|---|---|---|---|
and | (and a b) | a && b | 1+ |
or | (or a b) | a || b | 1+ |
not | (not a) | a ? false : true | 1 |
&& | (&& a b) | a && b | 2+ |
|| | (|| a b) | a || b | 2+ |
! | (! a) | !a | 1 |
?? | (?? a b) | a ?? b | 2+ |
and and or are macros (variadic, expand recursively). not is a macro that expands to (if value false true), which compiles to value ? false : true.
| Operator | HQL | JavaScript | Arity |
|---|---|---|---|
& | (& a b) | a & b | 2 |
| | (| a b) | a | b | 2 |
^ | (^ a b) | a ^ b | 2 |
~ | (~ a) | ~a | 1 |
<< | (<< a b) | a << b | 2 |
>> | (>> a b) | a >> b | 2 |
>>> | (>>> a b) | a >>> b | 2 |
| Operator | HQL | JavaScript | Description |
|---|---|---|---|
= | (= a b) | a = b | Assignment |
+= | (+= a b) | a += b | Add and assign |
-= | (-= a b) | a -= b | Subtract and assign |
*= | (*= a b) | a *= b | Multiply and assign |
/= | (/= a b) | a /= b | Divide and assign |
%= | (%= a b) | a %= b | Remainder and assign |
**= | (**= a b) | a **= b | Exponent and assign |
&= | (&= a b) | a &= b | Bitwise AND assign |
|= | (|= a b) | a |= b | Bitwise OR assign |
^= | (^= a b) | a ^= b | Bitwise XOR assign |
<<= | (<<= a b) | a <<= b | Left shift assign |
>>= | (>>= a b) | a >>= b | Right shift assign |
>>>= | (>>>= a b) | a >>>= b | Unsigned right shift assign |
??= | (??= a b) | a ??= b | Nullish coalescing assign |
&&= | (&&= a b) | a &&= b | Logical AND assign |
||= | (||= a b) | a ||= b | Logical OR assign |
| Operator | HQL | JavaScript | Arity |
|---|---|---|---|
typeof | (typeof a) | typeof a | 1 |
instanceof | (instanceof a Type) | a instanceof Type | 2 |
in | (in key obj) | key in obj | 2 |
delete | (delete obj.prop) | delete obj.prop | 1 |
void | (void expr) | void expr | 1 |
| Operator | HQL | JavaScript | Arity |
|---|---|---|---|
? | (? cond then else) | cond ? then : else | 3 |
HQL uses explicit parentheses -- there is no implicit precedence:
(+ 2 (* 3 4)) // => 14 (explicit: 2 + (3 * 4))
(* (+ 2 3) 4) // => 20 (explicit: (2 + 3) * 4)
The transpiler uses a precedence system internally for correct JavaScript parenthesization (defined in codegen/precedence.ts).
HQL Source
|
S-expression Parser (tokenizes 123n as BigInt)
|
Macro Expansion (and/or/not -> &&/||/if)
|
AST-to-IR (operator recognition in primitive.ts)
|
IR-to-TypeScript (infix conversion, precedence)
|
JavaScript/TypeScript Output
src/hql/transpiler/keyword/primitives.ts (PRIMITIVE_OPS, FIRST_CLASS_OPERATORS) and src/hql/transpiler/syntax/primitive.ts (COMPOUND_ASSIGN_OPS_SET)src/hql/transpiler/syntax/primitive.ts (transformArithmeticOp, transformComparisonOp, transformLogicalOp, transformBitwiseOp, transformTypeOp, transformCompoundAssignment, transformLogicalAssignment, transformEqualsOperator)src/hql/transpiler/syntax/conditional.ts (transformTernary)src/hql/transpiler/pipeline/hql-ast-to-hql-ir.ts (bigint-literal handler)__hql_get_op runtime helpersrc/hql/lib/macro/core.hql (and, or, not)src/hql/transpiler/pipeline/ir-to-typescript.ts (generateBinaryExpression, generateUnaryExpression, etc.)src/hql/transpiler/codegen/precedence.ts