Embeddable Morphir Scheme for IR transformation pipelines and runtime execution
| Chapter | Covers |
|---|---|
| Morphir Scheme | The language, its limits and embedding a runtime |
| Writing models | Compiling Scheme to Morphir IR |
| Running Morphir IR | Evaluating and emitting IR, externals, linking, the symbols sidecar |
| Transform scripts | Scripts as IRFile -> IRFile procedures |
| Specification notation | Package specifications as data |
| Import | Responsibility |
|---|---|
| finos/morphir-scheme | Reader, runtime values, lexical evaluation and host procedures |
| finos/morphir-scheme/frontend | Scheme expressions and files → current Morphir IR |
| finos/morphir-scheme/backend | Morphir IR → executable Scheme; runtime support |
| finos/morphir-scheme/pipeline | Scripted IR transforms, JSON bridge and bottom-up traversal |
| finos/morphir-scheme/spec | The specification notation (see the chapter) ↔ @ir.PackageSpecification |
(define (inc x) (+ x 1))
(inc 41)
;; => 42| Datum | Written as | Notes |
|---|---|---|
| List | (a b c) or [a b c] | A list closes with the delimiter that opened it |
| Dotted pair | (a . b), (a b . c) | The dot needs at least one datum before it and exactly one after it |
| Quotation | 'datum | Reads as (quote datum) |
| Vector | #(a b c) | |
| String | "text" | Escapes: \n, \r, \t, \\, \", and a backslash before a vertical bar |
| Character | #\a, #\space, #\newline, #\tab, #\return | A delimiter such as ( can follow #\ directly |
| Escaped identifier | a name between vertical bars | Can hold spaces, delimiters and the same escapes as a string |
| Boolean | #t, #true, #f, #false | |
| Exact integer | 42, -7, +5 | Any size |
| Exact rational | 6/4 | Normalized: 6/4 reads as 3/2, and 4/2 reads as 2 |
| Inexact real | 1.5, -0.25, 1e3, 2.5E-3 | JSON number syntax with an optional leading +; the literal must be finite |
| Symbol | any other token |
#| outer #| inner |# |# #; (+ 9 9) '(1 . 2)
;; => (1 . 2)(let ([x 1] [y 2]) (+ x y))
;; => 3(+ 18446744073709551616 1)
;; => 18446744073709551617(+ 1/3 1/6)
;; => 1/2(list 0.5 1e3 (symbol? '.5))
;; => (0.5 1000.0 #t)"say \"hi\"\tthen go"
;; => "say \"hi\"\tthen go"(list #\a #\( #\space #\newline)
;; => (#\a #\( #\space #\newline)(symbol->string '|two words|)
;; => "two words"| Refused input | Message |
|---|---|
| The text ends inside a list | Unclosed list |
| A closing delimiter with no open list, or one that does not match the opening delimiter | Unexpected closing delimiter |
| A dot with nothing before it | Dot requires a head |
| More than one datum after a dot | Expected closing delimiter after tail |
| A vector written with a dot, such as #(1 . 2) | Invalid vector |
| The text ends inside a string or escaped identifier | Unclosed quoted text |
| The text ends right after a backslash inside a string or escaped identifier | Unclosed escape |
| An escape other than the six listed above | Unsupported escape |
| The text ends inside a block comment | Unclosed block comment |
| The text ends where a datum must follow: after a quotation, a datum comment or a dot | Expected datum |
| The text ends right after #\ | Expected character |
| A character name other than the four listed above | Expected one character |
| A # token that is not a boolean, vector, character or comment, such as #x10 | Unknown reader token: #x10 |
| A token that starts with a digit but is not a number, such as 1abc or 1. | Invalid numeric token |
| Lists nested more than 256 deep | Reader nesting limit |
///|
test "the reader refuses text that ends too early" {
for
refusal in [
("(1 2", "Syntax at character 4: Unclosed list"),
("\"abc", "Syntax at character 4: Unclosed quoted text"),
("\"a\\", "Syntax at character 3: Unclosed escape"),
("#| open", "Syntax at character 7: Unclosed block comment"),
("'", "Syntax at character 1: Expected datum"),
("#;", "Syntax at character 2: Expected datum"),
("(1 .", "Syntax at character 4: Expected datum"),
("#\\", "Syntax at character 2: Expected character"),
] {
let (source, expected) = refusal
let message = try {
ignore(@scheme.parse_program(source))
""
} catch {
e => @scheme.error_message(e)
}
assert_eq(message, expected)
}
}#(1 . 2)
;; error: Syntax at character 8: Invalid vector(1 . 2 3)
;; error: Syntax at character 7: Expected closing delimiter after tail#x10
;; error: Syntax at character 4: Unknown reader token: #x10"a\q"
;; error: Syntax at character 4: Unsupported escape1/0
;; error: Division by zero1e400
;; error: Nonfinite number| Value | Printed form | Example |
|---|---|---|
| Exact integer | decimal digits | 42 |
| Exact rational | numerator / denominator | 1/2 |
| Inexact real | digits with a point or exponent | 2.5, 1000.0 |
| Inexact real that is not finite | NaN.0, Infinity.0 or -Infinity.0 | made only by a host or by evaluate |
| Boolean | #t or #f | #t |
| Character | #\ and the character or its name | #\a, #\space |
| String | between double quotes, with escapes | "hi" |
| Symbol | its name, between vertical bars when it needs escaping | abc |
| Empty list | () | () |
| Pair | a list, or a dotted list when the last tail is not the empty list | (1 2), (1 . 2) |
| Vector | #( elements ) | #(1 2) |
| Record | #<record key=value ... > | #<record x=1> |
| Tagged value | #< tag elements > | made by the backend for Morphir constructors |
| Unit | #<unit> | #<unit> |
| Procedure | #<procedure> | closures and host procedures |
| Morphir IR value, type or file | #<morphir-value>, #<morphir-type>, #<morphir-file> | made by host procedures |
///|
test "a host can return a real that is not finite" {
let runtime = @scheme.Runtime::new()
runtime.define_native("infinity", fn(_rt, _args) {
@scheme.Value::Number(@scheme.Number::Real(1.0 / 0.0))
})
inspect(
@scheme.value_to_string(runtime.eval("(infinity)")),
content="Infinity.0",
)
let below = @scheme.Value::Number(@scheme.Number::Real(-1.0 / 0.0))
inspect(@scheme.value_to_string(below), content="-Infinity.0")
let nan = @scheme.Value::Number(@scheme.Number::Real(0.0 / 0.0))
inspect(@scheme.value_to_string(nan), content="NaN.0")
let message = try {
ignore(runtime.eval("(+ (infinity) 1)"))
""
} catch {
e => @scheme.error_message(e)
}
inspect(message, content="Nonfinite arithmetic result")
}(if 0 'zero-is-true 'false)
;; => zero-is-true| Values | Equal when |
|---|---|
| Numbers | They have the same numeric value, whatever their exactness |
| Booleans, characters, strings, symbols | They have the same content |
| Empty lists, units | Always |
| Pairs, vectors | Their elements are equal, in order |
| Records | They have the same keys with equal values, in any order |
| Tagged values | They have the same tag and equal elements |
| Morphir IR values, types and files | They are equal as IR |
| Procedures | Never, not even a procedure and itself |
| Values of different kinds | Never |
(list (equal? 1 1.0) (equal? car car) (equal? '(1 #(2)) (list 1 (vector 2))))
;; => (#t #f #t)| Form | Syntax |
|---|---|
| define | (define name expression), (define (name parameter ...) body ...) |
| set! | (set! name expression) |
| lambda | (lambda (parameter ...) body ...), (lambda (parameter ... . rest) body ...), (lambda rest body ...) |
| if | (if test consequent alternative) |
| begin | (begin form ...) |
| and | (and form ...) |
| or | (or form ...) |
| let | (let ((name expression) ...) body ...) |
| let* | (let* ((name expression) ...) body ...) |
| letrec | (letrec ((name expression) ...) body ...) |
| named let | (let loop ((name expression) ...) body ...) |
| quote | (quote datum) or 'datum |
(let ((if (lambda (x) x))) (if 1))
;; error: if expects condition, consequent and alternative#(x (+ 1 2))
;; => #(x (+ 1 2))| Error | Message |
|---|---|
| A symbol has no binding | Unbound identifier: name |
| The list is empty | Empty application |
| The list is dotted | Cannot evaluate an improper application |
| The head is not a procedure | Attempt to call a non-procedure |
(1 2)
;; error: Attempt to call a non-procedure| Error | Message |
|---|---|
| Fewer than two parts after define | define expects name and value |
| More than one expression after a name | define expects one value |
| A target that is neither a symbol nor a non-empty list | Invalid definition |
| A procedure name that is not a symbol | Expected identifier |
(define (square x) (* x x))
(square 7)
;; => 49(define answer 42)
;; => #<unit>| Error | Message |
|---|---|
| No binding of the name exists | Unbound identifier: name |
| The name is a letrec binding that is not yet initialized | Uninitialized recursive binding: name |
| Anything other than a name and one expression | set! expects name and value |
(define count 0)
(set! count (+ count 1))
count
;; => 1(set! missing 1)
;; error: Unbound identifier: missing| Error | Message |
|---|---|
| No body | lambda expects parameters and body |
| A repeated parameter | Duplicate parameter: name |
| A rest parameter that repeats a parameter | Duplicate rest parameter: name |
| Parameters that are neither a symbol nor a list | Invalid lambda formals |
| A parameter that is not a symbol | Expected identifier |
| A call with the wrong number of arguments | Procedure argument count mismatch |
| A call from another session | Closure belongs to another runtime |
(define (make-adder x) (lambda (y) (+ x y)))
((make-adder 10) 32)
;; => 42((lambda (a . rest) rest) 1 2 3)
;; => (2 3)((lambda args args) 1 2)
;; => (1 2)((lambda (x) x))
;; error: Procedure argument count mismatch| Error | Message |
|---|---|
| Anything other than three parts after if | if expects condition, consequent and alternative |
(if (< 1 2) 'yes 'no)
;; => yes(if #t 1)
;; error: if expects condition, consequent and alternative(begin (define x 1) (+ x 1))
;; => 2(begin)
;; => #<unit>(list (and 1 2 3) (and 1 #f 3) (or #f 'b) (and) (or))
;; => (3 #f b #t #f)(let ((x 1)) (let ((x 2) (y x)) y))
;; => 1(let* ((x 1) (x (+ x 1))) x)
;; => 2(letrec ((even? (lambda (n) (if (= n 0) #t (odd? (- n 1)))))
(odd? (lambda (n) (if (= n 0) #f (even? (- n 1))))))
(even? 10))
;; => #t(letrec ((a b) (b 1)) a)
;; error: Uninitialized recursive binding: b| Error | Message |
|---|---|
| No body | let expects bindings and body |
| Bindings that are not a list | Expected proper syntax list |
| A binding that is not a name and one expression | Binding expects name and expression |
| A binding name that is not a symbol | Expected identifier |
| A repeated name in let or letrec | Duplicate binding: name |
| An early read of a letrec name | Uninitialized recursive binding: name |
(let ((x 1) (x 2)) x)
;; error: Duplicate binding: x(let loop ((n 10000) (sum 0))
(if (= n 0) sum (loop (- n 1) (+ sum 1))))
;; => 10000| Error | Message |
|---|---|
| Anything other than one datum after quote | quote expects one datum |
(list '(a b . c) (quote (+ 1 2)))
;; => ((a b . c) (+ 1 2))| Procedure | Arguments | Result | Example |
|---|---|---|---|
| + | zero or more numbers | their sum; 0 with no arguments | (+ 1 2 3) → 6 |
| - | one or more numbers | the first minus the rest; the negation of a single argument | (- 10 4 1) → 5 |
| * | zero or more numbers | their product; 1 with no arguments | (* 2 3 4) → 24 |
| / | one or more numbers | the first divided by the rest; the reciprocal of a single argument | (/ 1 3) → 1/3 |
| quotient | two exact integers | the quotient, truncated toward zero | (quotient -7 2) → -3 |
| remainder | two exact integers | the remainder, with the sign of the first argument | (remainder -7 2) → -1 |
| number? | one value | #t for any number | (number? 1/2) → #t |
| integer? | one value | #t for an exact integer | (integer? 2.0) → #f |
(list (+ 1 2 3) (- 10 4 1) (* 2 3 4) (/ 1 3) (quotient -7 2) (remainder -7 2) (number? 1/2) (integer? 2.0))
;; => (6 5 24 1/3 -3 -1 #t #f)(list (+ 1 0.5) (* 2 1.0) (- 5) (/ 6 4))
;; => (1.5 2.0 -5 3/2)| Raised when | Message |
|---|---|
| An argument is not a number | Expected number |
| A call to - or / has no arguments | - needs an operand, / needs an operand |
| A divisor is zero, exact or inexact | Division by zero |
| An inexact result overflows | Nonfinite arithmetic result |
| An exact integer is too large to convert to an inexact real | Inexact conversion overflow |
| An argument of quotient or remainder is a rational or a real | Expected exact integers |
(/ 1 0)
;; error: Division by zero| Procedure | Arguments | Result | Example |
|---|---|---|---|
| = | two or more numbers | #t when all are equal | (= 1 1.0) → #t |
| < | two or more numbers | #t when each is less than the next | (< 1 2 3) → #t |
| > | two or more numbers | #t when each is greater than the next | (> 3 2 2) → #f |
| <= | two or more numbers | #t when each is at most the next | (<= 1 1 2) → #t |
| >= | two or more numbers | #t when each is at least the next | (>= 3 1 2) → #f |
| equal? | two values | #t when they are equal by structure (see Equality) | (equal? '(1 (2)) '(1 (2))) → #t |
(list (= 1 1.0) (< 1 2 3) (> 3 2 2) (<= 1 1 2) (>= 3 1 2) (equal? '(1 (2)) '(1 (2))))
;; => (#t #t #f #t #f #t)| Raised when | Message |
|---|---|
| A numeric comparison has fewer than two arguments | Comparison needs at least two operands |
| An argument of a numeric comparison is not a number | Expected number |
(< 2 1 'x)
;; error: Expected number| Procedure | Arguments | Result | Example |
|---|---|---|---|
| cons | two values | a pair of them | (cons 1 2) → (1 . 2) |
| car | a pair | its first part | (car '(1 2)) → 1 |
| cdr | a pair | its second part | (cdr '(1 2)) → (2) |
| list | zero or more values | a list of them | (list 1 2) → (1 2) |
| null? | one value | #t for the empty list | (null? '()) → #t |
| pair? | one value | #t for a pair | (pair? '()) → #f |
| list? | one value | #t for a proper list | (list? '(1 . 2)) → #f |
| length | a proper list | its number of elements | (length '(1 2 3)) → 3 |
| append | zero or more proper lists, then any value | the lists joined, ending in the last argument | (append '(1) '(2 3)) → (1 2 3) |
| reverse | a proper list | its elements in reverse order | (reverse '(1 2 3)) → (3 2 1) |
(list (cons 1 2) (car '(1 2)) (cdr '(1 2)) (list 1 2) (null? '()) (pair? '()) (list? '(1 . 2)) (length '(1 2 3)) (append '(1) '(2 3)) (reverse '(1 2 3)))
;; => ((1 . 2) 1 (2) (1 2) #t #f #f 3 (1 2 3) (3 2 1))(list (append) (append '(1) 2))
;; => (() (1 . 2))| Raised when | Message |
|---|---|
| The argument of car or cdr is not a pair | Expected pair |
| The argument of length or reverse, or an argument of append before the last, is not a proper list | Expected proper list |
(car '())
;; error: Expected pair| Procedure | Arguments | Result | Example |
|---|---|---|---|
| map | a procedure and one proper list | a list of the procedure's results for each element, in order | (map (lambda (x) (* x x)) '(1 2 3)) → (1 4 9) |
| fold-left | a procedure, an initial value and a proper list | the procedure applied from the left: (f (f init a) b) | (fold-left - 0 '(1 2 3)) → -6 |
| apply | a procedure, zero or more values, then a proper list | the procedure's result for the values followed by the list's elements | (apply + 1 '(2 3)) → 6 |
| procedure? | one value | #t for a closure or a host procedure | (procedure? car) → #t |
(list (map (lambda (x) (* x x)) '(1 2 3)) (fold-left - 0 '(1 2 3)) (apply + 1 '(2 3)) (procedure? car))
;; => ((1 4 9) -6 6 #t)| Raised when | Message |
|---|---|
| A call to map has more than one list | map: expected 2 arguments |
| The procedure argument is not a procedure | Expected procedure |
| The list argument is not a proper list | Expected proper list |
| A call to apply has fewer than two arguments | apply needs a procedure and list |
(map (lambda (x y) x) '(1 2) '(3 4))
;; error: map: expected 2 arguments| Procedure | Arguments | Result | Example |
|---|---|---|---|
| string-append | zero or more strings | the strings joined | (string-append "ab" "cd") → "abcd" |
| string=? | two strings | #t when they are the same | (string=? "a" "a") → #t |
| string? | one value | #t for a string | (string? 'a) → #f |
(list (string-append "ab" "cd") (string=? "a" "a") (string? 'a))
;; => ("abcd" #t #f)| Raised when | Message |
|---|---|
| An argument of string-append or string=? is not a string | Expected string |
(string-append "a" 1)
;; error: Expected string| Procedure | Arguments | Result | Example |
|---|---|---|---|
| symbol? | one value | #t for a symbol | (symbol? 'a) → #t |
| symbol->string | a symbol | its name | (symbol->string 'abc) → "abc" |
| string->symbol | a string | the symbol with that name | (string->symbol "abc") → abc |
| number->string | a number | its printed form | (number->string 1/3) → "1/3" |
| string->number | a string | the number the reader reads from it, or #f | (string->number "2.5") → 2.5 |
(list (symbol? 'a) (symbol->string 'abc) (string->symbol "abc") (number->string 1/3) (string->number "2.5"))
;; => (#t "abc" abc "1/3" 2.5)(string->symbol "two words")
;; => |two words|(string->number "ten")
;; => #f| Raised when | Message |
|---|---|
| The argument of symbol->string is not a symbol | Expected symbol |
| The argument of string->symbol or string->number is not a string | Expected string |
| The argument of number->string is not a number | Expected number |
| The text given to string->number is a real that is not finite | Nonfinite number |
| The text given to string->number is a rational with denominator zero | Division by zero |
(string->number "1/0")
;; error: Division by zero| Procedure | Arguments | Result | Example |
|---|---|---|---|
| record | zero or more pairs, each with a string key | a record of those fields | (record (cons "x" 1)) → #<record x=1> |
| record-ref | a record and a key | the field's value | (record-ref (record (cons "x" 1)) "x") → 1 |
| record-set | a record, a key and a value | a new record with the field set, or added at the end | (record-set (record (cons "x" 1)) "x" 2) → #<record x=2> |
| record-keys | a record | a list of its keys | (record-keys (record (cons "x" 1) (cons "y" 2))) → ("x" "y") |
(list (record (cons "x" 1)) (record-ref (record (cons "x" 1)) "x") (record-set (record (cons "x" 1)) "x" 2) (record-keys (record (cons "x" 1) (cons "y" 2))))
;; => (#<record x=1> 1 #<record x=2> ("x" "y"))(define point (record (cons "x" 1)))
(define moved (record-set point "y" 2))
(list point moved)
;; => (#<record x=1> #<record x=1 y=2>)| Raised when | Message |
|---|---|
| An argument of record is not a pair with a string key | record expects pairs with string keys |
| Two arguments of record have the same key | Duplicate record field: key |
| The first argument of record-ref, record-set or record-keys is not a record | Expected record |
| The key is not a string | Expected string |
| The key given to record-ref is not in the record | Missing record field |
(record-ref (record (cons "x" 1)) "y")
;; error: Missing record field| Procedure | Arguments | Result | Example |
|---|---|---|---|
| vector | zero or more values | a vector of them | (vector 1 2) → #(1 2) |
| vector-ref | a vector and an exact integer index | the element at the index | (vector-ref #(a b c) 1) → b |
| vector-length | a vector | its number of elements | (vector-length #(a b c)) → 3 |
(list (vector 1 2) (vector-ref #(a b c) 1) (vector-length #(a b c)))
;; => (#(1 2) b 3)| Raised when | Message |
|---|---|
| The arguments of vector-ref are not a vector and an exact integer | Expected vector and integer |
| The index is negative or not less than the length | Vector index out of range |
| The argument of vector-length is not a vector | Expected vector |
(vector-ref #(a b c) 3)
;; error: Vector index out of range| Procedure | Arguments | Result | Example |
|---|---|---|---|
| not | one value | #t for #f, and #f for any other value | (not 0) → #f |
| boolean? | one value | #t for a boolean | (boolean? #f) → #t |
| unit | none | the unit value | (unit) → #<unit> |
| error | zero or more values | raises an Invalid error and never returns | (error "Not found:" 'key) raises "Not found:" key |
(list (not 0) (boolean? #f) (unit))
;; => (#f #t #<unit>)(error "Not found:" 'key)
;; error: "Not found:" key| Kind | Raised for | error_message gives |
|---|---|---|
| Syntax(message, offset) | Text the reader refuses | Syntax at character offset: message |
| Unbound(name) | A name with no binding | Unbound identifier: name |
| Invalid(message) | A wrong form, a wrong argument or the error procedure | message |
| Unsupported(message) | A feature outside the language | Unsupported: message |
| Limit(resource) | An exhausted limit or a cancelled run | Limit exceeded: resource |
| Domain(domain, message) | A domain failure raised by another package | message |
| Stage(stage, message) | A failure in a named pipeline stage, raised by another package | stage: message |
(define x 1) (car x))
;; error: Syntax at character 20: Unexpected closing delimiterundefined-name
;; error: Unbound identifier: undefined-name(car 1)
;; error: Expected pair`(1 ,x)
;; error: Unsupported: Quasiquotation is not in the Morphir Scheme core(define (deep n) (+ 1 (deep n)))
(deep 0)
;; error: Limit exceeded: evaluation depth| Limit | Default | Bounds | Message |
|---|---|---|---|
| steps | 1,000,000 | Evaluation steps in one public eval, call or with_budget | Limit exceeded: evaluation steps |
| depth | 256 | Nested evaluations, such as arguments inside arguments and calls that are not in tail position | Limit exceeded: evaluation depth |
| frames | 100,000 | Lexical scopes allocated in the session so far | Limit exceeded: environment frames |
(let loop () (loop))
;; error: Limit exceeded: environment frames///|
test "limits bound a session and leave it usable" {
let runtime = @scheme.Runtime::new(limits={
steps: 100,
depth: 32,
frames: 100,
})
let message = try {
ignore(runtime.eval("(let loop () (loop))"))
""
} catch {
e => @scheme.error_message(e)
}
inspect(message, content="Limit exceeded: evaluation steps")
inspect(@scheme.value_to_string(runtime.eval("(+ 1 2)")), content="3")
assert_eq(@scheme.Limits::default(), {
steps: 1000000,
depth: 256,
frames: 100000,
})
}///|
test "a runtime evaluates source text" {
let runtime = @scheme.Runtime::new()
ignore(runtime.eval("(define (inc x) (+ x 1))"))
let answer = runtime.eval("(inc 41)")
inspect(@scheme.value_to_string(answer), content="42")
}///|
test "a host procedure joins the session" {
let runtime = @scheme.Runtime::new()
runtime.define_native("shout", fn(_runtime, arguments) raise {
@scheme.arity(arguments, 1, "shout")
@scheme.Text(arguments[0].as_text() + "!")
})
inspect(
@scheme.value_to_string(runtime.eval("(shout \"hi\")")),
content="\"hi!\"",
)
}
///|
test "a closure belongs to its session" {
let first = @scheme.Runtime::new()
let second = @scheme.Runtime::new()
let closure = first.eval("(lambda (x) x)")
let message = try {
ignore(second.call(closure, [@scheme.Unit]))
""
} catch {
e => @scheme.error_message(e)
}
assert_true(message != "")
}
///|
test "error_message includes the error details" {
let runtime = @scheme.Runtime::new()
let message = try {
ignore(runtime.eval("(letrec ((a b) (b 1)) a)"))
""
} catch {
e => @scheme.error_message(e)
}
inspect(message, content="Uninitialized recursive binding: b")
}| Function | Purpose |
|---|---|
| Runtime::new(), Runtime::new(limits=...) | A new session with the built-in procedures; raises Runtime limits must be positive for a limit of zero or less |
| Runtime::eval(source) | Read and evaluate a program; the value of its last form |
| Runtime::call(procedure, arguments) | Call a procedure value from the host |
| Runtime::define_native(name, procedure) | Bind a host procedure, replacing any binding of the name, built-ins included |
| Runtime::register_native_binding(name, arity, procedure) | Bind a host procedure with a fixed arity; raises Duplicate native binding: name if the name has one |
| Runtime::replace_native_binding(name, arity, procedure) | Replace an existing host procedure; raises No native binding to replace: name otherwise |
| Runtime::has_native(name), Runtime::native_arity(name) | Whether a host procedure exists, and its registered arity |
| Runtime::set_global(name, value), Runtime::lookup(name) | Write and read a top-level binding |
| Runtime::set_cancelled(cancelled) | A check the evaluator runs at every step; true stops the run with Limit exceeded: cancelled |
| Runtime::with_budget(work) | Run host work and the Scheme calls it makes under one step budget |
| eval_program(source) | Evaluate a program in a fresh session |
| parse_program(source), parse_program_located(source) | Read a program into data, without and with source offsets |
| value_to_string(value) | The printed form of a value |
| error_message(error) | The message of an error |
| arity(arguments, count, name) | Check a host procedure's argument count, raising name: expected count arguments |
| Feature | What happens |
|---|---|
| Macros (define-syntax, syntax-rules) | The names are unbound |
| Quasiquotation (backquote, comma) | The reader raises Unsupported |
| Continuations (call/cc, dynamic-wind) | The names are unbound |
| Ports and input and output | No procedures exist |
| Mutable pairs (set-car!, set-cdr!) | The names are unbound |
| The full numeric tower: complex numbers, infinities, NaN, exactness conversion | Only exact integers, exact rationals and inexact reals exist. No literal, string->number or arithmetic gives a real that is not finite; only a host or evaluate can, as described in Values |
| The library system (library, import) | The names are unbound |
| Error handling (guard, with-exception-handler) | Errors always reach the host |
(call/cc (lambda (k) (k 1)))
;; error: Unbound identifier: call/ccpub struct Closure {
// private fields
}pub struct Runtime {
// private fields
}pub(all) enum Value {
Number(Number)
Boolean(Bool)
Character(Char)
Text(String)
Symbol(String)
Nil
Pair(Value, Value)
Vector(Array[Value])
Record(Map[String, Value])
Tagged(String, Array[Value])
Unit
Closure(Closure)
Native(String)
NativeFunction((Runtime, Array[Value]) -> Value raise)
IR(Value)
IRType(Type)
IRFile(IRFile)
}fn error_message(error : Error) -> String