eevo is a strong dynamically typed language (with a powerful first-class static type system in the works) inspired mostly by Scheme, with ideas from Lua, Python, Haskell, Julia, and Elm as well.
Expressions are the building blocks of eevo scripts, everything in eevo is an expression. They come in many different kinds, shown in the types section below.
Comments are explanatory notes that are ignored by eevo.
They help document how the code works and, more importantly, why it is done that way.
Comments start with a semicolon (;
) and continue until the end of the line.
Example: println "Hello World" ; ingnored by eevo until end of line
.
- Double semicolon
;;
denotes documentation comments. - Triple semicolon
;;;
marks section headers.
Note: While many scripting languages use #
, eevo adopts semicolons
for more convenient keyboard access, encouraging frequent use.
Types are the nouns of eevo, they describe the different kinds of expressions that eevo can represent and transform.
Whole numbers with optional +
or -
prefixes. Supports scientific notation
with e
or E
followed by another integer.
Note: The exponent should be a positive integer, it can also be negative but that would round to zero for integers.
Examples: 1
, -48
, +837e4
, 3E2
.
Floating-point numbers, denoted with decimal point. Supports scientific
notation with e
or E
followed by an integer.
Examples: 1.
, 0.018
, +3.14
, -.0054
, 800.001e-3
.
Fraction type, a ratio of two integers. Same integer rules apply for numerator and dominator, without scientific notation.
Automatically simplified, and will through error on division by zero.
Examples: 1/2
, 4/3
, -1/12
, 01/-30
, -6/-3
.
All values except Nil
are truthy, including explicit True
, integers,
strings, non-empty lists, etc.
Examples: True
, 1
, [8 7 6]
, "any string"
.
Nil represents false, or an empty list.
Examples: Nil
, False
, ()
.
Represents the absence of a value. Used to force functions to return nothing or as a placeholder in lists.
String of characters surrounded by double quotes "
. Any character is
valid, including newlines, except for double quotes and backslash which
need to be escaped as \"
and \\
respectively. Newlines and tabs can also be
escaped with \n
and \t
Examples: "foo"
, "foo bar"
, "string \"quoted\""
, "C:\\windows\\path"
,
"\tstring\twith white\n\tspace "
.
Case sensitive identifiers which are evaluated as variable names.
Unlike other types, symbols are not self-evaluating, they resolve to their defined value and throw an error if undefined.
Valid characters include lower and upper case letters, numbers, and
_+-*/\^=<>!?@#$%&~
. They can not start with a number, and if the first
character is a +
or -
then the second digit cannot be a number either.
Examples: foo
, foo-bar
, cat9
, +
, >=
, nil?
.
The basic types covered so far can be composed in multiple ways:
A grouping of exactly two expressions in (fst ... rst)
notation.
fst
is the first element and rst
is the second element, either can be any
type (including another pair) and do not have to be the same type.
When evaluated, the first element is a procedure, the rest are its argument, and the result is the procedure applied to the argument.
Examples: (1 ... 2)
, (f ... args)
Ordered sequences of expressions enclosed in parenthesis ( )
or brackets [ ]
separated by white space.
Made from chains of pairs ending with an empty list (eg (1 2 3) = (1 ... (2 ... (3 ... Nil)))
).
Lists written in parentheses are evaluated as function calls, while those in
brackets produce lists with their inner expressions evaluated
(eg [1/2 2/2 3/2] = (list 1/2 1 3/2)
).
The first element of a list can be placed before the parentheses to
imitate more traditional function call syntax (eg f(x y z) = (f x y z)
).
Examples: (one two three)
, [proton.mass neutron.mass electron.mass]
,
split(notes ",")
, (now)
Chains of pairs where the last expression is not Nil
.
Examples: (Fry Leela Bender ... others)
, [1 2 3 ... 4]
Records are unordered groupings of one or more key-value pairs separated by a colon and enclosed in curly braces. Sometimes called dictionaries, hash tables, structs, objects, or maps.
The key is a symbol which maps to a value of any type. Each value is evaluated when the record is defined. (In the future the key will also support any type, and the value will only be evaluated when the key is accessed, much like functions.)
Examples: { name: "Omar Little" age: (- 2008 1966) alive: False }
Functions take an input value and produce an output value. This happens by evaluating the function’s body expression with the given arguments added to the environment. The environment is captured when the function is defined, making functions closures.
Typically, the input value is a list of arguments. For example, the expression
(inc 54)
calls the function inc
with a list of one value, the number 54
,
returning 55
.
Examples: list
, not
, apply
, map
, sqrt
, println
Functions written in an external language other than eevo, such as the ones built-in to the language written in C. They behave like normal functions but are opaque, their implementation cannot be inspected within the language.
See built-ins section for more examples.
Examples: fst
, write
, +
Functions that operate at the syntax level, accepting code as input and return code as output. Unlike functions they receive their arguments unevaluated, and return code that gets evaluated in the context the macro is called in.
This enables eevo to extend its own syntax, eliminate repetition, construct custom languages for specific tasks, or directly transform source code.
Examples: if
, and
, or
, let
Macros written in an external language; like macros, their arguments are not necessarily evaluated, and like primitives, their internals cannot be inspected.
Examples: cond
, def
, quote
Procedures are the verbs of eevo, they describe how expressions change. Procedures can be either functions, macros, primitives, or forms.
Convention: Procedures which return a boolean type should end with ?
(eg
pair?
, integer?
, even?
), procedures with side-effects should end with !
(eg cd!
, exit!
).
The following are core procedures implemented in C and provided by default in the base environment.
Returns first element of given list.
Return rest of the given list, either just the second element if it is a pair, or another list with the first element removed.
Creates a new pair with the two given arguments, first one as the fst, second
as the rst. Can be chained together to create a list if ending with Nil
.
See also Pair and List types.
Prevents evaluation of an expression. Used to convert code to data, or create lists and symbols without evaluation.
Equivalent to single quote as prefix.
(quote x) ; returns the symbol x
'(a b c) ; returns a list of 3 symbols, symbols do not get evaulated
'(* 3 4) ; returns a list of 3 expressions, instead of 12
Evaluates the expression given. Can be dangerous to use as arbitrary code could
be executed if the input is not from a trusted source. In general apply
should be used when possible.
Tests if values are all equal. Returns Nil
if any are not, and True
if they
are.
Evaluates each expression if the given condition corresponding to it is true.
Runs through all arguments, each is a list with the first element as the
condition which needs to evaluate to True
, and the rest of the list is
the body to be run if and only if the condition is met. Used for if/elseif/else
statements found in C-like languages.
Also see if
,when
,unless
,switch
.
Returns a string stating the given argument's type.
Creates anonymous function, first argument is a list of symbols for the names of the new function arguments. Rest of the arguments to Func is the body of code to be run when the function is called.
Also see def
.
Transformers which operate on syntax expressions, and return syntax. Similar to Func, Macro creates anonymous macro with first argument as macro's argument list and rest as macro's body. Unlike functions macros do not evaluate their arguments when called, allowing the expressions to be transformed by the macro, returning a new expression to be evaluated at run time.
Also see defmacro
.
Create new symbol with the name of the first argument, and the value of the second. If the name given is a list use the first element of this list as a new functions name and rest of list as its arguments. If only one argument is given define a self evaluating symbol.
Remove symbol from environment. Errors if symbol is not defined before.
Return boolean on if symbol is defined in the environment.
Loads the library given as a string.
Throw error, print message given by second argument string with the first argument being a symbol of the function throwing the error.
A value can not be changed after it has been defined (since there is no set!
function).
The same symbol already defined can be redefined so that a new value shadows
the previous one, but the value itself is not modified.
A variable shadowed in a new scope returns to its original value when that
scope exits.
For example, a function can not change a variable outside its scope:
def x 64
def x 54
def add(a)
def x 12
+ x a
add 1 ; = 13
x ; = 54 (not 64 or 12)
In eevo parenthesis are implied around every new line, and a line indented more than the previous one is a sub-expression of it. A line with only one expression stays that expression, not a list of length one. For example:
a b c
d e
f
g h i
Becomes:
(a b c
(d e
f)
(g h i))
eevo only has one builtin equality primitive, =
, which tests numbers, text,
lists, and objects which occupy the same space in memory, such as primitives.
eevo is single value named, so procedures share the same namespace as
variables. This way functions are full first class citizens. It removes the
need for common lisp's defunc
vs defvar
, let
vs flet
, and redundant
syntax for getting the function from a symbol.
Symbols are case sensitive, unlike many other older lisps, in order to better interface with modern languages.
By default eevo's output is valid eevo code, fully equivalent to the evaluated
input.
Lists and symbols are quoted ((list 1 2 3) => '(1 2 3)
), errors are comments.
The only exception is anonymous functions/macros which will be supported soon.
To print a value as valid eevo code use display
and displayln
, to get a
plain output use print
and println
.
The traditional car
/cdr
have little meaning beyond their historical use, so instead replace
them with fst
/rst
to get the first expression in a list and the rest of the list respectively.
Compositions can be shorted just like with cxr: fst(fst(x))
is the same as ffst(x)
,
rst(fst(rst(x)))
is rfrst(x)
, etc.
The combination of fst(rst(x))
is given the name snd
since it is very common to get the
second expression of a list, and to avoid confusing frst
as first.
The function to construct a new pair is changed from cons
to Pair
to stay consistent with
other ways to create new types.
Likewise, lambda
is renamed to Func
, since function has a clear well known meaning,
opposed to lambda which has meaning only from convention.
eevo(1)
See project at https://edryd.org/projects/eevo
View source code at https://git.edryd.org/eevo
Edryd van Bruggen [email protected]
zlib License