Skip to content

phunanon/Epizeuxis

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Epizeuxis - try it here

A JS-hosted programming language for teaching/learning/scripting. Pronounced /ɛpɪzuksɪs/ (ep-i-ZOOK-sis).
Similar to ClojureScript interfacing with JavaScript is permitted and encouraged.
Usable both in the browser and with Node.js (run node repl.js).
It's not fast*, it's not efficient, but is incredibly flexible and hackable!
* in fact, it's slower than its predecessor Chika on a 48MHz Arduino...

Syntax

Literals, data types

  • All symbols are internally strings, e.g. hello
  • true, false, null
  • "Hello, world!" is a string literal, with double quotes
  • 123 or -123 or 1.23 or -1.23 are number literals
  • \a is a character literal, with a backslash
    • \nl for newline, \sp for space
  • % is the first function argument
  • %N is the Nth argument

Note: undefined is also a literal, which is falsey and only appears within Epizeuxis internally.

Collections

  • [a 1 :c] is a vector of elements of any type, equivalent to (vec a 1 :c)
  • {a 0 b :c} is a dictionary of key-value pairs of any type, equivalent to (dict a 0 b :c)
  • #{a 1 :c} is a set of unique values, equivalent to (set a 1 :c)
  • args returns a vector of the arguments of the function

Note: dictionaries are stringified such as {a 0, b :c} to help readability.
Note: dictionary keys and values can be any type at all, including vectors, e.g. {[0 1] 2}.
Note: sets will only preserve distinct elements, i.e. #{1 1 2} or (into #{1 2} #{1}) is equal to #{1 2}.
Note: dictionaries with duplicate keys will only preserve the last distinctly keyed entry.

