d9c9d10d9d
A few more changes to edify:
- fix write_raw_image(); my last change neglected to close the write
context, so the written image was corrupt.
- each expression tracks the span of the source code from which it
was compiled, so that assert()'s error message can include the
source of the expression that failed.
- the 'cookie' argument to each Function is replaced with a State
object, which contains the cookie, the source script (for use with
the above spans), and the current error message (replacing the
global variables that were used for this purpose).
- in the recovery image, a new command "ui_print" can be sent back
through the command pipe to cause text to appear on the screen.
Add a new ui_print() function to print things from scripts.
Rename existing "print" function to "stdout".
Update scripts (from donut onwards) are written in a new little
scripting language ("edify") that is superficially somewhat similar to
the old one ("amend"). This is a brief overview of the new language.
- The entire script is a single expression.
- All expressions are string-valued.
- String literals appear in double quotes. \n, \t, \", and \\ are
understood, as are hexadecimal escapes like \x4a.
- String literals consisting of only letters, numbers, colons,
underscores, slashes, and periods don't need to be in double quotes.
- The following words are reserved:
if then else endif
They have special meaning when unquoted. (In quotes, they are just
string literals.)
- When used as a boolean, the empty string is "false" and all other
strings are "true".
- All functions are actually macros (in the Lisp sense); the body of
the function can control which (if any) of the arguments are
evaluated. This means that functions can act as control
structures.
- Operators (like "&&" and "||") are just syntactic sugar for builtin
functions, so they can act as control structures as well.
- ";" is a binary operator; evaluating it just means to first evaluate
the left side, then the right. It can also appear after any
expression.
- Comments start with "#" and run to the end of the line.
Some examples:
- There's no distinction between quoted and unquoted strings; the
quotes are only needed if you want characters like whitespace to
appear in the string. The following expressions all evaluate to the
same string.
"a b"
a + " " + b
"a" + " " + "b"
"a\x20b"
a + "\x20b"
concat(a, " ", "b")
"concat"(a, " ", "b")
As shown in the last example, function names are just strings,
too. They must be string *literals*, however. This is not legal:
("con" + "cat")(a, " ", b) # syntax error!
- The ifelse() builtin takes three arguments: it evaluates exactly
one of the second and third, depending on whether the first one is
true. There is also some syntactic sugar to make expressions that
look like if/else statements:
# these are all equivalent
ifelse(something(), "yes", "no")
if something() then yes else no endif
if something() then "yes" else "no" endif
The else part is optional.
if something() then "yes" endif # if something() is false,
# evaluates to false
ifelse(condition(), "", abort()) # abort() only called if
# condition() is false
The last example is equivalent to:
assert(condition())
- The && and || operators can be used similarly; they evaluate their
second argument only if it's needed to determine the truth of the
expression. Their value is the value of the last-evaluated
argument:
file_exists("/data/system/bad") && delete("/data/system/bad")
file_exists("/data/system/missing") || create("/data/system/missing")
get_it() || "xxx" # returns value of get_it() if that value is
# true, otherwise returns "xxx"
- The purpose of ";" is to simulate imperative statements, of course,
but the operator can be used anywhere. Its value is the value of
its right side:
concat(a;b;c, d, e;f) # evaluates to "cdf"
A more useful example might be something like:
ifelse(condition(),
(first_step(); second_step();), # second ; is optional
alternative_procedure())