Macros all the way down.
Jihva is a programming language, intended to compile to JS.
Practically everything about Jihva is wrong. It is neither compiled nor interpreted. It is a stack language that uses infix notation.
Jihva is a bit like Scheme: a simple syntax, a simple interpreter, and a rich standard library.
Everything in Jihva is represented using JSON. All objects that occur during parsing have a "kind", and most have a "val".
{ kind: "ident", val: "foo" }
{ kind: "number", val: "-3.14" }
{ kind: "block", val: [ line1, line2, line3 ] }
Jihva files are first parsed into a list of tokens.
"hello" string
-3.14 number
@% symbol
// comment
identifier
The tokens are then parsed into "bracket-balanced lines".
e.g. the following is one balanced line, because (
holds the line open.
a b ( c
d ) f
The brackets are ( )
, [ ]
and { }
.
The brackets and lines form a tree.
e.g.
foo {
bar {
baz
}
}
Gives the following JSON tree structure
var block3 = { kind: "block", val: [[ { kind: "ident", val: "baz" } ]] } var block2 = { kind: "block", val: [[ { kind: "ident", val: "bar" }, block3 ]] } var prog1 = [ { kind: "ident", val: "foo" }, block2 ]
Each block has two nested arrays. The first is of lines the second is is the tokens in each line.
A module is a collection of items. That is very abstract, sorry.
mod Array {
fn map: f {
}
}
Here "mod Array" begins a module, and "fn map" declares an item.
You can make a new kind of item with macros, and it can have whatever syntax you want.
void macros roughly correspond to statements. They're "void" because they don't return anything. e.g.
if x {...}
else {...}
break
continue
You can make your own, e.g.
voidmacro yield x'expr {
... // implement a yield statement
}
value macros correspond to keyword operators. e.g. "typeof" in JS or "sizeof" in C. They have supreme powers over syntax, and always work in a sentence (that is, a list of tokens separated by parentheses.)
// Use:
(typeof "hello")
// Definition:
valmacro typeof x'expr {
... // implement a typeof macro
}
If method calls could be macros, then they would be postfix macros. A postfix macro is applied at the end of a sentence.
[1, 2, 3] each |x {
rtn x * 2
}
"each" is the macro. |x { ... }
is the normal syntax for closures.
postmacro a'tight each b'expr {
for _x in a.eval() { // _x declares a hygienic variable, although lexical scoping means we don't really have to
b.eval().(_x) // we must evaluate b then apply it with .(_x)
}
}
Jihva is a stack language, but it uses infix operators to make the proles happy.
This is done by registering operators on the infix table. A special stage then converts infix to postfix.
a + b * c + d
becomes: a b c * + d +
Infix operators are declared with
infix 1000 + // declares an infix operator +, with precedence 1000
There are 5 (yes 5!) call styles. Three are from JS, two are new for Jihva.
// JS
action(foo) // call function action
foo.action() // call method action
@action() // means self.action(). self is always the lexical object, not always the function's dynamic "this"
// Native
foo action // postfix macro
foo action() // postfix macro, with paren arguments
For function arguments it is necessary to separate them by commas. This is a separate parsing stage:
a, b, c
becomes: { kind: "commas", val: [a, b, c] }
=
is the set macro.
Macros can either run at runtime, or compile-time.
If a line contains only compile-time macros, then the stack will be optimised out.
e.g. "dup" is a runtime macro that duplicates the top item on the stack
1 2 dup // gives stack [1, 2, 2]
Which can be turned into an array with >array
xs = 1 2 dup ->array
// xs is [1, 2, 2]