Expressions
An evaluated expression returns a value. An expression is formed such as (+ 1 2 3) whereby the + could be a native operation, program function, lambda, variable containing a function or lambda reference, or another expression evaluated as one of the aforementioned; the 1 2 3 in this case are three arguments, of any data type including expressions.
E.g. (+ 1 2 3) => 6
E.g. (double 5) => 10
E.g. (#(* % %) 8) => 64
E.g. (variable "blah" [random arguments]) => ...
E.g. ((if true + -) 12 9) => 21
E.g. (+ (- 10 5) 8) => 12

Functions
Function declarations are only accepted at the top-level of a document or REPL interaction - they cannot be contained within expressions.
They are declared as (fn function-name [0…] [1…]) or (fn function-name [1…])
where [0…] is zero or more named parameters (e.g. a b c), and [1…] is one or more expressions (e.g. (val "hello"))
E.g. (fn add a b (+ a b))
E.g. (fn say2x string (println string " " string))
E.g. (fn say-hello (println "Hello.") (println "You're handsome"))

Note: functions can be overloaded with arguments which can be accessed either through a numbered argument i.e. %5 or through the args collection. If a function is underloaded, i.e. called with less arguments than named parameters, the missing named parameters are undefined.
Note #[…] is shorthand for #(vec …).

Native operations

An operation is opposed to a function which is user-defined.

An integer N can take one argument which is a vector, string, dictionary, or set, and will return the Nth element of that collection. Negative numbers return the Nth element from the end of the collection.
E.g. (2 [a 1 b 2 c 3]) => b
E.g. (-2 [a 1 b 2 c 3]) => c E.g. (map 1 [[a b c] [1 2 3] [e f g]]) => [b 2 f]

A vector can take one argument, and it will return the argument if it is within the vector, otherwise null.
E.g. ([a b c] b) => b
E.g. (map [1 2] [5 4 2 3 1]) => [null null 2 null 1]

A dictionary can take one argument, and will return the value that corresponds to the argument as a key retrieval, otherwise null.
E.g. ({a 1 b 2} b) => 2
E.g. (map {0 1 1 0} [0 0 1 1 2 1 0]) => [1 1 0 0 null 0 1]

A set can take one argument, and will return the argument if it is within the set, otherwise null.
E.g. (#{1 2 3} 1) => 1
E.g. (#{a 1 :c} d) => null

A keyword such as :keyword can be used to get the :keyword key from a dictionary.
E.g. (:name {:age 23 :name "Patrick" :gender "Male"}) => "Patrick"

A native JavaScript function can be called by appending a colon (:) and using as an operation.
E.g. (Math.tanh: 1) => 0.761…
E.g. (parseInt: "E1" 16) => 225
E.g. (vm: "(+ 1 1)") => 2

A native JavaScript method can be called by prepending a period (.) and using it as an operation, with the method object as the first argument, and subsequent arguments passed through to the method.
E.g. (.split "hello, world!" ",") => ["hello" " world!"]
E.g. (.sort [1 2 8 6 7 5]) => [1 2 5 6 7 8]

Note: calls with function: or .method have their collection-type arguments cloned so mutative operations (e.g. .sort, .reverse) do not modify the original Epizeuxis collection.

There are many arthimetic and comparison operations, demonstrated:
(+ 1 2 3) => 3, addition, varadic;
(- 1 2 3) => -4, subtraction, varadic;
(- 123) => -123, sign change, arity 1;
(* 60 60 24) => 86400, multiplication, varadic;
(/ 10 2 2) => 2.5, division, varadic;
(quo 19 2 2) => 4, quotient, varadic;
(& 123 12 9) => 8, bitwise AND, varadic;
(| 128 64 1) => 193, bitwise OR, varadic;
(^ 55 170 55) => 170, bitwise XOR, varadic;
(>> 128 2) => 64, bitwise right shift, arity 2;
(<< 16 3) => 64, bitwise left shift, arity 2;
(~ 170) => -171, bitwise NOT, arity 1;
(** 2 8) => 256, expotent, arity 2;
(mod 1234 10) => 4, modulus, arity 2;
(! 123) => false, negation, arity 1;
(= [0 a [:b]] [0 a [:b]]) => true, equality, varadic;
(!= #{0 1 2} [0 1 2] null) => true, inequality, varadic;
(< 0 1 2 3) => true, monotonically increasing numbers, varadic;
(> 4 3 2 9) => false, monotonically decreasing numbers, varadic;
(<= 0 0 1 2) => true, monotonically non-decreasing numbers, varadic;
(>= 8 8 3 2) => true, monotonically non-increasing numbers, varadic.

sect returns vector or string v with…
(sect v) … the first element dropped;
(sect d v)d number of elements dropped;
(sect d t v)t number of elements after d number of elements dropped.

The following operations evaluate all their arguments but…
val returns its first argument;
do returns its last argument.

(map f v…) calls the f function for each item across each collection v until one of the collections is exhausted, passing an item from each collection as arguments.
E.g. (map + [0 1 2 3] [0 1 2 3 4 5 6]) => [0 2 4 6]
E.g. (map - [0 1 2 3]) => [0 -1 -2 -3]
E.g. (map str "hello" "world") => [hw eo lr ll od]

(loop n f) calls the f function n times like (f acc i) where acc is the return value of the previous repetition or null, and i is the repetition number starting from 0.
(loop n s f) does the same except acc is first set to s.
Returns the last retition's return value.
E.g. (loop 4 +) => 6 which is the same as (+ (+ (+ (+ null 0) 1) 2) 3)
E.g. (loop 3 5 +) => 8 which is the same as (+ (+ (+ 5 0) 1) 2)

.. bursts vector or set elements or dictionary entries into its parent's arguments.
E.g. (+ (.. [0 1 2 3])) => 6
E.g. (str (.. {a b c d})) => "[a b][c d]"
E.g. [(.. [1 2 3]) (.. [4 5 6])] => [1 2 3 4 5 6]

vec returns a vector with its arguments as the elements.
dict returns a dictionary with its arguments as the entries.
set returns a set with its distinct arguments as the elements.
E.g. (dict (vec a b c) (set d e e)) => {[a b c] #{d e}}

The following operations return true if its first argument is…
vec? a vector;
str? a string;
dict? a dictionary.

len returns the length of a vector, string, or number of dictionary entries.
E.g. (map len [[0 1 2] "hello" {a b c d}]) => [3 5 2]

(nth v n) returns the nth vector element, string character, or dictionary entry.
E.g. (map #(nth % 1) [[1 2 3] "hello" {a b c d}]) => [2 e [c d]]

(into dest src) returns collection dest with src merged into it. It intelligently merges between vectors, dictionaries and sets. E.g. (into [a b c d] {k v}) => [a b c d [k v]]
E.g. (into {k 0 a b} {k v}) => {a b k v}
E.g. (into #{} [0 1 2 3 3 3]) => #{0 1 2 3}

(filter f v) returns v with only items where (f item) is truthy.
E.g. (filter odd? (range 10)) => [1 3 5 7 9]
E.g. (filter val ["hello" null "hey" false "hi"]) => ["hello" "hey" "hi"]

(remove f v), complementary to filter, returns v with only items where (f item) is falsey.
E.g. (remove odd? (range 10)) => [0 2 4 6 8]
E.g. (remove #(< 3 (len %)) ["hello" "hey"]) => ["hey"]

juxt returns a function that acts like this: #[(arg0 %) (arg1 %) …] where argN are arguments to juxt. Means juxtposition.
E.g. ((juxt halve double triple) 12) => [6 24 36]
E.g. ((juxt filter remove) odd? (range 10)) => [[1 3 5 7 9] [0 2 4 6 8]]

comp returns a function that calls the functions in its arguments in turn. Means compose.
E.g. ((comp inc double) 10) => 22
E.g. ((comp double dec dec) 10) => 18 E.g. (comp inc double) is shorthand for #(double (inc (.. args)))

eval invokes JavaScript's eval() function with a string of JavaScript.
Use in conjunction with x->js to serialise complex Epizeuxis data.
Concatenates its arguments into one string.
E.g. (eval "2 + 2") or (eval 2 + 2) => 4
E.g. (eval (x->js [0 1 2]) .length) => 3

x->js serialises its argument into a JSON string. Does not natively work with dictionaries.
E.g. (x->js [0 b 2]) => [0,"b",2]

print and println prints its arguments concetanated as a string to the HTML transcript of the Epizeuxis REPL, either without or with a newline after it.

Other currently undocumented functions:
if and or let def recur str reduce when random rrandom round floor ceil sqrt abs sin cos tan