use package.utils;
use package.array;
use package.stdio;
use package.iterator;
def primeNumbers(n) = such(array()):
iterator(onNumbersAfter(2))
.filter(~(k) { iterator(onArray(it)).all(~(p) { k % p != 0; }); })
.take(n)
.do(~(p) { push(p, it); })
.forAllElements();
println(primeNumbers(15));
#=> [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
Juhz
(yoodz) is a general-purpose programming language. Although this language is built by pure context-free grammar (language.rkt shows you how cumbersome it is for CFG to express operation priority), it at least looks like a modern language and has flexibility and originality at some level.
Data in Juhz
can be classified in seven types:
boolean
, expressed by keywordstrue
andfalse
number
, e.g.1
,1.4
,.1
string
, e.g. “hello”. Escaped character: \\, \n, \" (better than nothing)array
, e.g.[1, 2, 3]
. Most of common operations likelength
can be found in standard libraryarray
function
, e.g.function (x) { x; }
package
, e.g.package { def foo = bar; }
- Others. Usually created by racket interfaces and opaque to
Juhz
facilities
The first four types work as what you are thinking. Functions are constructed by keyword function
:
function (arg1, arg2) { arg1 + arg2; }
function () { 0; }
function { 0; }
(the same as 2.)
Packages are used as both the libraries (e.g. package.stdio
) and data structures. In fact, every source files in Juhz
will be wrapped by a package
and every definition in it will be interpreted as a field. Although arbitrary statement can be put between the curly brackets of package
, the following two statements are important:
def foo = bar;
use expr;
The former defines a field in the package and the latter “links” current package with another. For every identifier, the interpreter searchs in local definition introduced by def
at first, and then recursively searchs in “used” packages. Arbitrary expressions can be used, as long as it returns something like a package.
There are several variants of def
statement. Let’s call foo
and bar
in the example above left-value and right-value respectively. Left-value can be:
foo
, identifier.f()
,f(a1, a2)
, function definition.package.foo
, library package definition.foo.bar
, customized definition.
The first and the second are intuitive. The third defines foo
as a library. The only difference between library and ordinary package is whether it is registered by keyword package
, like the third. After registry, you can access foo
anywhere by keyword package
:
# A.juhz
def package.foo = 1;
# B.juhz
package.foo; #=> 1
So in this case, package
works as a global variable that can introduce new fields by def
.
As to the forth, the following two statements are exactly equal.
def foo.bar = quz;
##################
use foo.__DEFINE__("bar", quz);
Regarding to the right-value, there are seven valid forms (boring, right?). Basically, for right-values, there are two rules:
- For something end with
}
, it forms a right-value - Otherwise, try to add a semicolon
;
.
Two abvious exceptions are definition, “use”, and assignment. something like def foo = bar;
, use foo
and foo = bar
is not a right-value. Wrap them by curly brackets.
Finally, tilde (~
) and at (@
) are equal to function
and package
respectively in token level. Therefore, the following two expression are equal:
function { println("hello"); }
##############################
~{ println("hello"); }
As shown above, most syntax in Juhz
are the same as most of modern programming languages. However, Juhz
do have some original syntax which may confuse you. To begin with, function call can be put at the left side of assignment.
length(array) = 10;
That is just a syntax sugar and it treats value at the right side as the last argument of the invocation. As a result, the example above is exactly equal to the following:
length(array, 10);
This feature enables Juhz
to define only one function to maintain a property, compared to languages like Java
. For example, package.specialVariables
defines a function called ref
to express modifiable state:
use package.stdio;
use package.specialVariables;
def nullify(n) = { n.value() = 0; }
def counter = ref(10);
nullify(counter);
println(counter.value()); #=> 0
By the way, functions in Juhz
can take arguments more than it declares (ignore the extra arguments) and arguments less than it declares (fill them by special constant NOT_PROVIDED
). Therefore, a simplified definition of ref
is as follows:
def ref(object) = package {
def value(newValue) = if newValue == NOT_PROVIDED {
object;
} else {
object = newValue;
}
}
Another special syntax is colon (:
). Every function call can be followed by a colon and a expression (or right-value technically).
when(x < 0): println(x);
when(x < 0): { x = -x; }
iterate(onArray([1, 2, 3])): ~(x) {
println(x);
}
action(x): @{ def it = x; }
The first three statements are just syntax sugar. For ordinary expressions, Juhz
will wrap them as functions take no arguments, and pass them to the invocation as the last argument. For functions, they will be passed as the last argument directly. However, in the case of package (the forth statement), Juhz
will introduce a intermediate level of environment for the invocation. In particular, assume that function action
is defined as follows:
def action(arg) = body;
Then when it is called as the forth statement, it will exprience the following steps:
- Bind
arg
tox
- Bind
it
tox
- Evaluate
body
That means it
is visible for body
. it
is a famous name for this purpose. For example, suchPackage
provided by package.reflection
can construct a package dynamically:
# A.juhz
use package.reflection;
def override(pkg, name, value) = suchPackage(): {
it.uses(pkg);
it.has(name, value);
}
# B.juhz using A.juhz to implement a sleepy toString
use package.string;
def package.string = override(package.string, "toString"): ~(object) {
toString(object) + "..zzZ";
}
# C.juhz loaded after B.juhz
use package.stdio;
use package.string;
println(toString([2, 3, 5, 7, 13]));
#=> [2, 3, 5, 7, 13]..zzZ
In this example, it
is a package builder with two fields has
and uses
.
Btw. the following statement are also syntactical valid, but I don’t think you need them.
f():
if x > 0 {
println(1);
} else {
println(0);
}
f():
while x > 0 {
x = x - 1;
}
f(): f(): f(): f(): println("Heart");
As you guessed, racket is necessary. You can install this repo by raco
:
raco pkg install https://github.com/dzangfan/juhz.git
This command will install runtime API (e.g. juhz/api
) and a executable juhz-run
to your system. juhz-run
is a easy interface to run Juhz
code. Pass pathes of sources as commandline argument to run them sequentially. Optional flag -i
enables juhz-run
to read standard input port at the end.
Note that there are no library system for Juhz
. I mean when you can access a package by package.lib
, there must be a statement like def package.lib = foo
. So dependencies should be loaded in advance. juhz-run
will automatically load .all.juhz, which registers for standard libraries.
Finally, juhz-mode.el provides basic syntax highlighting and indentation for .juhz
files in Emacs. Add it to load-path
and load it when needed. For example:
(use-package juhz-mode
:mode "\\.juhz$"
:load-path "/path/to/juhz")
Every unary and binary operator corresponds to a hook function, meaning that such operations will be interpreted as a method invocation. Logical operation &&
(and) and ||
(or) are exceptions since their operands must be evaluated lazily. In summary, except &&
and ||
, every operator is implemented by method defined in package, and those two exceptions cannot be customized. The following table lists all operators, as well as their priorities and hooks.
priority | name | operator | example | hook |
---|---|---|---|---|
1 | logical or | \vert\vert{} | a || b | none |
2 | logical and | && | a && b | none |
3 | equality | == | a == b | a.__SAME__(a, b) |
3 | difference | != | a != b | a.__DIFF__(a, b) |
4 | less than | < | a < b | a.__LT__(a, b) |
4 | greater than | > | a > b | a.__GT__(a, b) |
4 | less equal | <= | a <= b | a.__LE__(a, b) |
4 | greater than | >= | a >= b | a.__GE__(a, b) |
5 | add | + | a + b | a.__PLUS__(a, b) |
5 | subtract | - | a - b | a.__MINUS__(a, b) |
6 | multiply | * | a * b | a.__TIMES__(a, b) |
6 | divide | / | a / b | a.__DIVIDE__(a, b) |
6 | remainder | % | a % b | a.__REM__(a, b) |
7 | logical not | ! | !a | a.__BANG__(a) |
7 | positive | + | +a | a.__PLUS__(a) |
7 | negative | - | -a | a.__MINUS__(a) |
Besides, three types of operation related to subscript and definition are also implemented by hooks. The corresponding relationships are as follows.
example | hook | common meaning |
---|---|---|
foo[i] | foo.__INDEX__(foo, i) | Find the i-th element of foo |
foo[i] = j | foo.__INDEX__(foo, i, j) | Modify the i-th element of foo |
def class.foo = bar; | class.__DEFINE__(class, "foo", bar) | Define something called “foo” |