Compare commits
82 Commits
master
...
simplify_c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a08ee50f84 | ||
|
|
ed7dd91c3f | ||
|
|
3abe20fee0 | ||
|
|
a92a96118e | ||
|
|
4e407fe301 | ||
|
|
ab74cdc173 | ||
|
|
2c9d039271 | ||
|
|
80d314c58f | ||
|
|
611fba2b6f | ||
|
|
f5fad52d47 | ||
|
|
2fc7d333ad | ||
|
|
d4635f2a75 | ||
|
|
19576533d9 | ||
|
|
c08249b6f1 | ||
|
|
dc348d023f | ||
|
|
fd5e4d155e | ||
|
|
e734353722 | ||
|
|
94f1645be1 | ||
|
|
41e3a6d91a | ||
|
|
04c569eab1 | ||
|
|
dc3c474b3a | ||
|
|
f1117bbd41 | ||
|
|
43faad95e0 | ||
|
|
acc9878b36 | ||
|
|
03c45ee8b0 | ||
|
|
a171a0d2af | ||
|
|
bb8d3930b3 | ||
|
|
522ae6128a | ||
|
|
16c26e4bf2 | ||
|
|
a9804785e0 | ||
|
|
11ae703693 | ||
|
|
f203278c3e | ||
|
|
a80557283a | ||
|
|
3e40885e07 | ||
|
|
e4b7de46f6 | ||
|
|
e680439a9b | ||
|
|
ae11504e00 | ||
|
|
893deaec23 | ||
|
|
69b032d3dc | ||
|
|
256a00c501 | ||
|
|
8e166b8f98 | ||
|
|
ddbdd00496 | ||
|
|
bdf0461e1f | ||
|
|
0b86af1d4c | ||
|
|
beac9608ea | ||
|
|
a04bebd0d7 | ||
|
|
ce74f726dd | ||
|
|
4d1ab60852 | ||
|
|
22ab6c8098 | ||
|
|
9a9775690f | ||
|
|
be71ae3bba | ||
|
|
f2a76cbb55 | ||
|
|
2d834c37b3 | ||
|
|
ba1b92aa78 | ||
|
|
c356fe462d | ||
|
|
b23b918f97 | ||
|
|
e720152bcd | ||
|
|
f093e6f5a3 | ||
|
|
4fc904de63 | ||
|
|
e53f55fb23 | ||
|
|
6150406905 | ||
|
|
a189440769 | ||
|
|
6c3c492446 | ||
|
|
bb83327a52 | ||
|
|
c74bee89a7 | ||
|
|
271a3d6724 | ||
|
|
c5ccc66e51 | ||
|
|
3a0ea31896 | ||
|
|
67e82fd12c | ||
|
|
3c59087c0c | ||
|
|
b79e07f57b | ||
|
|
fdcb374403 | ||
|
|
03feb370fd | ||
|
|
6712755940 | ||
|
|
b3f3bc8a5f | ||
|
|
a49b94e0a1 | ||
|
|
24ecff3f1c | ||
|
|
3ccaf68a5b | ||
|
|
64933260d4 | ||
|
|
561ab9d917 | ||
|
|
bcd6e641a5 | ||
|
|
2857581271 |
@@ -1,9 +1,20 @@
|
||||
BasedOnStyle: GNU
|
||||
Language: C
|
||||
|
||||
IndentWidth: 2
|
||||
TabWidth: 2
|
||||
UseTab: Never
|
||||
ContinuationIndentWidth: 2 # Indents continuation lines by 2 spaces
|
||||
ContinuationIndentWidth: 2
|
||||
|
||||
AllowShortFunctionsOnASingleLine: true
|
||||
AllowShortBlocksOnASingleLine: true
|
||||
AllowShortIfStatementsOnASingleLine: true
|
||||
BreakBeforeBraces: Attach
|
||||
ColumnLimit: 0
|
||||
BreakFunctionDefinitionParameters: false
|
||||
BinPackParameters: false
|
||||
BinPackArguments: false
|
||||
|
||||
# --- Fix the "static T\nname(...)" style ---
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
BreakAfterReturnType: None
|
||||
|
||||
4
Makefile
4
Makefile
@@ -9,6 +9,8 @@
|
||||
CELL_SHOP = $(HOME)/.cell
|
||||
CELL_CORE_PACKAGE = $(CELL_SHOP)/packages/core
|
||||
|
||||
maker: install
|
||||
|
||||
makecell:
|
||||
cell pack core -o cell
|
||||
cp cell /opt/homebrew/bin/
|
||||
@@ -56,7 +58,7 @@ static:
|
||||
# Bootstrap: build cell from scratch using meson (only needed once)
|
||||
# Also installs core scripts to ~/.cell/core
|
||||
bootstrap:
|
||||
meson setup build_bootstrap -Dbuildtype=debugoptimized
|
||||
meson setup build_bootstrap -Dbuildtype=debug -Db_sanitize=address
|
||||
meson compile -C build_bootstrap
|
||||
cp build_bootstrap/cell .
|
||||
cp build_bootstrap/libcell_runtime.dylib .
|
||||
|
||||
@@ -8,14 +8,14 @@ static JSClassID js_writer_class_id;
|
||||
static void js_reader_finalizer(JSRuntime *rt, JSValue val) {
|
||||
mz_zip_archive *zip = JS_GetOpaque(val, js_reader_class_id);
|
||||
mz_zip_reader_end(zip);
|
||||
js_free_rt(rt,zip);
|
||||
js_free_rt(zip);
|
||||
}
|
||||
|
||||
static void js_writer_finalizer(JSRuntime *rt, JSValue val) {
|
||||
mz_zip_archive *zip = JS_GetOpaque(val, js_writer_class_id);
|
||||
mz_zip_writer_finalize_archive(zip);
|
||||
mz_zip_writer_end(zip);
|
||||
js_free_rt(rt,zip);
|
||||
js_free_rt(zip);
|
||||
}
|
||||
|
||||
static JSClassDef js_reader_class = {
|
||||
@@ -101,7 +101,7 @@ static JSValue js_miniz_compress(JSContext *js, JSValue this_val,
|
||||
size_t in_len = 0;
|
||||
const void *in_ptr = NULL;
|
||||
|
||||
if (JS_IsString(argv[0])) {
|
||||
if (JS_IsText(argv[0])) {
|
||||
/* String → UTF-8 bytes without the terminating NUL */
|
||||
cstring = JS_ToCStringLen(js, &in_len, argv[0]);
|
||||
if (!cstring)
|
||||
|
||||
23
debug/js.c
23
debug/js.c
@@ -1,8 +1,6 @@
|
||||
#include "cell.h"
|
||||
|
||||
JSC_CCALL(os_gc, JS_RunGC(JS_GetRuntime(js)) )
|
||||
JSC_CCALL(os_mem_limit, JS_SetMemoryLimit(JS_GetRuntime(js), js2number(js,argv[0])))
|
||||
JSC_CCALL(os_gc_threshold, JS_SetGCThreshold(JS_GetRuntime(js), js2number(js,argv[0])))
|
||||
JSC_CCALL(os_max_stacksize, JS_SetMaxStackSize(JS_GetRuntime(js), js2number(js,argv[0])))
|
||||
|
||||
// Compute the approximate size of a single JS value in memory.
|
||||
@@ -15,8 +13,7 @@ JSC_CCALL(os_calc_mem,
|
||||
JS_SetPropertyStr(js,ret,"memory_used_size",number2js(js,mu.memory_used_size));
|
||||
JS_SetPropertyStr(js,ret,"malloc_count",number2js(js,mu.malloc_count));
|
||||
JS_SetPropertyStr(js,ret,"memory_used_count",number2js(js,mu.memory_used_count));
|
||||
JS_SetPropertyStr(js,ret,"atom_count",number2js(js,mu.atom_count));
|
||||
JS_SetPropertyStr(js,ret,"atom_size",number2js(js,mu.atom_size));
|
||||
/* atom_count and atom_size removed - atoms are now just strings */
|
||||
JS_SetPropertyStr(js,ret,"str_count",number2js(js,mu.str_count));
|
||||
JS_SetPropertyStr(js,ret,"str_size",number2js(js,mu.str_size));
|
||||
JS_SetPropertyStr(js,ret,"obj_count",number2js(js,mu.obj_count));
|
||||
@@ -42,20 +39,22 @@ JSC_CCALL(os_calc_mem,
|
||||
JSC_SSCALL(os_eval,
|
||||
if (!str2) return JS_ThrowReferenceError(js, "Second argument should be the script.");
|
||||
if (!str) return JS_ThrowReferenceError(js, "First argument should be the name of the script.");
|
||||
ret = JS_Eval(js,str2,strlen(str2),str, 0);
|
||||
JSValue bytecode = JS_Compile(js, str2, strlen(str2), str);
|
||||
if (JS_IsException(bytecode)) return bytecode;
|
||||
ret = JS_Integrate(js, bytecode, JS_NULL);
|
||||
)
|
||||
|
||||
// Compile a string of JavaScript code into a function object.
|
||||
JSC_SSCALL(js_compile,
|
||||
if (!str2) return JS_ThrowReferenceError(js, "Second argument should be the script.");
|
||||
if (!str) return JS_ThrowReferenceError(js, "First argument should be the name of the script.");
|
||||
ret = JS_Eval(js, str2, strlen(str2), str, JS_EVAL_FLAG_COMPILE_ONLY | JS_EVAL_FLAG_BACKTRACE_BARRIER);
|
||||
ret = JS_Compile(js, str2, strlen(str2), str);
|
||||
)
|
||||
|
||||
// Evaluate a function object in the current QuickJS context.
|
||||
JSC_CCALL(js_eval_compile,
|
||||
JS_DupValue(js,argv[0]);
|
||||
ret = JS_EvalFunction(js, argv[0]);
|
||||
// Link compiled bytecode with environment and execute.
|
||||
JSC_CCALL(js_integrate,
|
||||
JSValue env = (argc > 1 && !JS_IsNull(argv[1])) ? argv[1] : JS_NULL;
|
||||
ret = JS_Integrate(js, argv[0], env);
|
||||
)
|
||||
|
||||
// Compile a function object into a bytecode blob.
|
||||
@@ -92,12 +91,10 @@ JSC_CCALL(js_fn_info,
|
||||
static const JSCFunctionListEntry js_js_funcs[] = {
|
||||
MIST_FUNC_DEF(os, calc_mem, 0),
|
||||
MIST_FUNC_DEF(os, mem_limit, 1),
|
||||
MIST_FUNC_DEF(os, gc_threshold, 1),
|
||||
MIST_FUNC_DEF(os, max_stacksize, 1),
|
||||
MIST_FUNC_DEF(os, gc, 0),
|
||||
MIST_FUNC_DEF(os, eval, 2),
|
||||
MIST_FUNC_DEF(js, compile, 2),
|
||||
MIST_FUNC_DEF(js, eval_compile, 1),
|
||||
MIST_FUNC_DEF(js, integrate, 2),
|
||||
MIST_FUNC_DEF(js, compile_blob, 1),
|
||||
MIST_FUNC_DEF(js, compile_unblob, 1),
|
||||
MIST_FUNC_DEF(js, disassemble, 1),
|
||||
|
||||
921
docs/functions.md
Normal file
921
docs/functions.md
Normal file
@@ -0,0 +1,921 @@
|
||||
# Cell Functions
|
||||
|
||||
The intrinsics are constants and functions that are built into the language. The use statement is not needed to access them.
|
||||
|
||||
A programmer is not obliged to consult the list of intrinsics before naming a new variable or input.
|
||||
New intrinsics may be added to Misty without breaking existing programs.
|
||||
Constants
|
||||
|
||||
false
|
||||
|
||||
This is the value of 1 = 0. The false value is one of the two logical values.
|
||||
|
||||
true
|
||||
|
||||
This is the value of 1 = 1. The true value is one of the two logical values.
|
||||
|
||||
null
|
||||
|
||||
This is the value of 1 / 0. The null value is an empty immutable object. All attempts to obtain a value from null by refinement will produce null.
|
||||
|
||||
Any attempt to modify null will disrupt. Any attempt to call null as a function will disrupt.
|
||||
|
||||
null is the value of missing input values, missing fields in records, and invalid numbers. The |default operator can detect the presence of null and substitute another value.
|
||||
|
||||
pi
|
||||
|
||||
This is an approximation of the circle expression circumference / diameter, or to be precisely approximate, 3.1415926535897932.
|
||||
|
||||
Creator Functions
|
||||
|
||||
The creator functions are used to make new objects. Some of them can take various types. All of these functions can return null if their inputs are not suitable.
|
||||
|
||||
Array
|
||||
|
||||
array(number)
|
||||
|
||||
Make an array. All of the elements are initialized to null.
|
||||
|
||||
number is a non-negative integer, the intended length of the new array.
|
||||
|
||||
array(number, initial_value)
|
||||
|
||||
Make an array. All of the elements are initialized to initial_value.
|
||||
|
||||
number is a non-negative integer, the intended length of the new array.
|
||||
|
||||
If initial_value is a function, then the function is called for each element to produce initialization values. If the function has an arity of 1 or more, it is passed the element number.
|
||||
|
||||
array(array)
|
||||
|
||||
Copy. Make a mutable copy of the array.
|
||||
|
||||
array(array, function, reverse, exit)
|
||||
|
||||
Map. Call the function with each element of the array, collecting the return values in a new array. The function is passed each element and its element number.
|
||||
|
||||
function (element, element_nr)
|
||||
If reverse is true, then it starts with the last element and works backwards.
|
||||
|
||||
If exit is not null, then when the function returns the exit value, then the array function returns early. The exit value will not be stored into the new array. If the array was processed normally, then the returned array will be shorter than the input array. If the array was processed in reverse, then the returned array will have the same length as the input array, and the first elements will be null. The elements in the new array that were not set due to an early exit will be set to null.
|
||||
|
||||
This is like the for function except that the return values are collected into a new array.
|
||||
|
||||
array(array, another_array)
|
||||
|
||||
Concat. Produce a new array that concatenates the array and another_array.
|
||||
|
||||
array(array, from, to)
|
||||
|
||||
Slice. Make a mutable copy of all or part of an array.
|
||||
|
||||
array: the array to copy
|
||||
|
||||
from: the position at which to start copying. Default: 0, the beginning. If negative, add length(array).
|
||||
|
||||
to: the position at which to stop copying. Default: length(array), the end. If negative, add length(array).
|
||||
|
||||
If, after adjustment, from and to are not valid integers in the proper range, then it returns null. from must be positive and less than or equal to to. to must be less than or equal to length(array).
|
||||
|
||||
array(record)
|
||||
|
||||
Keys. Make an array containing all of the text keys in the record. The keys are not guaranteed to be in any particular order.
|
||||
|
||||
array(text)
|
||||
|
||||
Split the text into grapheme clusters. A grapheme cluster is a Unicode codepoint and its contributing combining characters, if any.
|
||||
|
||||
array(text, separator)
|
||||
|
||||
Split the text into an array of subtexts. The separator can be a text or pattern.
|
||||
|
||||
array(text, length)
|
||||
|
||||
Dice the text into an array of subtexts of a given length.
|
||||
|
||||
Logical
|
||||
|
||||
logical(value)
|
||||
|
||||
if value = 0 \/ value = false \/ value = "false" \/ value = null
|
||||
return false
|
||||
fi
|
||||
if value = 1 \/ value = true \/ value = "true"
|
||||
return true
|
||||
fi
|
||||
return null
|
||||
Number
|
||||
|
||||
number(logical)
|
||||
|
||||
The result is 1 or 0.
|
||||
|
||||
number(number)
|
||||
|
||||
The number is returned.
|
||||
|
||||
number(text, radix)
|
||||
|
||||
Convert a text to a number. The optional radix is an integer from 2 thru 37. (See Base 32.) The default radix is 10.
|
||||
|
||||
number(text, format)
|
||||
|
||||
number format
|
||||
format radix separator decimal point
|
||||
"" 10 .period
|
||||
"n"
|
||||
"u" _underbar
|
||||
"d" ,comma
|
||||
"s" space
|
||||
"v" .period ,comma
|
||||
"l" dependent on locale
|
||||
"i" _underbar
|
||||
"b" 2
|
||||
"o" 8
|
||||
"h" 16
|
||||
"t" 32
|
||||
"j" 0x- base 16
|
||||
0o- base 8
|
||||
0b- base 2
|
||||
otherwise base 10
|
||||
The number function converts a text into a number.
|
||||
|
||||
If it is unable to (possibly because of a formatting error), it returns null. The format character determines how the text is interpreted. If the format is not one of those listed, then null is returned.
|
||||
|
||||
Examples:
|
||||
|
||||
assign result: number("123,456,789.10", "d") # result is 123456789.1
|
||||
assign result: number("123.456.789,10", "v") # result is 123456789.1
|
||||
assign result: number("123.456.789,10", "d") # result is null
|
||||
assign result: number("123 456 789.10", "s") # result is 123456789.1
|
||||
assign result: number("12.350") # result is 12.35
|
||||
assign result: number("12.350", "v") # result is 12350
|
||||
assign result: number("12.350", "i") # result is null
|
||||
assign result: number("666") # result is 666
|
||||
assign result: number("666", "b") # result is null
|
||||
assign result: number("666", "o") # result is 438
|
||||
assign result: number("666", "h") # result is 1638
|
||||
assign result: number("666", "t") # result is 6342
|
||||
assign result: number("0666") # result is 666
|
||||
Record
|
||||
|
||||
record(record)
|
||||
|
||||
Copy. Make a mutable copy.
|
||||
|
||||
record(record, another_record)
|
||||
|
||||
Combine. Make a copy of a record, and then put all the fields of another_record into the copy.
|
||||
|
||||
record(record, array_of_keys)
|
||||
|
||||
Select. Make a new record containing only the fields that are named by the array_of_keys.
|
||||
|
||||
record(array_of_keys)
|
||||
|
||||
Set. Make a record using the array as the source of the keys. Each field value is true.
|
||||
|
||||
record(array_of_keys, value)
|
||||
|
||||
Value Set. Make a record using the array as the source of the keys. Each field value is value.
|
||||
|
||||
record(array_of_keys, function)
|
||||
|
||||
Functional Value Set. Make a record using the array as the source of the keys. The function is called for each key, yielding the field values.
|
||||
|
||||
Text
|
||||
|
||||
text(array)
|
||||
|
||||
Convert an array to text. The array can contain text and unicode codepoints. All are concatenated together to make a single text.
|
||||
|
||||
text(array, separator)
|
||||
|
||||
Convert an array to text. The array can contain text and unicode codepoints. All are concatenated together to make a single text. The separator text is inserted between each piece. The default separator is the empty text.
|
||||
|
||||
text(number, radix)
|
||||
|
||||
Convert a number to text. The optional radix is an integer from 2 thru 37. (See Base 32.) The default radix is 10.
|
||||
|
||||
text(number, format)
|
||||
|
||||
The format of the format text is
|
||||
|
||||
|
||||
format
|
||||
format_separation format_style format_places
|
||||
|
||||
|
||||
format_separation
|
||||
""
|
||||
digit
|
||||
|
||||
|
||||
format_style
|
||||
'b'
|
||||
'c'
|
||||
'e'
|
||||
'h'
|
||||
'i'
|
||||
'l'
|
||||
'n'
|
||||
'o'
|
||||
's'
|
||||
't'
|
||||
'u'
|
||||
|
||||
|
||||
format_places
|
||||
digit digit
|
||||
digit
|
||||
|
||||
Convert a number to formatted text. The text function converts a number to a text. It takes a format text input.
|
||||
|
||||
A format text contains a style letter that controls how a text is produced from the number. It is optionally preceded by a separation digit, and optionally followed by a places digit. There are real styles and integer styles. If the format input is not a proper format text, then null is returned.
|
||||
|
||||
Separation is a character that is placed between digits to improve readability. If separation is 0, then there is no separation. If separation is 3, then a character is inserted before the quadrillions, trillions, billions, millions, and thousands.
|
||||
|
||||
Places is the number of places to display after the decimal point (in real styles) or the minimum number of digits to display with zero-fill (in integer styles). If places is 0, then as many digits as necessary are displayed. Places can be zero or one or two digits.
|
||||
|
||||
real style base default
|
||||
separation default
|
||||
places decimal
|
||||
point separator
|
||||
e exponential 10 0 0 .period
|
||||
n number
|
||||
s space 3 space
|
||||
u underbar _underbar
|
||||
d decimal 2 ,comma
|
||||
c comma ,comma .period
|
||||
l locale determined by the locale
|
||||
The real format options are
|
||||
|
||||
"e" uses scientific notation. One digit is placed before the decimal point, and all of the remaining digits after, followed by e and the exponent.
|
||||
|
||||
"n" uses .period as the decimal point and no separator. It is the format used for numbers in Misty source programs and JSON. Scientific notation is used if the number value is extreme.
|
||||
|
||||
"s" uses .period as the decimal point and a space as the separator.
|
||||
|
||||
"u" uses .period as the decimal point and _underbar as the separator.
|
||||
|
||||
"d" uses .period as the decimal point and ,comma as the separator.
|
||||
|
||||
"c" uses ,comma as the decimal point and .period as the separator.
|
||||
|
||||
"l" depends on the locale to determine the characters to use as the decimal point and the separator.
|
||||
|
||||
The optional places determines the number of digits after the decimal point. The default is determined by the format, as seen in the table. If the places is 0, then the number of decimal places will be the fewest to exactly display the number without truncating. If the places is larger, then the field is padded if necessary with trailing 0.
|
||||
|
||||
The optional separation determines the spacing of the separator character. For example, to place a separator between billions, millions, and thousands (that is, every 3 digits) then separation should be 3. If separation is zero, then there is no separation. The default is determined by the style.
|
||||
|
||||
integer style base default
|
||||
separation minimum
|
||||
places separator
|
||||
i integer 10 0 1 _underbar
|
||||
b binary 2
|
||||
o octal 8
|
||||
h hexadecimal 16
|
||||
t Base32 32
|
||||
The integer styles first trunc the number. The fractional part of the number is ignored. The separation character is _underbar.
|
||||
|
||||
The optional places determines the minimum number of digits to show. More leading 0 may be shown if necessary. The default is determined by the format, as seen in the table.
|
||||
|
||||
The optional separation determines the spacing of the separator character. For example, to place a separator between billions, millions, and thousands (that is, every 3 digits) then separation should be 3. If separation is zero, then there is no separation. The default is determined by the format, as seen in the table.
|
||||
|
||||
Examples:
|
||||
|
||||
def data: 0123456789.1
|
||||
assign result: text(data) # result is "123456789.1"
|
||||
assign result: text(data, "n") # result is "123456789.1"
|
||||
assign result: text(data, "3s4") # result is "123 456 789.1000"
|
||||
assign result: text(data, "s") # result is "123 456 789.1"
|
||||
assign result: text(data, "d2") # result is "123,456,789.10"
|
||||
assign result: text(data, "4d0") # result is "1,2345,6789.1"
|
||||
assign result: text(data, "v2") # result is "123.456.789,10"
|
||||
assign result: text(data, "e") # result is "1.234567891e8"
|
||||
assign result: text(data, "e4") # result is "1.2345e8"
|
||||
assign result: text(data, "i") # result is "123456789"
|
||||
assign result: text(data, "8b") # result is "111_01011011_11001101_00010101"
|
||||
assign result: text(data, "o") # result is "726746425"
|
||||
assign result: text(data, "h") # result is "75BCD15"
|
||||
assign result: text(data, "t") # result is "3NQK8N"
|
||||
assign result: text(12) # result is "12"
|
||||
assign result: text(12, 8) # result is "14"
|
||||
assign result: text(12, 32) # result is "C"
|
||||
assign result: text(12, "4b8") # result is "0000_1100"
|
||||
assign result: text(12, "o3") # result is "014"
|
||||
assign result: text(12, "h4") # result is "000C"
|
||||
assign result: text(12, "t2") # result is "0C"
|
||||
text(text)
|
||||
|
||||
Return the text. The text is not altered.
|
||||
|
||||
text(text, from, to)
|
||||
|
||||
Make a copy of part of a text.
|
||||
|
||||
text: the text to copy.
|
||||
|
||||
from: the position at which to start copying. Default: 0, the beginning. If negative, add length(text).
|
||||
|
||||
to: the position at which to stop copying. Default: length(text), the end. If negative, add length(text).
|
||||
|
||||
If, after adjustment, from and to are not valid integers in the proper range, then it returns null. from must be positive and less than or equal to to. to must be less than or equal to length(text).
|
||||
|
||||
assign my_text: "miskatonic"
|
||||
text(my_text, 0, 3) # "mis" # the first 3
|
||||
text(my_text, 3, 6) # "kat" # from 3 to 6
|
||||
text(my_text, 5) # "tonic" # exclude the first 5
|
||||
text(my_text, 0, -4) # "miskat" # exclude the last 4
|
||||
text(my_text, -3) # "nic" # the last 3
|
||||
text(my_text, 0, 0) # ""
|
||||
text(my_text, 10) # ""
|
||||
text(my_text, 11) # null
|
||||
text(my_text, 2, 1) # null
|
||||
Sensory Functions
|
||||
|
||||
The sensory functions end with ?question mark. They always return a logical value.
|
||||
|
||||
actor?(value)
|
||||
|
||||
Is the value an actor address object?
|
||||
|
||||
actor?(me!) # true
|
||||
actor?("actor") # false
|
||||
actor?({actor: true}) # false
|
||||
array?(value)
|
||||
|
||||
Is the value an array? If the value is an array, the result is true. Otherwise, the result is false.
|
||||
|
||||
array?(0) # false
|
||||
array?({}) # false
|
||||
array?([]) # true
|
||||
not(array?([])) # false
|
||||
array?(pattern (1- {letter digit "_-%"})) # false
|
||||
array?(null) # false
|
||||
array?("array") # false
|
||||
blob?(value)
|
||||
|
||||
Is the value a blob? If the value is a blob, the result is true. Otherwise, the result is false.
|
||||
|
||||
blob?(0) # false
|
||||
blob?("blob") # false
|
||||
blob?(blob()) # true
|
||||
character?(value)
|
||||
|
||||
Is the value a character? If the value is a text with a length of 1, then the result is true. Otherwise, the result is false.
|
||||
|
||||
character?(1) # false
|
||||
character?("1") # true
|
||||
character?("character") # false
|
||||
character?("") # false
|
||||
character?("\u{FFFE}") # true
|
||||
character?() # false
|
||||
character?("/q") # false
|
||||
character?("\q") # true
|
||||
character?(<<">>) # true
|
||||
character?(null) # false
|
||||
data?(value)
|
||||
|
||||
Is the value data? If the value is a text, number, logical, array, blob, or record, then the result is true. If the value is a function, pattern, or null, the result is false.
|
||||
|
||||
data?(0) # true
|
||||
data?("") # true
|
||||
data?(["0"]) # true
|
||||
data?({}) # true
|
||||
data?(null) # false
|
||||
digit?(value)
|
||||
|
||||
Is the value a digit? If the value is a text with a length of 1 and is one of the 10 digit characters, then the result is true. Otherwise, the result is false.
|
||||
|
||||
digit?(0) # false
|
||||
digit?("0") # true
|
||||
digit?("9") # true
|
||||
digit?("09") # false
|
||||
digit?("digit") # false
|
||||
digit?("") # false
|
||||
digit?(1) # false
|
||||
digit?(["0"]) # false
|
||||
digit?("Z") # false
|
||||
false?(value)
|
||||
|
||||
Is the value false?
|
||||
|
||||
fit?(number)
|
||||
|
||||
Is the number a fit number? A number is a fit number if it is an integer that fits in 56 bits. All fit numbers are integers in the range -36028797018963968 thru 36028797018963967. Only fit numbers can be given to the fit functions. Misty has additional integers that are too big to fit.
|
||||
|
||||
function?(value)
|
||||
|
||||
Is the value a function? If the value is a function, then the result is true. Otherwise, the result is false.
|
||||
|
||||
function?(0) # false
|
||||
function?(function () (null)) # true
|
||||
function?("function") # false
|
||||
function?(null) # false
|
||||
function?(function?) # true
|
||||
integer?(value)
|
||||
|
||||
Is the value an integer? If the value is a number and if its fraction part is zero, then the result is true. Otherwise, the result is false.
|
||||
|
||||
integer?(0) # true
|
||||
integer?(13 / 4) # false
|
||||
integer?(16 / 4) # true
|
||||
integer?(65.0000000) # true
|
||||
integer?(65.0000001) # false
|
||||
integer?(null) # false
|
||||
integer?(true) # false
|
||||
integer?(1) # true
|
||||
integer?(36028797018963968) # true
|
||||
integer?(1.00001e100) # true
|
||||
letter?(value)
|
||||
|
||||
Is the value a letter? If the value is a text with a length of 1 and is a letter, then the result is true. Otherwise, the result is false.
|
||||
|
||||
letter?(0) # false
|
||||
letter?("0") # false
|
||||
letter?("letter") # false
|
||||
letter?("l") # true
|
||||
letter?("L") # true
|
||||
letter?("") # false
|
||||
letter?(null) # false
|
||||
logical?(value)
|
||||
|
||||
Is the value a logical? A logical is either a false or a true. All other values are not logical.
|
||||
|
||||
logical?(false) # true
|
||||
logical?(true) # true
|
||||
logical?(0) # false
|
||||
logical?() # false
|
||||
logical?(null) # false
|
||||
lower?(value)
|
||||
|
||||
Is the value a lower case letter? If the value is a text with a length of 1 and is a lower case letter, then the result is true. Otherwise, the result is false.
|
||||
|
||||
lower?(0) # false
|
||||
lower?("0") # false
|
||||
lower?("lower") # false
|
||||
lower?("l") # true
|
||||
lower?("L") # false
|
||||
lower?("") # false
|
||||
null?(value)
|
||||
|
||||
Is the value null? This does the same thing as value = null.
|
||||
|
||||
number?(value)
|
||||
|
||||
Is the value a number? If the value is a number, then the result is true. Otherwise, the result is false.
|
||||
|
||||
number?(0) # true
|
||||
number?((13 / 4)) # true
|
||||
number?((13 / 0)) # false
|
||||
number?(98.6) # true
|
||||
number?("0") # false
|
||||
number?(1) # true
|
||||
pattern?(value)
|
||||
|
||||
Is the value a pattern? If the value is a pattern, then the result is true. Otherwise, the result is false.
|
||||
|
||||
pattern?(pattern (1- {letter digit "_-%"})) # true
|
||||
record?(value)
|
||||
|
||||
Is the value a record? If the value is a record, then the result is true. Otherwise, the result is false.
|
||||
|
||||
record?(0) # false
|
||||
record?({}) # true
|
||||
record?([]) # false
|
||||
record?("record") # false
|
||||
record?("{}") # false
|
||||
record?(function () ({})) # false
|
||||
record?(pattern (1- {letter digit "_-%"})) # false
|
||||
record?(@) # true
|
||||
stone?(value)
|
||||
|
||||
Is the value stone?
|
||||
|
||||
stone?("false") # true
|
||||
stone?(9) # true
|
||||
stone?(null) # true
|
||||
stone?({}) # false
|
||||
stone?(stone({})) # true
|
||||
text?(value)
|
||||
|
||||
Is the value a text? If the value is a text, then the result is true. Otherwise, the result is false.
|
||||
|
||||
text?(0) # false
|
||||
text?("0") # true
|
||||
text?("number") # true
|
||||
text?("") # true
|
||||
text?(null) # false
|
||||
true?(value)
|
||||
|
||||
Is the value true?
|
||||
|
||||
upper?(value)
|
||||
|
||||
Is the value an upper case letter? If the value is a text with a length of 1 and is an upper case letter, then the result is true. Otherwise, the result is false.
|
||||
|
||||
upper?(0) # false
|
||||
upper?("0") # false
|
||||
upper?("UPPER") # false
|
||||
upper?("u") # false
|
||||
upper?("U") # true
|
||||
upper?("") # false
|
||||
whitespace?(value)
|
||||
|
||||
Is the value whitespace? If the value is a nonempty text containing only whitespace characters, then the result is true. Otherwise, the result is false.
|
||||
|
||||
whitespace?(0) # false
|
||||
whitespace?(32) # false
|
||||
whitespace?(char(32)) # true
|
||||
whitespace?("0") # false
|
||||
whitespace?(" ") # true
|
||||
whitespace?("\t") # true
|
||||
whitespace?("\r") # true
|
||||
whitespace?("\r\n") # true
|
||||
whitespace?("space") # false
|
||||
whitespace?(" ") # true
|
||||
whitespace?(" L") # false
|
||||
whitespace?("") # false
|
||||
Standard Functions
|
||||
|
||||
abs(number)
|
||||
|
||||
Absolute value. Return the positive form of the number. If the input value is not a number, the result is null.
|
||||
|
||||
apply(function, array)
|
||||
|
||||
Apply. Execute the function and return its return value. Pass the elements of the array as input values. See proxy.
|
||||
|
||||
If the first input value is not a function, apply returns its first input value.
|
||||
|
||||
If length(array) is greater than length(function), it disrupts.
|
||||
|
||||
If the second argument is not an array, it is used as a single input value.
|
||||
|
||||
ceiling(number, place)
|
||||
|
||||
If place is 0 or null, the number is rounded up to the smallest integer that is greater than or equal to the number. If place is a small positive integer, then the number is rounded up to that decimal place.
|
||||
|
||||
Examples:
|
||||
|
||||
assign result: ceiling(12.3775) # result is 13
|
||||
assign result: ceiling(12.3775, 0) # result is 13
|
||||
assign result: ceiling(12.3775, 1) # result is 20
|
||||
assign result: ceiling(12.3775, -2) # result is 12.38
|
||||
assign result: ceiling(-12.3775, -2) # result is -12.37
|
||||
assign result: ceiling(-12.3775) # result is -12
|
||||
character(value)
|
||||
|
||||
If the value is a text, it returns the first character. If the value is a non-negative 32-bit integer, it returns the character from that codepoint. Otherwise, it returns the empty string.
|
||||
|
||||
codepoint(text)
|
||||
|
||||
The codepoint function returns the codepoint number of the first character of the text. If the input value is not a text, or if it is the empty text, then it returns null.
|
||||
|
||||
extract(text, pattern, from, to)
|
||||
|
||||
The text is matched to the pattern. If it does not match, the result is null. If the pattern does match, then the result is a record containing the saved fields.
|
||||
|
||||
fallback(requestor_array)
|
||||
|
||||
The fallback requestor factory returns a requestor function that tries each of the requestors in the requstor_array until it gets a success. When the requestor is called, it calls the first requestor in requestor_array. If that is eventually successful, its value is passed to the callback. But if that requestor fails, the next requestor is called, and so on. If none of the requestors is successful, then the fallback fails. If any one succeeds, then the fallback succeeds.
|
||||
|
||||
The fallback requestor returns a cancel function that can be called when the result is no longer needed.
|
||||
|
||||
filter(array, function)
|
||||
|
||||
The filter function calls a function for every element in the array, passing each element and its element number.
|
||||
|
||||
(element, element_nr)
|
||||
When the function's return value is true, then the element is copied into a new array. If the function's return value is false, then the element is not copied into the new array. If the return value is not a logical, then the filter returns null.
|
||||
|
||||
It returns a new array. The length of the new array is between 0 thru length(array). It returns null if the function input is not a function.
|
||||
|
||||
Example:
|
||||
|
||||
def data: [0, 1.25, 2, 3.5, 4, 5.75]
|
||||
def integers: filter(data, integer?) # integers is [0, 2, 4]
|
||||
find(array, function, reverse, from)
|
||||
|
||||
Call the function for each element of the array, passing each element and its element number.
|
||||
|
||||
(element, element_nr)
|
||||
If the function returns true, then find returns the element number of the current element.
|
||||
|
||||
If the second input value is not a function, then it is compared exactly to the elements.
|
||||
|
||||
If the reverse input value is true, then search begins at the end of the array and works backward.
|
||||
|
||||
The from input value gives the element number to search first. The default is 0 unless reverse is true, when the default is length(array) - 1.
|
||||
|
||||
find returns the element number of the found value. If nothing is found, find returns null.
|
||||
|
||||
floor(number, place)
|
||||
|
||||
If place is 0 or null, the number is rounded down to the greatest integer that is less than or equal to the number. If place is a small positive integer, then the number is rounded down to that many decimal places. For positive numbers, this is like discarding decimal places.
|
||||
|
||||
Examples:
|
||||
|
||||
assign result: floor(12.3775) # result is 12
|
||||
assign result: floor(12.3775, -2) # result is 12.37
|
||||
assign result: floor(-12.3775, 0) # result is -13
|
||||
assign result: floor(-12.3775, -2) # result is -12.38
|
||||
for(array, function, reverse, exit)
|
||||
|
||||
For each. Call the function with each element of the array. The function is passed each element and its element number.
|
||||
|
||||
(element, element_nr)
|
||||
If reverse is true, then it starts with the last element and works backwards.
|
||||
|
||||
If exit is not null, then when the function returns the exit value, then the for function returns early. The exit value is usually true or false, but it may be anything. If exit is null, then every element is processed.
|
||||
|
||||
The for function returns null unless it did an early exit, when it returns the exit value.
|
||||
|
||||
format(text, collection, transformer)
|
||||
|
||||
The format function makes a new text with substitutions in an original text. A collection is either an array of texts or a record of texts.
|
||||
|
||||
A search is made for {left brace and }right brace in the text. If they are found, the middle text between them is examined. If the collection is an array, the middle text is used as a number, and then the matched {left brace and middle and }right brace are replaced with the text at that subscript in the array. If the collection is a record, and if the middle text is the key of a member of the collection with a text value, then the value of the member is used in the substitution. Unmatched text is not altered.
|
||||
|
||||
The text between {left brace and }right brace is broken on the :colon character. The left text will be used as a number or name to select a value from the collection. (The value need not be a text.)
|
||||
|
||||
If a transformer input is a function, then it is called with the collection[left text] and right text as inputs. If it returns a text, then the substitution is made.
|
||||
|
||||
If a transformer input is a record, then the right text is used to select a function from the transformer. That function is passed the value from the collection. If the return value is a text, that text will substitute. If there is no colon, then the empty text is used to select the function from the transformer. If the transformer does not produce a function, or if the function does not return a text, then no replacement occurs. If transformer[right text](collection[left text]) produces a text, then the substitution is made.
|
||||
|
||||
If the substitution is not made, and if collection[left text] is a number, then the right text is used as a format input in calling collection[left text].text(right text). If it returns a text, then the substitution is made.
|
||||
|
||||
Example:
|
||||
|
||||
var result: format("{0} in {1}!", ["Malmborg", "Plano"])
|
||||
# result is "Malmborg in Plano!"
|
||||
fraction(number)
|
||||
|
||||
The fraction function returns the fractional part of a number It returns null for non-numbers. Also see whole.
|
||||
|
||||
length(value)
|
||||
|
||||
The length function
|
||||
value result
|
||||
array number of elements
|
||||
blob number of bits
|
||||
logical null
|
||||
function number of named inputs
|
||||
null null
|
||||
number null
|
||||
record record.length()
|
||||
text number of codepoints
|
||||
Length. Find the length of an array in elements, a blob in bits, or a text in codepoints. For functions, it is the arity (or number of inputs).
|
||||
|
||||
If the value is a record containing a length field
|
||||
|
||||
If the length field contains a function, then length(my_record) has the same effect as my_record.length().
|
||||
|
||||
If the length field contains a number, return the number.
|
||||
|
||||
All other values produce null.
|
||||
|
||||
lower(text)
|
||||
|
||||
The lower function returns a text in which all uppercase characters are converted to lowercase.
|
||||
|
||||
Example:
|
||||
|
||||
assign result: lower("Carl Hollywood") # result is "carl hollywood"
|
||||
max(number, number)
|
||||
|
||||
Maximum. The result is the larger of the two numbers. If either input value is not a number, the result is null.
|
||||
|
||||
max(3, 4) # 4
|
||||
max can be used with min to constrain values within an acceptable range.
|
||||
|
||||
min(max(2, 3), 7) # 3
|
||||
min(max(4, 3), 7) # 4
|
||||
min(max(8, 3), 7) # 7
|
||||
max(1, null) # null
|
||||
max(null, 1) # null
|
||||
min(number,number)
|
||||
|
||||
Minimum. The result is the smaller of the two numbers. If either input value is not a number, the result is null.
|
||||
|
||||
min(3, 4) # 3
|
||||
modulo(dividend, divisor)
|
||||
|
||||
The result of modulo(dividend, divisor) is dividend - (divisor * floor(dividend / divisor)). The result has the sign of the divisor.
|
||||
|
||||
If dividend is 0, then the result is 0. If divisor is 0, or if either operand is not a number, then the result is null. dividend and divisor are not required to be integers.
|
||||
|
||||
If both input values are integers, and if the divisor is a positive power of two, then it is the same as and(dividend, divisor - 1).
|
||||
|
||||
neg(number)
|
||||
|
||||
Negate. Reverse the sign of a number. If the input value is not a number, the result is null. Note that the -minus sign is not used as a prefix negation operator.
|
||||
|
||||
normalize(text)
|
||||
|
||||
Unicode normalize. Return a text whose textual value is the same as text, but whose binary representation is in the specified Unicode normalization form. The two texts will display the same, but might not be equal.
|
||||
|
||||
not(logical)
|
||||
|
||||
Not. Return the opposite logical. If the input value is not a logical, it returns null.
|
||||
|
||||
parallel(requestor_array, throttle, need)
|
||||
|
||||
Parallel. The parallel requestor factory returns a resquestor function. When the requestor function is called with a callback function and a value, every requestor in the requestor_array is called with the value, and the results are placed in corresponding elements of the results array. This all happens in parallel. The value produced by the first element of the requestor_array provides the first element of the result. The completed results array is passed to the callback function.
|
||||
|
||||
By default, it starts all of the requestors in the requestor_array at once, each in its own turn so that they do not interfere with each other. This can shock some systems by unleashing a lot of demand all at once. To mitigate the shock, the optional throttle argument sets the maximum number of requestors running at a time. As requestors succeed or fail, waiting requestors can be started. The throttle is optional. If provided, the throttle is a number: the maximum number of requestors to have running at any time.
|
||||
|
||||
Ordinarily, the number of successes must be the same as the number of requestors in the requestor_array. If you need few successes, specify your need with the need argument. The need could be 1, meaning that 1 or more successes are needed. The need could be 0, meaning that no successes are needed. If the number of successes is greater than or equal to need, then the whole operation succeeds. The need must be between 0 and requestor_array.length.
|
||||
|
||||
The requestor function itself returns a cancel function that cancels all of the pending requestors from the requestor_array.
|
||||
|
||||
race(requestor_array, throttle, need)
|
||||
|
||||
Race. The race function is a requestor factory that takes an array of requestor functions and returns a requestor function that starts all of the requestors in the requestor_array at once.
|
||||
|
||||
By default, it starts all of the requestors in the requestor_array at once, each in its own turn so that they do not interfere with each other. This can shock some systems by unleashing a lot of demand at once. To mitigate the shock, the optional throttle argument sets the maximum number of requestors running at a time. As requestors succeed or fail, waiting requestors can be started.
|
||||
|
||||
By default, a single result is produced. If an array of results is need, specify the needed number of results in the need parameter. When the needed number of successful results is obtained, the operation ends. The results go into a sparce array aligned with the requestor_array, and unfinished requestors are cancelled. The need argument must be between 1 and requestor_array.length.
|
||||
|
||||
The returned requestor function returns a cancel function that cancels all of the pending requestors from the requestor_array.
|
||||
|
||||
reduce(array, function, initial, reverse)
|
||||
|
||||
Reduce. The reduce function takes a function that takes two input values and returns a value.
|
||||
|
||||
function (first, second) {
|
||||
return ...
|
||||
}
|
||||
The function is called for each element of the array, passing the result of the previous iteration to the next iteration.
|
||||
|
||||
The initial value is optional. If present, the function is called for every element of the array.
|
||||
|
||||
If initial is null:
|
||||
|
||||
If length(array) is 0, then it returns null.
|
||||
If length(array) is 1, then it returns array[0].
|
||||
If length(array) is 2, it returns function(array[0], array[1]).
|
||||
If length(array) is 3, it returns function(function(array[0], array[1]), array[2]).
|
||||
And so on.
|
||||
If initial is not null:
|
||||
|
||||
If length(array) is 0, then it returns initial.
|
||||
If length(array) is 1, then it returns function(initial, array[0]).
|
||||
If length(array) is 2, it returns function(function(initial, array[0]), array[1]).
|
||||
If length(array) is 3, it returns function(function(function(initial, array[0]), array[1]), array[2]).
|
||||
And so on.
|
||||
If reverse is true, then the work begins at the end of the array and works backward.
|
||||
|
||||
Example:
|
||||
|
||||
def data: [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
def total: reduce(data, '+) # total is 45
|
||||
def product: reduce(data, '*) # product is 362880
|
||||
remainder(dividend, divisor)
|
||||
|
||||
Remainder. For fit integers, the remainder is dividend - ((dividend // divisor) * divisor).
|
||||
|
||||
replace(text, target, replacement, limit)
|
||||
|
||||
Return a new text in which the target is replaced by the replacement.
|
||||
|
||||
text: the source text.
|
||||
|
||||
target: a text or pattern that should be replaced in the source.
|
||||
|
||||
replacement: text to replace the matched text, or a function that takes the matched text and the starting position, and returns the replacement text or null if it should be left alone.
|
||||
|
||||
limit: The maximum number of replacements. The default is all possible replacements. The limit includes null matches.
|
||||
|
||||
reverse(array)
|
||||
|
||||
The reverse function makes a new array or blob with the elements or bits in the opposite order.
|
||||
|
||||
It returns a new, reversed array or blob.
|
||||
|
||||
Example:
|
||||
|
||||
def data: ["I", "am", "Sam"]
|
||||
def result: reverse(data) # the result is ["Sam", "am", "I"]
|
||||
round(number, place)
|
||||
|
||||
If place is 0 or null, the number is rounded to the nearest integer. If place is a small integer, then the number is rounded to that many decimal places.
|
||||
|
||||
Examples:
|
||||
|
||||
round(12.3775) # 12
|
||||
round(12.3775, -2) # 12.38
|
||||
round(-12.3775, 0) # -12
|
||||
round(-12.3775, 1) # -10
|
||||
round(-12.3775, 2) # 0
|
||||
round(-12.3775, -2) # -12.38
|
||||
search(text, target, from)
|
||||
|
||||
Search the text for the target. If the target is found, return the character position of the left-most part of the match. If the search fails, return null.
|
||||
|
||||
text: the source text.
|
||||
|
||||
target: a text or pattern that should be found in the source.
|
||||
|
||||
from: The starting position for the search. The default is 0, the beginning of the text. If from is negative, it is added to length(text).
|
||||
|
||||
sequence(requestor_array)
|
||||
|
||||
Sequence. The sequence requestor factory that takes a requestor_array and returns a requestor function that starts all of the requestors in the requestor_array one at a time. The result of each becomes the input to the next. The last result is the result of the sequence.
|
||||
|
||||
sequence returns a requestor that returns a cancel function and processes each requestor in requestor_array one at a time. Each of those requestors is passed the result of the previous requestor as its value argument. If all succeed, then the sequence succeeds, giving the result of the last of the requestors. If any fail, then the sequence fails.The sequence succeeds if every requestor in the requestor_array succeeds
|
||||
|
||||
sign(number)
|
||||
|
||||
The sign function returns -1 if the number is negative, 0 if the number is exactly 0, 1 if the number is positive, and null if it is not a number.
|
||||
|
||||
sort(array, select)
|
||||
|
||||
The sort function produces a new array in which the values are sorted. Sort keys must be either all numbers or all texts. Any other type of key or any error in the key calculation will cause the sort to fail, returning null. The sort is ascending. The sort is stable, so the relative order of equal keys is preserved.
|
||||
|
||||
The optional select input determines how the sort key for each element is selected.
|
||||
|
||||
select type Sort key for array[index] Description
|
||||
null array[index] The sort key is the element itself. This is useful for sorting simple arrays of numbers or texts.
|
||||
text array[index][select] The sort key is the select field of each record element. This is useful for sorting arrays of records.
|
||||
number array[index][select] The sort key is the select element of each array element. This is useful for sorting arrays of arrays.
|
||||
array select[index] select is an array of the same length containing the sort keys.
|
||||
It returns a new, sorted array.
|
||||
|
||||
Examples:
|
||||
|
||||
def foods: ["oats", "peas", "beans", "barley"]
|
||||
def result: sort(foods) # result is ["barley", "beans", "oats", "peas"]
|
||||
|
||||
var stooges: [
|
||||
{first: "Moe", last: "Howard"}
|
||||
{first: "Joe", last: "DeRita"}
|
||||
{first: "Shemp", last: "Howard"}
|
||||
{first: "Larry", last: "Fine"}
|
||||
{first: "Joe", last: "Besser"}
|
||||
{first: "Curly", last: "Howard"}
|
||||
]
|
||||
assign stooges: sort(sort(stooges, "first"), "last")
|
||||
# stooges is now [
|
||||
# {first: "Joe", last: "Besser"}
|
||||
# {first: "Joe", last: "DeRita"}
|
||||
# {first: "Larry", last: "Fine"}
|
||||
# {first: "Curly", last: "Howard"}
|
||||
# {first: "Moe", last: "Howard"}
|
||||
# {first: "Shemp", last: "Howard"}
|
||||
# ]
|
||||
assign stooges: sort(stooges, [50, 60, 20, 40, 10, 30])
|
||||
# stooges is now [
|
||||
# {first: "Moe", last: "Howard"}
|
||||
# {first: "Larry", last: "Fine"}
|
||||
# {first: "Shemp", last: "Howard"}
|
||||
# {first: "Curly", last: "Howard"}
|
||||
# {first: "Joe", last: "Besser"}
|
||||
# {first: "Joe", last: "DeRita"}
|
||||
# ]
|
||||
stone(value)
|
||||
|
||||
Petrify the value, turning it into stone. Its contents are preserved, but it can no longer be modified by the assign statement or the blob.write functions. This is usually performed on arrays, records, and blobs. All other types are already stone. Attempting to turn a value that is stone to stone will have no effect.
|
||||
|
||||
The stone operation is deep. Any mutable objects in the value will also be turned to stone.
|
||||
|
||||
This can not be reversed. Immutable objects can never become mutable. A mutable copy can be made of an immutable object, but the original object remains immutable.
|
||||
|
||||
The stone function returns the value.
|
||||
|
||||
trim(text, reject)
|
||||
|
||||
The trim function removes selected characters from the ends of a text. The default is to remove control characters and spaces.
|
||||
|
||||
assign result: " Hello there ".trim() # result is "Hello there"
|
||||
trunc(number, place)
|
||||
|
||||
The number is truncated toward zero. If the number is positive, the result is the same as floor(place). If the number is negative, the result is the same as ceiling(place).
|
||||
|
||||
If place is a small integer, then the number is rounded down to that many decimal places. This is like discarding decimal places.
|
||||
|
||||
Examples:
|
||||
|
||||
assign result: trunc(12.3775, 0) # result is 12
|
||||
assign result: trunc(12.3775, 2) # result is 12.37
|
||||
assign result: trunc(-12.3775) # result is -12
|
||||
assign result: trunc(-12.3775, 2) # result is -12.37
|
||||
turkish_lower(text)
|
||||
|
||||
Similar to lower, except that I goes to ıdotless i.
|
||||
|
||||
turkish_upper(text)
|
||||
|
||||
Similar to upper, except that i goes to İI dot.
|
||||
|
||||
upper(text)
|
||||
|
||||
The upper function returns a text in which all lowercase characters are converted to uppercase.
|
||||
|
||||
Example:
|
||||
|
||||
assign result: upper("Carl Hollywood") # result is "CARL HOLLYWOOD"
|
||||
whole(number)
|
||||
|
||||
The whole function returns the whole part of a number. It returns null for non-numbers. Also see fraction.
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||

|
||||
|
||||
Cell is an actor-based scripting language for building concurrent applications. It combines a familiar JavaScript-like syntax with the actor model of computation.
|
||||
Cell is an actor-based scripting language for building concurrent applications. It combines a familiar C-like syntax with the actor model of computation, optimized for low memory usage and simplicity.
|
||||
|
||||
## Key Features
|
||||
|
||||
|
||||
248
docs/memory.md
Normal file
248
docs/memory.md
Normal file
@@ -0,0 +1,248 @@
|
||||
# Cell actor scripting language
|
||||
|
||||
Cell is a Misty [https://mistysystem.com](https://mistysystem.com) implementation.
|
||||
|
||||
## Memory
|
||||
Values are 32 bit for 32 bit builds and 64 bit for 64 bit builds.
|
||||
|
||||
### 32 bit value
|
||||
|
||||
LSB = 0
|
||||
payload is a 31 bit signed int
|
||||
|
||||
LSB = 01
|
||||
payload is a 30 bit pointer
|
||||
|
||||
LSB = 11
|
||||
next 3 bits = special tag. 27 bits of payload.
|
||||
|
||||
### 64 bit value
|
||||
LSB = 0
|
||||
payload is a 32 bit signed int, using high 32 bits
|
||||
|
||||
LSB = 01
|
||||
payload is a 61 bit pointer
|
||||
|
||||
LSB = 101
|
||||
Short float: a 61 bit double, with 3 less exponent bits
|
||||
|
||||
LSB = 11
|
||||
Special tag: next 3 bits. 5 bits total. 59 bits of payload. 8 total special tags.
|
||||
|
||||
Special tags:
|
||||
1: Bool. Payload is 0 or 1.
|
||||
2: null. payload is 0.
|
||||
3: exception.
|
||||
4: string.
|
||||
Immediate string. Next 3 low bits = length in bytes. Rest is string data. This allows for strings up to 7 ascii letters. Encoded in utf8.
|
||||
|
||||
## Numbers and math
|
||||
Cell can be compiled with different levels of exactness for numeracy. Any number which cannot be represented exactly becomes "null". Any numeric operation which includes "null" results in "null".
|
||||
|
||||
Using short floats in a 64 bit system means you have doubles in the range of +- 10^38, not the full range of double. If you create a number out of that range, it's null.
|
||||
|
||||
You can also compile a 64 bit system with full precision doubles, but this will use more memory and may be slower.
|
||||
|
||||
You can also compile a 64 bit system with 32 bit floats, stored as a 32 bit int is. Again, out of the 32 bit float range = null.
|
||||
|
||||
You can compile without floating point support at all; 32 bit ints are then used for fixed point calculations.
|
||||
|
||||
Or, you can compile using Dec64, which is a 64 bit decimal floating point format, for exact precision.
|
||||
|
||||
## Objects
|
||||
Objects are heap allocated, referenced by a pointer value. They are all preceded by an object header, the length of a word on the system.
|
||||
|
||||
### 64 bit build
|
||||
56 bits capacity
|
||||
1 bit memory reclamation flag: note that this obj has already been moved
|
||||
2 bit reserved (per object)
|
||||
1 bit stone: note that this obj is immutable
|
||||
3 bit type: note the type of the object
|
||||
1 bit: fwd: note that this obj is a forward linkage
|
||||
|
||||
Last bit ..1:
|
||||
The forward type indicates that the object (an array, blob, pretext, or record) has grown beyond its capacity and is now residing at a new address. The remaining 63 bits contain the address of the enlarged object. Forward linkages are cleaned up by the memory reclaimer.
|
||||
|
||||
Type 7: C light C object
|
||||
|
||||
Header
|
||||
Pointer
|
||||
|
||||
Capacity is an ID of a registered C type.
|
||||
Pointer is a pointer to the opaque C object.
|
||||
|
||||
Type 0: Array
|
||||
Header
|
||||
Length
|
||||
Element[]
|
||||
|
||||
Capacity is number of elements the array can hold. Length is number of elements in use. Number of words used by an array is capacity + 2.
|
||||
|
||||
Type 1: blob
|
||||
Header
|
||||
Length
|
||||
Bit[]
|
||||
Capacity is number of bits the blob can hold. Length is number of bits in use. Bits follow, from [0] to [capacity - 1], with [0] bit in the most significant position of word 2, and [63] in the least significant position of word 2. The last word is zero filled, if necessary.
|
||||
|
||||
Number of words used is (capacity + 63) // 64 + 2
|
||||
|
||||
Type 2: Text
|
||||
Text has two forms, depending on if it is stone or not, which changes the meaning of its length word.
|
||||
|
||||
Header
|
||||
Length(pretext) or Hash(text)
|
||||
Character[0] and character[1]
|
||||
|
||||
Capacity of pretex is the number of characters it can hold. During stoning and reclamation, capacity is set to the length.
|
||||
|
||||
The capacity of a text is its length.
|
||||
|
||||
The length of a pretext is the number of characters it contains; it is not greater than the capacity.
|
||||
|
||||
Hash of a text is used for organizing records. If the hash is zero, it's not been computed yet. All texts in the immutable memory have hashes.
|
||||
|
||||
A text object contains UTF32 characters, packed two per word. If the number of characters is odd, the least significant half of the last word is zero filled.
|
||||
|
||||
The number of words used by a text is (capacity + 1) // 2 + 2
|
||||
|
||||
Type 3: Record
|
||||
|
||||
A record is an array of fields represented as key/value pairs. Fields are located by hashes of texts, using open addressing with linear probing and lazy deletion. The load factor is less than 0.5.
|
||||
|
||||
Header
|
||||
Prototype
|
||||
Length
|
||||
Key[0]
|
||||
Value[0]
|
||||
Key[1]
|
||||
Value[1]
|
||||
...
|
||||
|
||||
The capacity is the number of fields the record can hold. It is a power of two minus one. It is at least twice the length.
|
||||
|
||||
The length is the number of fields that the record currently contains.
|
||||
|
||||
A field candidate number is identified by and(key.hash, capacity). In case of hash collision, advance to the next field. If this goes past the end, continue with field 1. Field 0 is reserved.
|
||||
|
||||
The "exception" special tag is used to mark deleted entries in the object map.
|
||||
|
||||
The number of words used by a record is (capacity + 1) * 2.
|
||||
|
||||
Prototypes are searched for for properties if one cannot be found on the record itself. Prototypes can have prototypes.
|
||||
|
||||
#### key[0] and value[0]
|
||||
These are reserved for internal use, and skipped over during key probing.
|
||||
|
||||
The first 32 bits of key are used as a 32 bit integer key, if this object has ever been used as a key itself.
|
||||
|
||||
The last 32 bits are used as an opaque C class key. C types can be registered with the system, and each are assigned a monotonically increasing number. In the case that this object has a C type, then the bottom 32 bits of key[0] are not 0. If that is the case, then a pointer to its C object is stored in value[0].
|
||||
|
||||
#### Valid keys & Hashing
|
||||
Keys are stored directly in object maps. There are three possibilities for a vaild key: an object text, an object record, or an immediate text.
|
||||
|
||||
In the case of an immediate text, the hash is computed on the fly using the fash64_hash_one function, before being used to look up the key in the object map. Direct value comparison is used to confirm the key.
|
||||
|
||||
For object texts (texts longer than 7 ascii chars), the hash is stored in the text object itself. When an object text is used as a key, a stone version is created and interned. Any program static texts reference this stoned, interned text. When looking up a heap text as a key, it is first discovered if it's in the interned table. If it's not, the key is not in the object (since all keys are interned). If it is, the interned version is returned to check against the object map. The hash of the interned text is used to look up the key in the object map, and then direct pointer comparison is used to confirm the key.
|
||||
|
||||
For record keys, these are unique; once a record is used as a key, it gets assigned a monotonically increasing 32 bit integer, stored in key[0]. When checking it in an object map, the integer is used directly as the key. If key[0] is 0, the record has not been used as a key yet. If it's not 0, fash64_hash_one is used to compute a hash of its ID, and then direct value pointer comparison is used to confirm.
|
||||
|
||||
### Text interning
|
||||
Texts that cannot fit in an immediate, and which are used as an object key, create a stoned and interned version (the pointer which is used as the key). Any text literals are also stoned and interned.
|
||||
|
||||
The interning table is an open addressed hash, with a load of 0.8, using a robin hood value. Probing is done using the text hash, confirmation is done using length, and then memcmp of the text.
|
||||
|
||||
When the GC run, a new interned text table is created. Each text literal, and each text used as a key, is added to the new table, as the live objects are copied. This keeps the interning table from becoming a graveyard. Interned values are never deleted until a GC.
|
||||
|
||||
Type 4: Function
|
||||
|
||||
Header
|
||||
Code
|
||||
Outer
|
||||
A function object has zero capacity and is always stone.
|
||||
|
||||
Code is a pointer to the code object that the function executes.
|
||||
|
||||
Outer is a pointer to the frame that created this function object.
|
||||
|
||||
Size is 3 words.
|
||||
|
||||
Type 5: Frame
|
||||
|
||||
Header
|
||||
Function
|
||||
Caller
|
||||
Return address
|
||||
|
||||
The activation frame is created when a function is invoked to hold its linkages and state.
|
||||
|
||||
The capacity is the number of slots, including the inputs, variables, temporaries, and the four words of overhead. A frame, unlike the other types, is never stone.
|
||||
|
||||
The function is the address of the function object being called.
|
||||
|
||||
The caller is the address of the frame that is invoking the function.
|
||||
|
||||
The return address is the address of the instruction in the code that should be executed upon return.
|
||||
|
||||
Next come the input arguments, if any.
|
||||
|
||||
Then the variables closed over by the inner functions.
|
||||
|
||||
Then the variables that are not closed over, followed by the temporaries.
|
||||
|
||||
When a function returns, the caller is set to zero. This is a signal to the memory reclaimer that the frame can be reduced.
|
||||
|
||||
Type 6: Code
|
||||
|
||||
Header
|
||||
Arity
|
||||
Size
|
||||
Closure size
|
||||
Entry point
|
||||
Disruption point
|
||||
|
||||
A code object exists in the actor's immutable memory. A code object never exists in mutable memory.
|
||||
|
||||
A code object has a zero capacity and is always stone.
|
||||
|
||||
The arity is the maximum number of inputs.
|
||||
|
||||
The size is the capacity of an activation frame that will execute this code.
|
||||
|
||||
The closure size is a reduced capacity for returned frames that survive memory reclamation.
|
||||
|
||||
The entry point is the address at which to begin execution.
|
||||
|
||||
The disruption point is the address of the disruption clause.
|
||||
|
||||
### opaque C objects
|
||||
Records can have opaque C data attached to them.
|
||||
|
||||
A C class can register a GC clean up, and a GC trace function. The trace function is called when the record is encountered in the live object graph; and it should mark any values it wants to keep alive in that function.
|
||||
|
||||
The system maintains an array of live opaque C objects. When such an object is encountered, it marks it as live in the array. When the GC completes, it iterates this array and calls the GC clean up function for each C object in the array with alive=0. Alive is then cleared for the next GC cycle.
|
||||
|
||||
## 32 bit build
|
||||
~3 bit type
|
||||
1 bit stone
|
||||
1 bit memory reclamation flag
|
||||
27 bit capacity
|
||||
|
||||
Key differences here are
|
||||
|
||||
blob max capacity is 2**27 bits = 2**24 bytes = 16 MB [this likely needs addressed]
|
||||
|
||||
fwd is type ...0, and the pointer is 31 bits
|
||||
other types are
|
||||
111 array
|
||||
101 object
|
||||
011 blob
|
||||
001
|
||||
|
||||
## Memory
|
||||
Cell uses a single block of memory that it doles out as needed to the actors in its system.
|
||||
|
||||
Actors are given a block of memory in standard sizes using a doubling buddy memory manager. An actor is given an immutable data section on birth, as well as a mutable data section. When its mutable data becomes full, it requests a new one. Actors utilize their mutable memory with a simple bump allocation. If there is not sufficient memory available, the actor suspends and its status changes to exhausted.
|
||||
|
||||
The smallest block size is determined per platform, but it can be as small as 4KB on 64 bit systems.
|
||||
|
||||
The actor is then given a new block of memory of the same size, and it runs a garbage collector to reclaim memory. It uses the cheney copying algorithm. If a disappointing amount of memory was reclaimed, it is noted, and the actor is given a larger block of memory on the next request.
|
||||
93
fash.c
Normal file
93
fash.c
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
Fash64: Douglas Crockford (2017-02-02)
|
||||
64-bit hash that uses the high 64 bits of a 128-bit product for feedback.
|
||||
|
||||
Notes:
|
||||
- Requires a way to get the high half of a 64x64->128 multiply.
|
||||
- Uses __uint128_t when available; otherwise uses MSVC _umul128.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct fash64_state {
|
||||
uint64_t result;
|
||||
uint64_t sum;
|
||||
} fash64_state;
|
||||
|
||||
enum {
|
||||
FASH64_PRIME_11 = 11111111111111111027ull,
|
||||
FASH64_PRIME_8 = 8888888888888888881ull,
|
||||
FASH64_PRIME_3 = 3333333333333333271ull
|
||||
};
|
||||
|
||||
static inline void fash64_mul_hi_lo(uint64_t a, uint64_t b, uint64_t *hi, uint64_t *lo)
|
||||
{
|
||||
#if defined(__SIZEOF_INT128__)
|
||||
__uint128_t p = (__uint128_t)a * (__uint128_t)b;
|
||||
*lo = (uint64_t)p;
|
||||
*hi = (uint64_t)(p >> 64);
|
||||
#elif defined(_MSC_VER) && defined(_M_X64)
|
||||
*lo = _umul128(a, b, hi);
|
||||
#else
|
||||
/* Portable fallback (no 128-bit type, no _umul128). */
|
||||
uint64_t a0 = (uint32_t)a;
|
||||
uint64_t a1 = a >> 32;
|
||||
uint64_t b0 = (uint32_t)b;
|
||||
uint64_t b1 = b >> 32;
|
||||
|
||||
uint64_t p00 = a0 * b0;
|
||||
uint64_t p01 = a0 * b1;
|
||||
uint64_t p10 = a1 * b0;
|
||||
uint64_t p11 = a1 * b1;
|
||||
|
||||
uint64_t mid = (p00 >> 32) + (uint32_t)p01 + (uint32_t)p10;
|
||||
*lo = (p00 & 0xffffffffull) | (mid << 32);
|
||||
*hi = p11 + (p01 >> 32) + (p10 >> 32) + (mid >> 32);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void fash64_begin(fash64_state *s)
|
||||
{
|
||||
s->result = (uint64_t)FASH64_PRIME_8;
|
||||
s->sum = (uint64_t)FASH64_PRIME_3;
|
||||
}
|
||||
|
||||
static inline void fash64_word(fash64_state *s, uint64_t word)
|
||||
{
|
||||
uint64_t high, low;
|
||||
uint64_t mixed = s->result ^ word;
|
||||
|
||||
fash64_mul_hi_lo(mixed, (uint64_t)FASH64_PRIME_11, &high, &low);
|
||||
|
||||
s->sum += high;
|
||||
s->result = low ^ s->sum;
|
||||
}
|
||||
|
||||
static inline void fash64_block(fash64_state *s, const uint64_t *block, size_t word_count)
|
||||
{
|
||||
for (size_t i = 0; i < word_count; i++) fash64_word(s, block[i]);
|
||||
}
|
||||
|
||||
static inline uint64_t fash64_end(const fash64_state *s)
|
||||
{
|
||||
return s->result;
|
||||
}
|
||||
|
||||
/* Convenience one-shot helper */
|
||||
static inline uint64_t fash64_hash_words(const uint64_t *words, size_t word_count, uint64_t extra_word)
|
||||
{
|
||||
fash64_state s;
|
||||
fash64_begin(&s);
|
||||
fash64_block(&s, words, word_count);
|
||||
fash64_word(&s, extra_word);
|
||||
return fash64_end(&s);
|
||||
}
|
||||
|
||||
static inline uint64_t fash64_hash_one(uint64_t word)
|
||||
{
|
||||
uint64_t high, low;
|
||||
uint64_t mixed = (uint64_t)FASH64_PRIME_8 ^ word;
|
||||
fash64_mul_hi_lo(mixed, (uint64_t)FASH64_PRIME_11, &high, &low);
|
||||
return low ^ ((uint64_t)FASH64_PRIME_3 + high);
|
||||
}
|
||||
8
fd.c
8
fd.c
@@ -50,7 +50,7 @@ JSC_SCALL(fd_open,
|
||||
mode_t mode = 0644;
|
||||
|
||||
// Parse optional flags argument
|
||||
if (argc > 1 && JS_IsString(argv[1])) {
|
||||
if (argc > 1 && JS_IsText(argv[1])) {
|
||||
const char *flag_str = JS_ToCString(js, argv[1]);
|
||||
flags = 0;
|
||||
|
||||
@@ -78,7 +78,7 @@ JSC_CCALL(fd_write,
|
||||
|
||||
size_t len;
|
||||
ssize_t wrote;
|
||||
if (JS_IsString(argv[1])) {
|
||||
if (JS_IsText(argv[1])) {
|
||||
const char *data = JS_ToCStringLen(js, &len, argv[1]);
|
||||
if (!data) return JS_EXCEPTION;
|
||||
wrote = write(fd, data, len);
|
||||
@@ -276,7 +276,7 @@ JSC_SCALL(fd_mkdir,
|
||||
JSC_SCALL(fd_mv,
|
||||
if (argc < 2)
|
||||
ret = JS_ThrowTypeError(js, "fd.mv requires 2 arguments: old path and new path");
|
||||
else if (!JS_IsString(argv[1]))
|
||||
else if (!JS_IsText(argv[1]))
|
||||
ret = JS_ThrowTypeError(js, "second argument must be a string (new path)");
|
||||
else {
|
||||
const char *new_path = JS_ToCString(js, argv[1]);
|
||||
@@ -289,7 +289,7 @@ JSC_SCALL(fd_mv,
|
||||
JSC_SCALL(fd_symlink,
|
||||
if (argc < 2)
|
||||
ret = JS_ThrowTypeError(js, "fd.symlink requires 2 arguments: target and link path");
|
||||
else if (!JS_IsString(argv[1]))
|
||||
else if (!JS_IsText(argv[1]))
|
||||
ret = JS_ThrowTypeError(js, "second argument must be a string (link path)");
|
||||
else {
|
||||
const char *link_path = JS_ToCString(js, argv[1]);
|
||||
|
||||
@@ -39,7 +39,7 @@ JSC_SCALL(fd_open,
|
||||
FileOptions flags = kFileRead;
|
||||
|
||||
// Parse optional flags argument
|
||||
if (argc > 1 && JS_IsString(argv[1])) {
|
||||
if (argc > 1 && JS_IsText(argv[1])) {
|
||||
const char *flag_str = JS_ToCString(js, argv[1]);
|
||||
flags = 0;
|
||||
|
||||
@@ -70,7 +70,7 @@ JSC_CCALL(fd_write,
|
||||
|
||||
size_t len;
|
||||
int wrote;
|
||||
if (JS_IsString(argv[1])) {
|
||||
if (JS_IsText(argv[1])) {
|
||||
const char *data = JS_ToCStringLen(js, &len, argv[1]);
|
||||
if (!data) return JS_EXCEPTION;
|
||||
wrote = pd_file->write(fd, data, (unsigned int)len);
|
||||
@@ -202,7 +202,7 @@ JSC_SCALL(fd_mkdir,
|
||||
JSC_SCALL(fd_mv,
|
||||
if (argc < 2)
|
||||
ret = JS_ThrowTypeError(js, "fd.mv requires 2 arguments: old path and new path");
|
||||
else if (!JS_IsString(argv[1]))
|
||||
else if (!JS_IsText(argv[1]))
|
||||
ret = JS_ThrowTypeError(js, "second argument must be a string (new path)");
|
||||
else {
|
||||
const char *new_path = JS_ToCString(js, argv[1]);
|
||||
@@ -216,7 +216,7 @@ JSC_SCALL(fd_mv,
|
||||
|
||||
JSC_SCALL(fd_symlink,
|
||||
// Not supported
|
||||
if (argc >= 2 && JS_IsString(argv[1])) {
|
||||
if (argc >= 2 && JS_IsText(argv[1])) {
|
||||
// consume arg
|
||||
JS_FreeCString(js, JS_ToCString(js, argv[1]));
|
||||
}
|
||||
|
||||
403
gc_plan.md
Normal file
403
gc_plan.md
Normal file
@@ -0,0 +1,403 @@
|
||||
# Plan: Complete Copying GC Implementation
|
||||
|
||||
## Overview
|
||||
|
||||
Remove reference counting (DupValue/FreeValue) entirely and complete the Cheney copying garbage collector. Each JSContext will use bump allocation from a heap block, and when out of memory, request a new heap from JSRuntime's buddy allocator and copy live objects to the new heap.
|
||||
|
||||
## Target Architecture (from docs/memory.md)
|
||||
|
||||
### Object Types (simplified from current):
|
||||
|
||||
**Type 0 - Array**: `{ header, length, elements[] }`
|
||||
**Type 1 - Blob**: `{ header, length, bits[] }`
|
||||
**Type 2 - Text**: `{ header, length_or_hash, packed_chars[] }`
|
||||
**Type 3 - Record**: `{ header, prototype, length, key_value_pairs[] }`
|
||||
**Type 4 - Function**: `{ header, code_ptr, outer_frame_ptr }` - 3 words only, always stone
|
||||
**Type 5 - Frame**: `{ header, function_ptr, caller_ptr, ret_addr, args[], closure_vars[], local_vars[], temps[] }`
|
||||
**Type 6 - Code**: Lives in immutable memory only, never copied
|
||||
**Type 7 - Forward**: Object has moved; cap56 contains new address
|
||||
|
||||
### Key Design Points:
|
||||
- **JSFunction** is just a pointer to code and a pointer to the frame that created it (3 words)
|
||||
- **Closure variables live in frames** - when a function returns, its frame is "reduced" to just the closure variables
|
||||
- **Code objects are immutable** - stored in stone memory, never copied during GC
|
||||
- **Frame reduction**: When a function returns, `caller` is set to zero, signaling the frame can be shrunk
|
||||
|
||||
## Current State (needs refactoring)
|
||||
|
||||
1. **Partial Cheney GC exists** at `source/quickjs.c:1844-2030`: `ctx_gc`, `gc_copy_value`, `gc_scan_object`
|
||||
2. **744 calls to JS_DupValue/JS_FreeValue** scattered throughout (currently undefined, causing compilation errors)
|
||||
3. **Current JSFunction** is bloated (has kind, name, union of cfunc/bytecode/bound) - needs simplification
|
||||
4. **Current JSVarRef** is a separate object - should be eliminated, closures live in frames
|
||||
5. **Bump allocator** in `js_malloc` (line 1495) with `heap_base`/`heap_free`/`heap_end`
|
||||
6. **Buddy allocator** for memory blocks (lines 1727-1837)
|
||||
7. **Header offset inconsistency** - some structs have header at offset 0, some at offset 8
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Define No-Op DupValue/FreeValue (To Enable Compilation)
|
||||
|
||||
Add these near line 100 in `source/quickjs.c`:
|
||||
|
||||
```c
|
||||
/* Copying GC - no reference counting needed */
|
||||
#define JS_DupValue(ctx, v) (v)
|
||||
#define JS_FreeValue(ctx, v) ((void)0)
|
||||
#define JS_DupValueRT(rt, v) (v)
|
||||
#define JS_FreeValueRT(rt, v) ((void)0)
|
||||
```
|
||||
|
||||
This makes the code compile while keeping existing call sites (they become no-ops).
|
||||
|
||||
### Phase 2: Standardize Object Headers (offset 0)
|
||||
|
||||
Remove `JSGCObjectHeader` (ref counting remnant) and put `objhdr_t` at offset 0:
|
||||
|
||||
```c
|
||||
typedef struct JSArray {
|
||||
objhdr_t hdr; // offset 0
|
||||
word_t length;
|
||||
JSValue values[];
|
||||
} JSArray;
|
||||
|
||||
typedef struct JSRecord {
|
||||
objhdr_t hdr; // offset 0
|
||||
JSRecord *proto;
|
||||
word_t length;
|
||||
slot slots[];
|
||||
} JSRecord;
|
||||
|
||||
typedef struct JSText {
|
||||
objhdr_t hdr; // offset 0
|
||||
word_t length; // pretext: length, text: hash
|
||||
word_t packed[];
|
||||
} JSText;
|
||||
|
||||
typedef struct JSBlob {
|
||||
objhdr_t hdr; // offset 0
|
||||
word_t length;
|
||||
uint8_t bits[];
|
||||
} JSBlob;
|
||||
|
||||
/* Simplified JSFunction per memory.md - 3 words */
|
||||
typedef struct JSFunction {
|
||||
objhdr_t hdr; // offset 0, always stone
|
||||
JSCode *code; // pointer to immutable code object
|
||||
struct JSFrame *outer; // frame that created this function
|
||||
} JSFunction;
|
||||
|
||||
/* JSFrame per memory.md */
|
||||
typedef struct JSFrame {
|
||||
objhdr_t hdr; // offset 0
|
||||
JSFunction *function; // function being executed
|
||||
struct JSFrame *caller; // calling frame (NULL = reduced/returned)
|
||||
word_t ret_addr; // return instruction address
|
||||
JSValue slots[]; // args, closure vars, locals, temps
|
||||
} JSFrame;
|
||||
|
||||
/* JSCode - always in immutable (stone) memory */
|
||||
typedef struct JSCode {
|
||||
objhdr_t hdr; // offset 0, always stone
|
||||
word_t arity; // max number of inputs
|
||||
word_t frame_size; // capacity of activation frame
|
||||
word_t closure_size; // reduced capacity for returned frames
|
||||
word_t entry_point; // address to begin execution
|
||||
word_t disruption_point;// address of disruption clause
|
||||
uint8_t bytecode[]; // actual bytecode
|
||||
} JSCode;
|
||||
```
|
||||
|
||||
### Phase 3: Complete gc_object_size for All Types
|
||||
|
||||
Update `gc_object_size` (line 1850) to read header at offset 0:
|
||||
|
||||
```c
|
||||
static size_t gc_object_size(void *ptr) {
|
||||
objhdr_t hdr = *(objhdr_t*)ptr; // Header at offset 0
|
||||
uint8_t type = objhdr_type(hdr);
|
||||
uint64_t cap = objhdr_cap56(hdr);
|
||||
|
||||
switch (type) {
|
||||
case OBJ_ARRAY:
|
||||
return sizeof(JSArray) + cap * sizeof(JSValue);
|
||||
case OBJ_BLOB:
|
||||
return sizeof(JSBlob) + (cap + 7) / 8; // cap is bits
|
||||
case OBJ_TEXT:
|
||||
return sizeof(JSText) + ((cap + 1) / 2) * sizeof(uint64_t);
|
||||
case OBJ_RECORD:
|
||||
return sizeof(JSRecord) + (cap + 1) * sizeof(slot); // cap is mask
|
||||
case OBJ_FUNCTION:
|
||||
return sizeof(JSFunction); // 3 words
|
||||
case OBJ_FRAME:
|
||||
return sizeof(JSFrame) + cap * sizeof(JSValue); // cap is slot count
|
||||
case OBJ_CODE:
|
||||
return 0; // Code is never copied (immutable)
|
||||
default:
|
||||
return 64; // Conservative fallback
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 4: Complete gc_scan_object for All Types
|
||||
|
||||
Update `gc_scan_object` (line 1924):
|
||||
|
||||
```c
|
||||
static void gc_scan_object(JSContext *ctx, void *ptr, uint8_t **to_free, uint8_t *to_end) {
|
||||
objhdr_t hdr = *(objhdr_t*)ptr;
|
||||
uint8_t type = objhdr_type(hdr);
|
||||
|
||||
switch (type) {
|
||||
case OBJ_ARRAY: {
|
||||
JSArray *arr = (JSArray*)ptr;
|
||||
for (uint32_t i = 0; i < arr->length; i++) {
|
||||
arr->values[i] = gc_copy_value(ctx, arr->values[i], to_free, to_end);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OBJ_RECORD: {
|
||||
JSRecord *rec = (JSRecord*)ptr;
|
||||
// Copy prototype
|
||||
if (rec->proto) {
|
||||
JSValue proto_val = JS_MKPTR(rec->proto);
|
||||
proto_val = gc_copy_value(ctx, proto_val, to_free, to_end);
|
||||
rec->proto = (JSRecord*)JS_VALUE_GET_PTR(proto_val);
|
||||
}
|
||||
// Copy table entries
|
||||
uint32_t mask = objhdr_cap56(rec->hdr);
|
||||
for (uint32_t i = 1; i <= mask; i++) { // Skip slot 0
|
||||
JSValue k = rec->slots[i].key;
|
||||
if (!rec_key_is_empty(k) && !rec_key_is_tomb(k)) {
|
||||
rec->slots[i].key = gc_copy_value(ctx, k, to_free, to_end);
|
||||
rec->slots[i].value = gc_copy_value(ctx, rec->slots[i].value, to_free, to_end);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OBJ_FUNCTION: {
|
||||
JSFunction *func = (JSFunction*)ptr;
|
||||
// Code is immutable, don't copy - but outer frame needs copying
|
||||
if (func->outer) {
|
||||
JSValue outer_val = JS_MKPTR(func->outer);
|
||||
outer_val = gc_copy_value(ctx, outer_val, to_free, to_end);
|
||||
func->outer = (JSFrame*)JS_VALUE_GET_PTR(outer_val);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OBJ_FRAME: {
|
||||
JSFrame *frame = (JSFrame*)ptr;
|
||||
// Copy function pointer
|
||||
if (frame->function) {
|
||||
JSValue func_val = JS_MKPTR(frame->function);
|
||||
func_val = gc_copy_value(ctx, func_val, to_free, to_end);
|
||||
frame->function = (JSFunction*)JS_VALUE_GET_PTR(func_val);
|
||||
}
|
||||
// Copy caller (unless NULL = reduced frame)
|
||||
if (frame->caller) {
|
||||
JSValue caller_val = JS_MKPTR(frame->caller);
|
||||
caller_val = gc_copy_value(ctx, caller_val, to_free, to_end);
|
||||
frame->caller = (JSFrame*)JS_VALUE_GET_PTR(caller_val);
|
||||
}
|
||||
// Copy all slots (args, closure vars, locals, temps)
|
||||
uint32_t slot_count = objhdr_cap56(frame->hdr);
|
||||
for (uint32_t i = 0; i < slot_count; i++) {
|
||||
frame->slots[i] = gc_copy_value(ctx, frame->slots[i], to_free, to_end);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OBJ_TEXT:
|
||||
case OBJ_BLOB:
|
||||
case OBJ_CODE:
|
||||
// No internal references to scan
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 5: Fix gc_copy_value Forwarding
|
||||
|
||||
Update `gc_copy_value` (line 1883) for offset 0 headers:
|
||||
|
||||
```c
|
||||
static JSValue gc_copy_value(JSContext *ctx, JSValue v, uint8_t **to_free, uint8_t *to_end) {
|
||||
if (!JS_IsPtr(v)) return v; // Immediate value
|
||||
|
||||
void *ptr = JS_VALUE_GET_PTR(v);
|
||||
|
||||
// Stone memory - don't copy (includes Code objects)
|
||||
objhdr_t hdr = *(objhdr_t*)ptr;
|
||||
if (objhdr_s(hdr)) return v;
|
||||
|
||||
// Check if in current heap
|
||||
if ((uint8_t*)ptr < ctx->heap_base || (uint8_t*)ptr >= ctx->heap_end)
|
||||
return v; // External allocation
|
||||
|
||||
// Already forwarded?
|
||||
if (objhdr_type(hdr) == OBJ_FORWARD) {
|
||||
void *new_ptr = (void*)(uintptr_t)objhdr_cap56(hdr);
|
||||
return JS_MKPTR(new_ptr);
|
||||
}
|
||||
|
||||
// Copy object to new space
|
||||
size_t size = gc_object_size(ptr);
|
||||
void *new_ptr = *to_free;
|
||||
*to_free += size;
|
||||
memcpy(new_ptr, ptr, size);
|
||||
|
||||
// Leave forwarding pointer in old location
|
||||
*(objhdr_t*)ptr = objhdr_make((uint64_t)(uintptr_t)new_ptr, OBJ_FORWARD, 0, 0, 0, 0);
|
||||
|
||||
return JS_MKPTR(new_ptr);
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 6: Complete GC Root Tracing
|
||||
|
||||
Update `ctx_gc` (line 1966) to trace all roots including JSGCRef:
|
||||
|
||||
```c
|
||||
static int ctx_gc(JSContext *ctx) {
|
||||
// ... existing setup code ...
|
||||
|
||||
// Copy roots: global object, class prototypes, etc. (existing)
|
||||
ctx->global_obj = gc_copy_value(ctx, ctx->global_obj, &to_free, to_end);
|
||||
ctx->global_var_obj = gc_copy_value(ctx, ctx->global_var_obj, &to_free, to_end);
|
||||
// ... other existing root copying ...
|
||||
|
||||
// Copy GC root stack (JS_PUSH_VALUE/JS_POP_VALUE)
|
||||
for (JSGCRef *ref = ctx->top_gc_ref; ref; ref = ref->prev) {
|
||||
ref->val = gc_copy_value(ctx, ref->val, &to_free, to_end);
|
||||
}
|
||||
|
||||
// Copy GC root list (JS_AddGCRef/JS_DeleteGCRef)
|
||||
for (JSGCRef *ref = ctx->last_gc_ref; ref; ref = ref->prev) {
|
||||
ref->val = gc_copy_value(ctx, ref->val, &to_free, to_end);
|
||||
}
|
||||
|
||||
// Copy current exception
|
||||
ctx->current_exception = gc_copy_value(ctx, ctx->current_exception, &to_free, to_end);
|
||||
|
||||
// Cheney scan (existing)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 7: Trigger GC on Allocation Failure
|
||||
|
||||
Update `js_malloc` (line 1495):
|
||||
|
||||
```c
|
||||
void *js_malloc(JSContext *ctx, size_t size) {
|
||||
size = (size + 7) & ~7; // Align to 8 bytes
|
||||
|
||||
if ((uint8_t*)ctx->heap_free + size > (uint8_t*)ctx->heap_end) {
|
||||
if (ctx_gc(ctx) < 0) {
|
||||
JS_ThrowOutOfMemory(ctx);
|
||||
return NULL;
|
||||
}
|
||||
// Retry after GC
|
||||
if ((uint8_t*)ctx->heap_free + size > (uint8_t*)ctx->heap_end) {
|
||||
JS_ThrowOutOfMemory(ctx);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void *ptr = ctx->heap_free;
|
||||
ctx->heap_free = (uint8_t*)ctx->heap_free + size;
|
||||
return ptr;
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 8: Frame Reduction (for closures)
|
||||
|
||||
When a function returns, "reduce" its frame to just closure variables:
|
||||
|
||||
```c
|
||||
static void reduce_frame(JSContext *ctx, JSFrame *frame) {
|
||||
if (frame->caller == NULL) return; // Already reduced
|
||||
|
||||
JSCode *code = frame->function->code;
|
||||
uint32_t closure_size = code->closure_size;
|
||||
|
||||
// Shrink capacity to just closure variables
|
||||
frame->hdr = objhdr_make(closure_size, OBJ_FRAME, 0, 0, 0, 0);
|
||||
frame->caller = NULL; // Signal: frame is reduced
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 9: Remove Unused Reference Counting Code
|
||||
|
||||
Delete:
|
||||
- `gc_decref`, `gc_decref_child` functions
|
||||
- `gc_scan_incref_child`, `gc_scan_incref_child2` functions
|
||||
- `JS_GCPhaseEnum`, `gc_phase` fields
|
||||
- `JSGCObjectHeader` struct (merge into objhdr_t)
|
||||
- `ref_count` fields from any remaining structs
|
||||
- `mark_function_children_decref` function
|
||||
- All `free_*` functions that rely on ref counting
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. **source/quickjs.c** - Main implementation:
|
||||
- Add DupValue/FreeValue no-op macros (~line 100)
|
||||
- Restructure JSArray, JSBlob, JSText, JSRecord (lines 468-499)
|
||||
- Simplify JSFunction to 3-word struct (line 1205)
|
||||
- Add JSFrame as heap object (new)
|
||||
- Restructure JSCode/JSFunctionBytecode (line 1293)
|
||||
- Fix gc_object_size (line 1850)
|
||||
- Fix gc_copy_value (line 1883)
|
||||
- Complete gc_scan_object (line 1924)
|
||||
- Update ctx_gc for all roots (line 1966)
|
||||
- Update js_malloc to trigger GC (line 1495)
|
||||
- Delete ref counting code throughout
|
||||
|
||||
2. **source/quickjs.h** - Public API:
|
||||
- Remove JSGCObjectHeader
|
||||
- Update JSValue type checks if needed
|
||||
- Ensure JS_IsStone works with offset 0 headers
|
||||
|
||||
## Execution Order
|
||||
|
||||
1. **First**: Add DupValue/FreeValue macros (enables compilation)
|
||||
2. **Second**: Standardize struct layouts (header at offset 0)
|
||||
3. **Third**: Fix gc_object_size and gc_copy_value
|
||||
4. **Fourth**: Complete gc_scan_object for all types
|
||||
5. **Fifth**: Update ctx_gc with complete root tracing
|
||||
6. **Sixth**: Wire js_malloc to trigger GC
|
||||
7. **Seventh**: Add frame reduction for closures
|
||||
8. **Finally**: Remove ref counting dead code
|
||||
|
||||
## Verification
|
||||
|
||||
1. **Compile test**: `make` should succeed without errors
|
||||
2. **Basic test**: Run simple scripts:
|
||||
```js
|
||||
var a = [1, 2, 3]
|
||||
log.console(a[1])
|
||||
```
|
||||
3. **Stress test**: Allocate many objects to trigger GC:
|
||||
```js
|
||||
for (var i = 0; i < 100000; i++) {
|
||||
var x = { value: i }
|
||||
}
|
||||
log.console("done")
|
||||
```
|
||||
4. **Closure test**: Test functions with closures survive GC:
|
||||
```js
|
||||
fn make_counter() {
|
||||
var count = 0
|
||||
fn inc() { count = count + 1; return count }
|
||||
return inc
|
||||
}
|
||||
var c = make_counter()
|
||||
log.console(c()) // 1
|
||||
log.console(c()) // 2
|
||||
```
|
||||
5. **GC stress with closures**: Create many closures, trigger GC, verify they still work
|
||||
|
||||
## Key Design Decisions (Resolved)
|
||||
|
||||
1. **JSCode storage**: Lives in stone (immutable) memory, never copied during GC ✓
|
||||
2. **Header offset**: Standardized to offset 0 for all heap objects ✓
|
||||
3. **Closure variables**: Live in JSFrame objects; frames are "reduced" when functions return ✓
|
||||
4. **JSVarRef**: Eliminated - closures reference their outer frame directly ✓
|
||||
@@ -1,19 +1,12 @@
|
||||
(function engine() {
|
||||
var _cell = globalThis.cell
|
||||
delete globalThis.cell
|
||||
var ACTORDATA = _cell.hidden.actorsym
|
||||
// Hidden vars (os, actorsym, init, core_path) come from env
|
||||
var ACTORDATA = actorsym
|
||||
var SYSYM = '__SYSTEM__'
|
||||
|
||||
var hidden = _cell.hidden
|
||||
|
||||
var os = hidden.os;
|
||||
|
||||
_cell.os = null
|
||||
var _cell = {}
|
||||
|
||||
var dylib_ext
|
||||
|
||||
_cell.id ??= "newguy"
|
||||
|
||||
switch(os.platform()) {
|
||||
case 'Windows': dylib_ext = '.dll'; break;
|
||||
case 'macOS': dylib_ext = '.dylib'; break;
|
||||
@@ -28,8 +21,7 @@ function use_embed(name) {
|
||||
return load_internal("js_" + name + "_use")
|
||||
}
|
||||
|
||||
globalThis.logical = function(val1)
|
||||
{
|
||||
function logical(val1) {
|
||||
if (val1 == 0 || val1 == false || val1 == "false" || val1 == null)
|
||||
return false;
|
||||
if (val1 == 1 || val1 == true || val1 == "true")
|
||||
@@ -37,19 +29,19 @@ globalThis.logical = function(val1)
|
||||
return null;
|
||||
}
|
||||
|
||||
globalThis.some = function(arr, pred) {
|
||||
function some(arr, pred) {
|
||||
return find(arr, pred) != null
|
||||
}
|
||||
|
||||
globalThis.every = function(arr, pred) {
|
||||
function every(arr, pred) {
|
||||
return find(arr, x => not(pred(x))) == null
|
||||
}
|
||||
|
||||
globalThis.starts_with = function(str, prefix) {
|
||||
function starts_with(str, prefix) {
|
||||
return search(str, prefix) == 0
|
||||
}
|
||||
|
||||
globalThis.ends_with = function(str, suffix) {
|
||||
function ends_with(str, suffix) {
|
||||
return search(str, suffix, -length(suffix)) != null
|
||||
}
|
||||
|
||||
@@ -99,8 +91,7 @@ function use_core(path) {
|
||||
|
||||
var blob = use_core('blob')
|
||||
|
||||
globalThis.actor = function()
|
||||
{
|
||||
function actor() {
|
||||
|
||||
}
|
||||
|
||||
@@ -108,7 +99,7 @@ var actor_mod = use_core('actor')
|
||||
var wota = use_core('wota')
|
||||
var nota = use_core('nota')
|
||||
|
||||
globalThis.is_actor = function(value) {
|
||||
function is_actor(value) {
|
||||
return is_object(value) && value[ACTORDATA]
|
||||
}
|
||||
|
||||
@@ -138,7 +129,7 @@ function console_rec(line, file, msg) {
|
||||
// time: [${time.text("mb d yyyy h:nn:ss")}]
|
||||
}
|
||||
|
||||
globalThis.log = function(name, args) {
|
||||
function log(name, args) {
|
||||
var caller = caller_data(1)
|
||||
var msg = args[0]
|
||||
|
||||
@@ -201,9 +192,8 @@ function disrupt(err)
|
||||
|
||||
actor_mod.on_exception(disrupt)
|
||||
|
||||
_cell.args = _cell.hidden.init
|
||||
_cell.args ??= {}
|
||||
_cell.id ??= "newguy"
|
||||
_cell.args = init ?? {}
|
||||
_cell.id = "newguy"
|
||||
|
||||
function create_actor(desc = {id:guid()}) {
|
||||
var actor = {}
|
||||
@@ -224,10 +214,30 @@ var json = use_core('json')
|
||||
var time = use_core('time')
|
||||
|
||||
var pronto = use_core('pronto')
|
||||
globalThis.fallback = pronto.fallback
|
||||
globalThis.parallel = pronto.parallel
|
||||
globalThis.race = pronto.race
|
||||
globalThis.sequence = pronto.sequence
|
||||
var fallback = pronto.fallback
|
||||
var parallel = pronto.parallel
|
||||
var race = pronto.race
|
||||
var sequence = pronto.sequence
|
||||
|
||||
// Create runtime environment for modules
|
||||
var runtime_env = {
|
||||
logical: logical,
|
||||
some: some,
|
||||
every: every,
|
||||
starts_with: starts_with,
|
||||
ends_with: ends_with,
|
||||
actor: actor,
|
||||
is_actor: is_actor,
|
||||
log: log,
|
||||
send: send,
|
||||
fallback: fallback,
|
||||
parallel: parallel,
|
||||
race: race,
|
||||
sequence: sequence
|
||||
}
|
||||
|
||||
// Pass to os for shop to access
|
||||
os.runtime_env = runtime_env
|
||||
|
||||
$_.time_limit = function(requestor, seconds)
|
||||
{
|
||||
@@ -597,13 +607,13 @@ var need_stop = false
|
||||
|
||||
var replies = {}
|
||||
|
||||
globalThis.send = function send(actor, message, reply) {
|
||||
function send(actor, message, reply) {
|
||||
if (!is_object(actor))
|
||||
throw Error(`Must send to an actor object. Provided: ${actor}`);
|
||||
|
||||
if (!is_object(message))
|
||||
throw Error('Message must be an object')
|
||||
var send = {type:"user", data: message}
|
||||
var send_msg = {type:"user", data: message}
|
||||
|
||||
if (actor[HEADER] && actor[HEADER].replycc) {
|
||||
var header = actor[HEADER]
|
||||
@@ -611,7 +621,7 @@ globalThis.send = function send(actor, message, reply) {
|
||||
throw Error(`Supplied actor had a return, but it's not a valid actor! ${actor[HEADER]}`)
|
||||
|
||||
actor = header.replycc
|
||||
send.return = header.reply
|
||||
send_msg.return = header.reply
|
||||
}
|
||||
|
||||
if (reply) {
|
||||
@@ -623,12 +633,12 @@ globalThis.send = function send(actor, message, reply) {
|
||||
delete replies[id]
|
||||
}
|
||||
}, REPLYTIMEOUT)
|
||||
send.reply = id
|
||||
send.replycc = $_.self
|
||||
send_msg.reply = id
|
||||
send_msg.replycc = $_.self
|
||||
}
|
||||
|
||||
// Instead of sending immediately, queue it
|
||||
actor_prep(actor,send);
|
||||
actor_prep(actor, send_msg);
|
||||
}
|
||||
|
||||
stone(send)
|
||||
@@ -647,7 +657,6 @@ function turn(msg)
|
||||
}
|
||||
|
||||
//log.console(`FIXME: need to get main from config, not just set to true`)
|
||||
//log.console(`FIXME: add freeze/unfreeze at this level, so we can do it (but scripts cannot)`)
|
||||
actor_mod.register_actor(_cell.id, turn, true, config.ar_timer)
|
||||
|
||||
if (config.actor_memory)
|
||||
@@ -786,8 +795,6 @@ if (!locator) {
|
||||
if (!locator)
|
||||
throw Error(`Main program ${_cell.args.program} could not be found`)
|
||||
|
||||
stone(globalThis)
|
||||
|
||||
$_.clock(_ => {
|
||||
// Get capabilities for the main program
|
||||
var file_info = shop.file_info ? shop.file_info(locator.path) : null
|
||||
|
||||
386
internal/nota.c
386
internal/nota.c
@@ -1,386 +0,0 @@
|
||||
#include "cell.h"
|
||||
#include "cell_internal.h"
|
||||
|
||||
#define NOTA_IMPLEMENTATION
|
||||
#include "nota.h"
|
||||
|
||||
typedef struct NotaEncodeContext {
|
||||
JSContext *ctx;
|
||||
JSValue visitedStack;
|
||||
NotaBuffer nb;
|
||||
int cycle;
|
||||
JSValue replacer;
|
||||
} NotaEncodeContext;
|
||||
|
||||
static void nota_stack_push(NotaEncodeContext *enc, JSValueConst val)
|
||||
{
|
||||
JSContext *ctx = enc->ctx;
|
||||
int len = JS_ArrayLength(ctx, enc->visitedStack);
|
||||
JS_SetPropertyInt64(ctx, enc->visitedStack, len, JS_DupValue(ctx, val));
|
||||
}
|
||||
|
||||
static void nota_stack_pop(NotaEncodeContext *enc)
|
||||
{
|
||||
JSContext *ctx = enc->ctx;
|
||||
int len = JS_ArrayLength(ctx, enc->visitedStack);
|
||||
JS_SetPropertyStr(ctx, enc->visitedStack, "length", JS_NewUint32(ctx, len - 1));
|
||||
}
|
||||
|
||||
static int nota_stack_has(NotaEncodeContext *enc, JSValueConst val)
|
||||
{
|
||||
JSContext *ctx = enc->ctx;
|
||||
int len = JS_ArrayLength(ctx, enc->visitedStack);
|
||||
for (int i = 0; i < len; i++) {
|
||||
JSValue elem = JS_GetPropertyUint32(ctx, enc->visitedStack, i);
|
||||
if (JS_IsObject(elem) && JS_IsObject(val)) {
|
||||
if (JS_StrictEq(ctx, elem, val)) {
|
||||
JS_FreeValue(ctx, elem);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
JS_FreeValue(ctx, elem);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static JSValue apply_replacer(NotaEncodeContext *enc, JSValueConst holder, JSValueConst key, JSValueConst val) {
|
||||
if (JS_IsNull(enc->replacer)) return JS_DupValue(enc->ctx, val);
|
||||
|
||||
JSValue args[2] = { JS_DupValue(enc->ctx, key), JS_DupValue(enc->ctx, val) };
|
||||
JSValue result = JS_Call(enc->ctx, enc->replacer, holder, 2, args);
|
||||
JS_FreeValue(enc->ctx, args[0]);
|
||||
JS_FreeValue(enc->ctx, args[1]);
|
||||
|
||||
if (JS_IsException(result)) return JS_DupValue(enc->ctx, val);
|
||||
return result;
|
||||
}
|
||||
|
||||
char *js_do_nota_decode(JSContext *js, JSValue *tmp, char *nota, JSValue holder, JSValue key, JSValue reviver) {
|
||||
int type = nota_type(nota);
|
||||
JSValue ret2;
|
||||
long long n;
|
||||
double d;
|
||||
int b;
|
||||
char *str;
|
||||
uint8_t *blob;
|
||||
|
||||
switch(type) {
|
||||
case NOTA_BLOB:
|
||||
nota = nota_read_blob(&n, (char**)&blob, nota);
|
||||
*tmp = js_new_blob_stoned_copy(js, blob, n);
|
||||
free(blob);
|
||||
break;
|
||||
case NOTA_TEXT:
|
||||
nota = nota_read_text(&str, nota);
|
||||
*tmp = JS_NewString(js, str);
|
||||
free(str);
|
||||
break;
|
||||
case NOTA_ARR:
|
||||
nota = nota_read_array(&n, nota);
|
||||
*tmp = JS_NewArray(js);
|
||||
for (int i = 0; i < n; i++) {
|
||||
nota = js_do_nota_decode(js, &ret2, nota, *tmp, JS_NewInt32(js, i), reviver);
|
||||
JS_SetPropertyInt64(js, *tmp, i, ret2);
|
||||
}
|
||||
break;
|
||||
case NOTA_REC:
|
||||
nota = nota_read_record(&n, nota);
|
||||
*tmp = JS_NewObject(js);
|
||||
for (int i = 0; i < n; i++) {
|
||||
nota = nota_read_text(&str, nota);
|
||||
JSValue prop_key = JS_NewString(js, str);
|
||||
nota = js_do_nota_decode(js, &ret2, nota, *tmp, prop_key, reviver);
|
||||
JS_SetPropertyStr(js, *tmp, str, ret2);
|
||||
JS_FreeValue(js, prop_key);
|
||||
free(str);
|
||||
}
|
||||
break;
|
||||
case NOTA_INT:
|
||||
nota = nota_read_int(&n, nota);
|
||||
*tmp = JS_NewInt64(js, n);
|
||||
break;
|
||||
case NOTA_SYM:
|
||||
nota = nota_read_sym(&b, nota);
|
||||
if (b == NOTA_PRIVATE) {
|
||||
JSValue inner;
|
||||
nota = js_do_nota_decode(js, &inner, nota, holder, JS_NULL, reviver);
|
||||
JSValue obj = JS_NewObject(js);
|
||||
cell_rt *crt = JS_GetContextOpaque(js);
|
||||
// JS_SetProperty(js, obj, crt->actor_sym, inner);
|
||||
*tmp = obj;
|
||||
} else {
|
||||
switch(b) {
|
||||
case NOTA_NULL: *tmp = JS_NULL; break;
|
||||
case NOTA_FALSE: *tmp = JS_NewBool(js, 0); break;
|
||||
case NOTA_TRUE: *tmp = JS_NewBool(js, 1); break;
|
||||
default: *tmp = JS_NULL; break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
case NOTA_FLOAT:
|
||||
nota = nota_read_float(&d, nota);
|
||||
*tmp = JS_NewFloat64(js, d);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!JS_IsNull(reviver)) {
|
||||
JSValue args[2] = { JS_DupValue(js, key), JS_DupValue(js, *tmp) };
|
||||
JSValue revived = JS_Call(js, reviver, holder, 2, args);
|
||||
JS_FreeValue(js, args[0]);
|
||||
JS_FreeValue(js, args[1]);
|
||||
if (!JS_IsException(revived)) {
|
||||
JS_FreeValue(js, *tmp);
|
||||
*tmp = revived;
|
||||
} else {
|
||||
JS_FreeValue(js, revived);
|
||||
}
|
||||
}
|
||||
|
||||
return nota;
|
||||
}
|
||||
|
||||
static void nota_encode_value(NotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValueConst key) {
|
||||
JSContext *ctx = enc->ctx;
|
||||
JSValue replaced = apply_replacer(enc, holder, key, val);
|
||||
int tag = JS_VALUE_GET_TAG(replaced);
|
||||
|
||||
switch (tag) {
|
||||
case JS_TAG_INT:
|
||||
case JS_TAG_FLOAT64: {
|
||||
double d;
|
||||
JS_ToFloat64(ctx, &d, replaced);
|
||||
nota_write_number(&enc->nb, d);
|
||||
break;
|
||||
}
|
||||
case JS_TAG_STRING: {
|
||||
const char *str = JS_ToCString(ctx, replaced);
|
||||
nota_write_text(&enc->nb, str);
|
||||
JS_FreeCString(ctx, str);
|
||||
break;
|
||||
}
|
||||
case JS_TAG_BOOL:
|
||||
if (JS_VALUE_GET_BOOL(replaced)) nota_write_sym(&enc->nb, NOTA_TRUE);
|
||||
else nota_write_sym(&enc->nb, NOTA_FALSE);
|
||||
break;
|
||||
case JS_TAG_NULL:
|
||||
nota_write_sym(&enc->nb, NOTA_NULL);
|
||||
break;
|
||||
case JS_TAG_OBJECT: {
|
||||
if (js_is_blob(ctx, replaced)) {
|
||||
size_t buf_len;
|
||||
void *buf_data = js_get_blob_data(ctx, &buf_len, replaced);
|
||||
if (buf_data == -1) {
|
||||
JS_FreeValue(ctx, replaced);
|
||||
return; // JS_EXCEPTION will be handled by caller
|
||||
}
|
||||
nota_write_blob(&enc->nb, (unsigned long long)buf_len * 8, (const char*)buf_data);
|
||||
break;
|
||||
}
|
||||
|
||||
if (JS_IsArray(ctx, replaced)) {
|
||||
if (nota_stack_has(enc, replaced)) {
|
||||
enc->cycle = 1;
|
||||
break;
|
||||
}
|
||||
nota_stack_push(enc, replaced);
|
||||
int arr_len = JS_ArrayLength(ctx, replaced);
|
||||
nota_write_array(&enc->nb, arr_len);
|
||||
for (int i = 0; i < arr_len; i++) {
|
||||
JSValue elem_val = JS_GetPropertyUint32(ctx, replaced, i);
|
||||
JSValue elem_key = JS_NewInt32(ctx, i);
|
||||
nota_encode_value(enc, elem_val, replaced, elem_key);
|
||||
JS_FreeValue(ctx, elem_val);
|
||||
JS_FreeValue(ctx, elem_key);
|
||||
}
|
||||
nota_stack_pop(enc);
|
||||
break;
|
||||
}
|
||||
|
||||
cell_rt *crt = JS_GetContextOpaque(ctx);
|
||||
// JSValue adata = JS_GetProperty(ctx, replaced, crt->actor_sym);
|
||||
JSValue adata = JS_NULL;
|
||||
if (!JS_IsNull(adata)) {
|
||||
nota_write_sym(&enc->nb, NOTA_PRIVATE);
|
||||
nota_encode_value(enc, adata, replaced, JS_NULL);
|
||||
JS_FreeValue(ctx, adata);
|
||||
break;
|
||||
}
|
||||
JS_FreeValue(ctx, adata);
|
||||
if (nota_stack_has(enc, replaced)) {
|
||||
enc->cycle = 1;
|
||||
break;
|
||||
}
|
||||
nota_stack_push(enc, replaced);
|
||||
|
||||
JSValue to_json = JS_GetPropertyStr(ctx, replaced, "toJSON");
|
||||
if (JS_IsFunction(to_json)) {
|
||||
JSValue result = JS_Call(ctx, to_json, replaced, 0, NULL);
|
||||
JS_FreeValue(ctx, to_json);
|
||||
if (!JS_IsException(result)) {
|
||||
nota_encode_value(enc, result, holder, key);
|
||||
JS_FreeValue(ctx, result);
|
||||
} else {
|
||||
nota_write_sym(&enc->nb, NOTA_NULL);
|
||||
}
|
||||
nota_stack_pop(enc);
|
||||
break;
|
||||
}
|
||||
JS_FreeValue(ctx, to_json);
|
||||
|
||||
JSPropertyEnum *ptab;
|
||||
uint32_t plen;
|
||||
if (JS_GetOwnPropertyNames(ctx, &ptab, &plen, replaced, JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK) < 0) {
|
||||
nota_write_sym(&enc->nb, NOTA_NULL);
|
||||
nota_stack_pop(enc);
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t non_function_count = 0;
|
||||
for (uint32_t i = 0; i < plen; i++) {
|
||||
JSValue prop_val = JS_GetProperty(ctx, replaced, ptab[i].atom);
|
||||
if (!JS_IsFunction(prop_val)) non_function_count++;
|
||||
JS_FreeValue(ctx, prop_val);
|
||||
}
|
||||
|
||||
nota_write_record(&enc->nb, non_function_count);
|
||||
for (uint32_t i = 0; i < plen; i++) {
|
||||
JSValue prop_val = JS_GetProperty(ctx, replaced, ptab[i].atom);
|
||||
if (!JS_IsFunction(prop_val)) {
|
||||
const char *prop_name = JS_AtomToCString(ctx, ptab[i].atom);
|
||||
JSValue prop_key = JS_AtomToValue(ctx, ptab[i].atom);
|
||||
nota_write_text(&enc->nb, prop_name);
|
||||
nota_encode_value(enc, prop_val, replaced, prop_key);
|
||||
JS_FreeCString(ctx, prop_name);
|
||||
JS_FreeValue(ctx, prop_key);
|
||||
}
|
||||
JS_FreeValue(ctx, prop_val);
|
||||
JS_FreeAtom(ctx, ptab[i].atom);
|
||||
}
|
||||
js_free(ctx, ptab);
|
||||
nota_stack_pop(enc);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
nota_write_sym(&enc->nb, NOTA_NULL);
|
||||
break;
|
||||
}
|
||||
JS_FreeValue(ctx, replaced);
|
||||
}
|
||||
|
||||
void *value2nota(JSContext *ctx, JSValue v) {
|
||||
NotaEncodeContext enc_s, *enc = &enc_s;
|
||||
enc->ctx = ctx;
|
||||
enc->visitedStack = JS_NewArray(ctx);
|
||||
enc->cycle = 0;
|
||||
enc->replacer = JS_NULL;
|
||||
|
||||
nota_buffer_init(&enc->nb, 128);
|
||||
nota_encode_value(enc, v, JS_NULL, JS_NewString(ctx, ""));
|
||||
|
||||
if (enc->cycle) {
|
||||
JS_FreeValue(ctx, enc->visitedStack);
|
||||
nota_buffer_free(&enc->nb);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
JS_FreeValue(ctx, enc->visitedStack);
|
||||
void *data_ptr = enc->nb.data;
|
||||
enc->nb.data = NULL;
|
||||
nota_buffer_free(&enc->nb);
|
||||
return data_ptr;
|
||||
}
|
||||
|
||||
JSValue nota2value(JSContext *js, void *nota) {
|
||||
if (!nota) return JS_NULL;
|
||||
JSValue ret;
|
||||
JSValue holder = JS_NewObject(js);
|
||||
js_do_nota_decode(js, &ret, nota, holder, JS_NewString(js, ""), JS_NULL);
|
||||
JS_FreeValue(js, holder);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static JSValue js_nota_tostring(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
|
||||
size_t len;
|
||||
void *nota = js_get_blob_data(ctx, &len, this_val);
|
||||
if (nota == (void*)-1) return JS_EXCEPTION;
|
||||
if (!nota) return JS_NULL;
|
||||
|
||||
JSValue decoded;
|
||||
JSValue holder = JS_NewObject(ctx);
|
||||
js_do_nota_decode(ctx, &decoded, (char*)nota, holder, JS_NewString(ctx, ""), JS_NULL);
|
||||
JS_FreeValue(ctx, holder);
|
||||
|
||||
JSValue global = JS_GetGlobalObject(ctx);
|
||||
JSValue json = JS_GetPropertyStr(ctx, global, "JSON");
|
||||
JSValue stringify = JS_GetPropertyStr(ctx, json, "stringify");
|
||||
|
||||
JSValue args[3];
|
||||
args[0] = decoded;
|
||||
args[1] = JS_NULL;
|
||||
args[2] = JS_NewInt32(ctx, 1);
|
||||
|
||||
JSValue result = JS_Call(ctx, stringify, json, 3, args);
|
||||
|
||||
JS_FreeValue(ctx, stringify);
|
||||
JS_FreeValue(ctx, json);
|
||||
JS_FreeValue(ctx, global);
|
||||
JS_FreeValue(ctx, decoded);
|
||||
JS_FreeValue(ctx, args[2]);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static JSValue js_nota_encode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
|
||||
if (argc < 1) return JS_ThrowTypeError(ctx, "nota.encode requires at least 1 argument");
|
||||
|
||||
NotaEncodeContext enc_s, *enc = &enc_s;
|
||||
enc->ctx = ctx;
|
||||
enc->visitedStack = JS_NewArray(ctx);
|
||||
enc->cycle = 0;
|
||||
enc->replacer = (argc > 1 && JS_IsFunction(argv[1])) ? argv[1] : JS_NULL;
|
||||
|
||||
nota_buffer_init(&enc->nb, 128);
|
||||
nota_encode_value(enc, argv[0], JS_NULL, JS_NewString(ctx, ""));
|
||||
|
||||
if (enc->cycle) {
|
||||
JS_FreeValue(ctx, enc->visitedStack);
|
||||
nota_buffer_free(&enc->nb);
|
||||
return JS_ThrowReferenceError(ctx, "Tried to encode something to nota with a cycle.");
|
||||
}
|
||||
|
||||
JS_FreeValue(ctx, enc->visitedStack);
|
||||
size_t total_len = enc->nb.size;
|
||||
void *data_ptr = enc->nb.data;
|
||||
JSValue ret = js_new_blob_stoned_copy(ctx, (uint8_t*)data_ptr, total_len);
|
||||
|
||||
nota_buffer_free(&enc->nb);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static JSValue js_nota_decode(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
|
||||
if (argc < 1) return JS_NULL;
|
||||
|
||||
size_t len;
|
||||
unsigned char *nota = js_get_blob_data(js, &len, argv[0]);
|
||||
if (nota == -1) return JS_EXCEPTION;
|
||||
if (!nota) return JS_NULL;
|
||||
|
||||
JSValue reviver = (argc > 1 && JS_IsFunction(argv[1])) ? argv[1] : JS_NULL;
|
||||
JSValue ret;
|
||||
JSValue holder = JS_NewObject(js);
|
||||
js_do_nota_decode(js, &ret, (char*)nota, holder, JS_NewString(js, ""), reviver);
|
||||
JS_FreeValue(js, holder);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const JSCFunctionListEntry js_nota_funcs[] = {
|
||||
JS_CFUNC_DEF("encode", 1, js_nota_encode),
|
||||
JS_CFUNC_DEF("decode", 1, js_nota_decode),
|
||||
};
|
||||
|
||||
JSValue js_nota_use(JSContext *js) {
|
||||
JSValue export = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, export, js_nota_funcs, sizeof(js_nota_funcs)/sizeof(JSCFunctionListEntry));
|
||||
return export;
|
||||
}
|
||||
@@ -379,7 +379,16 @@ Shop.get_script_capabilities = function(path) {
|
||||
}
|
||||
|
||||
function inject_env(inject) {
|
||||
// Start with runtime functions from engine
|
||||
var env = {}
|
||||
var rt = my$_.os ? my$_.os.runtime_env : null
|
||||
if (rt) {
|
||||
for (var k in rt) {
|
||||
env[k] = rt[k]
|
||||
}
|
||||
}
|
||||
|
||||
// Add capability injections
|
||||
for (var i = 0; i < length(inject); i++) {
|
||||
var inj = inject[i]
|
||||
var key = trim(inj, '$')
|
||||
@@ -391,6 +400,17 @@ function inject_env(inject) {
|
||||
|
||||
function inject_bindings_code(inject) {
|
||||
var lines = []
|
||||
|
||||
// Runtime function bindings
|
||||
var runtime_fns = ['logical', 'some', 'every', 'starts_with', 'ends_with',
|
||||
'actor', 'is_actor', 'log', 'send',
|
||||
'fallback', 'parallel', 'race', 'sequence']
|
||||
for (var i = 0; i < length(runtime_fns); i++) {
|
||||
var fn = runtime_fns[i]
|
||||
push(lines, `var ${fn} = env["${fn}"];`)
|
||||
}
|
||||
|
||||
// Capability bindings ($delay, $start, etc.)
|
||||
for (var i = 0; i < length(inject); i++) {
|
||||
var inj = inject[i]
|
||||
var key = trim(inj, '$')
|
||||
@@ -432,18 +452,17 @@ function resolve_mod_fn(path, pkg) {
|
||||
var obj = pull_from_cache(stone(blob(script)))
|
||||
if (obj) {
|
||||
var fn = js.compile_unblob(obj)
|
||||
return js.eval_compile(fn)
|
||||
return js.integrate(fn, null)
|
||||
}
|
||||
|
||||
// Compile name is just for debug/stack traces
|
||||
// var compile_name = pkg ? pkg + ':' + path : 'local:' + path
|
||||
var compile_name = path
|
||||
|
||||
var fn = js.compile(compile_name, script)
|
||||
|
||||
put_into_cache(stone(blob(script)), js.compile_blob(fn))
|
||||
|
||||
return js.eval_compile(fn)
|
||||
return js.integrate(fn, null)
|
||||
}
|
||||
|
||||
// given a path and a package context
|
||||
|
||||
@@ -40,9 +40,9 @@ sources = []
|
||||
src += [ # core
|
||||
'monocypher.c',
|
||||
'cell.c',
|
||||
'suite.c',
|
||||
'wildmatch.c',
|
||||
'qjs_actor.c',
|
||||
'qjs_wota.c',
|
||||
'miniz.c',
|
||||
'quickjs.c',
|
||||
'libregexp.c', 'libunicode.c', 'cutils.c', 'dtoa.c'
|
||||
@@ -51,7 +51,6 @@ src += [ # core
|
||||
src += ['scheduler.c']
|
||||
|
||||
scripts = [
|
||||
'internal/nota.c',
|
||||
'debug/js.c',
|
||||
'qop.c',
|
||||
'wildstar.c',
|
||||
@@ -59,7 +58,6 @@ scripts = [
|
||||
'crypto.c',
|
||||
'internal/kim.c',
|
||||
'time.c',
|
||||
'internal/nota.c',
|
||||
'debug/debug.c',
|
||||
'internal/os.c',
|
||||
'fd.c',
|
||||
|
||||
@@ -70,7 +70,7 @@ static JSValue js_enet_host_create(JSContext *ctx, JSValueConst this_val, int ar
|
||||
|
||||
JSValue config_obj = argv[0];
|
||||
JSValue addr_val = JS_GetPropertyStr(ctx, config_obj, "address");
|
||||
const char *addr_str = JS_IsString(addr_val) ? JS_ToCString(ctx, addr_val) : NULL;
|
||||
const char *addr_str = JS_IsText(addr_val) ? JS_ToCString(ctx, addr_val) : NULL;
|
||||
JS_FreeValue(ctx, addr_val);
|
||||
|
||||
if (!addr_str)
|
||||
@@ -245,7 +245,7 @@ static JSValue js_enet_host_broadcast(JSContext *ctx, JSValueConst this_val, int
|
||||
size_t data_len = 0;
|
||||
uint8_t *buf = NULL;
|
||||
|
||||
if (JS_IsString(argv[0])) {
|
||||
if (JS_IsText(argv[0])) {
|
||||
data_str = JS_ToCStringLen(ctx, &data_len, argv[0]);
|
||||
if (!data_str) return JS_EXCEPTION;
|
||||
} else if (js_is_blob(ctx,argv[0])) {
|
||||
@@ -310,7 +310,7 @@ static JSValue js_enet_peer_send(JSContext *ctx, JSValueConst this_val, int argc
|
||||
size_t data_len = 0;
|
||||
uint8_t *buf = NULL;
|
||||
|
||||
if (JS_IsString(argv[0])) {
|
||||
if (JS_IsText(argv[0])) {
|
||||
data_str = JS_ToCStringLen(ctx, &data_len, argv[0]);
|
||||
if (!data_str) return JS_EXCEPTION;
|
||||
} else if (js_is_blob(ctx,argv[0])) {
|
||||
|
||||
@@ -293,7 +293,7 @@ static int par_easycurl_to_memory(char const *url, par_byte **data, int *nbytes)
|
||||
// Performs a blocking HTTP GET and returns a QuickJS Blob of the body
|
||||
static JSValue js_fetch_picoparser(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv) {
|
||||
if (argc < 1 || !JS_IsString(argv[0]))
|
||||
if (argc < 1 || !JS_IsText(argv[0]))
|
||||
return JS_ThrowTypeError(ctx, "fetch: URL string required");
|
||||
|
||||
const char *url = JS_ToCString(ctx, argv[0]);
|
||||
|
||||
@@ -135,7 +135,7 @@ static int parse_url(const char *url, char **host, int *port, char **path, int *
|
||||
// Performs a blocking HTTP GET and returns a QuickJS Blob of the body
|
||||
static JSValue js_fetch_playdate(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv) {
|
||||
if (argc < 1 || !JS_IsString(argv[0]))
|
||||
if (argc < 1 || !JS_IsText(argv[0]))
|
||||
return JS_ThrowTypeError(ctx, "fetch: URL string required");
|
||||
|
||||
if (!pd_network || !pd_network->http) {
|
||||
|
||||
12
net/socket.c
12
net/socket.c
@@ -144,7 +144,7 @@ JSC_CCALL(socket_socket,
|
||||
int protocol = 0;
|
||||
|
||||
// Parse domain
|
||||
if (JS_IsString(argv[0])) {
|
||||
if (JS_IsText(argv[0])) {
|
||||
const char *domain_str = JS_ToCString(js, argv[0]);
|
||||
if (strcmp(domain_str, "AF_INET") == 0) domain = AF_INET;
|
||||
else if (strcmp(domain_str, "AF_INET6") == 0) domain = AF_INET6;
|
||||
@@ -156,7 +156,7 @@ JSC_CCALL(socket_socket,
|
||||
|
||||
// Parse type
|
||||
if (argc > 1) {
|
||||
if (JS_IsString(argv[1])) {
|
||||
if (JS_IsText(argv[1])) {
|
||||
const char *type_str = JS_ToCString(js, argv[1]);
|
||||
if (strcmp(type_str, "SOCK_STREAM") == 0) type = SOCK_STREAM;
|
||||
else if (strcmp(type_str, "SOCK_DGRAM") == 0) type = SOCK_DGRAM;
|
||||
@@ -297,7 +297,7 @@ JSC_CCALL(socket_send,
|
||||
flags = js2number(js, argv[2]);
|
||||
}
|
||||
|
||||
if (JS_IsString(argv[1])) {
|
||||
if (JS_IsText(argv[1])) {
|
||||
const char *data = JS_ToCStringLen(js, &len, argv[1]);
|
||||
sent = send(sockfd, data, len, flags);
|
||||
JS_FreeCString(js, data);
|
||||
@@ -385,7 +385,7 @@ JSC_CCALL(socket_sendto,
|
||||
to_len = sizeof(addr);
|
||||
}
|
||||
|
||||
if (JS_IsString(argv[1])) {
|
||||
if (JS_IsText(argv[1])) {
|
||||
const char *data = JS_ToCStringLen(js, &len, argv[1]);
|
||||
sent = sendto(sockfd, data, len, flags, to_addr, to_len);
|
||||
JS_FreeCString(js, data);
|
||||
@@ -525,7 +525,7 @@ JSC_CCALL(socket_setsockopt,
|
||||
int optname = 0;
|
||||
|
||||
// Parse level
|
||||
if (JS_IsString(argv[1])) {
|
||||
if (JS_IsText(argv[1])) {
|
||||
const char *level_str = JS_ToCString(js, argv[1]);
|
||||
if (strcmp(level_str, "SOL_SOCKET") == 0) level = SOL_SOCKET;
|
||||
else if (strcmp(level_str, "IPPROTO_TCP") == 0) level = IPPROTO_TCP;
|
||||
@@ -537,7 +537,7 @@ JSC_CCALL(socket_setsockopt,
|
||||
}
|
||||
|
||||
// Parse option name
|
||||
if (JS_IsString(argv[2])) {
|
||||
if (JS_IsText(argv[2])) {
|
||||
const char *opt_str = JS_ToCString(js, argv[2]);
|
||||
if (strcmp(opt_str, "SO_REUSEADDR") == 0) optname = SO_REUSEADDR;
|
||||
else if (strcmp(opt_str, "SO_KEEPALIVE") == 0) optname = SO_KEEPALIVE;
|
||||
|
||||
338
plan.md
Normal file
338
plan.md
Normal file
@@ -0,0 +1,338 @@
|
||||
# Cell/QuickJS Refactoring Plan: Remove Atoms, Shapes, and Dual-Encoding
|
||||
|
||||
## Overview
|
||||
|
||||
Refactor `source/quickjs.c` to match `docs/memory.md` specification:
|
||||
- Remove JSAtom system (171 references → ~41 remaining)
|
||||
- Remove JSShape system (94 references) ✓
|
||||
- Remove IC caches (shape-based inline caches) ✓
|
||||
- Remove `is_wide_char` dual-encoding (18 locations) ✓
|
||||
- Use JSValue texts directly as property keys
|
||||
- Reference: `mquickjs.c` shows the target pattern
|
||||
|
||||
## Completed Phases
|
||||
|
||||
### Phase 1: Remove is_wide_char Remnants ✓
|
||||
### Phase 2: Remove IC Caches ✓
|
||||
### Phase 3: Remove JSShape System ✓
|
||||
### Phase 4: Complete Property Access with JSValue Keys ✓
|
||||
|
||||
Completed:
|
||||
- Removed JS_GC_OBJ_TYPE_JS_OBJECT fallbacks from OP_get_field
|
||||
- Removed JS_GC_OBJ_TYPE_JS_OBJECT fallbacks from OP_put_field
|
||||
- Removed JS_GC_OBJ_TYPE_JS_OBJECT fallbacks from OP_define_field
|
||||
- Created emit_key() function that adds JSValue to cpool and emits index
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Convert JSAtom to JSValue Text (IN PROGRESS)
|
||||
|
||||
This is the core transformation. All identifier handling moves from atoms to JSValue.
|
||||
|
||||
### Completed Items
|
||||
|
||||
**Token and Parser Infrastructure:**
|
||||
- [x] Change JSToken.u.ident.atom to JSToken.u.ident.str (JSValue)
|
||||
- [x] Change parse_ident() to return JSValue
|
||||
- [x] Create emit_key() function (cpool-based)
|
||||
- [x] Create JS_KEY_* macros for common names (lines ~279-335 in quickjs.c)
|
||||
- [x] Update all token.u.ident.atom references to .str
|
||||
- [x] Create keyword lookup table (js_keywords[]) with string comparison
|
||||
- [x] Rewrite update_token_ident() to use js_keyword_lookup()
|
||||
- [x] Rewrite is_strict_future_keyword() to use JSValue
|
||||
- [x] Update token_is_pseudo_keyword() to use JSValue and js_key_equal()
|
||||
|
||||
**Function Declaration Parsing:**
|
||||
- [x] Update js_parse_function_decl() signature to use JSValue func_name
|
||||
- [x] Update js_parse_function_decl2() to use JSValue func_name throughout
|
||||
- [x] Update js_parse_function_check_names() to use JSValue
|
||||
- [x] Convert JS_DupAtom/JS_FreeAtom to JS_DupValue/JS_FreeValue in function parsing
|
||||
|
||||
**Variable Definition and Lookup:**
|
||||
- [x] Update find_global_var() to use JSValue and js_key_equal()
|
||||
- [x] Update find_lexical_global_var() to use JSValue
|
||||
- [x] Update find_lexical_decl() to use JSValue and js_key_equal()
|
||||
- [x] Update js_define_var() to use JSValue
|
||||
- [x] Update js_parse_check_duplicate_parameter() to use JSValue and js_key_equal()
|
||||
- [x] Update js_parse_destructuring_var() to return JSValue
|
||||
- [x] Update js_parse_var() to use JSValue for variable names
|
||||
|
||||
**Comparison Helpers:**
|
||||
- [x] Create js_key_equal_str() for comparing JSValue with C string literals
|
||||
- [x] Update is_var_in_arg_scope() to use js_key_equal/js_key_equal_str
|
||||
- [x] Update has_with_scope() to use js_key_equal_str
|
||||
- [x] Update closure variable comparisons (cv->var_name) to use js_key_equal_str
|
||||
|
||||
**Property Access:**
|
||||
- [x] Fix JS_GetPropertyStr to create proper JSValue keys
|
||||
- [x] Fix JS_SetPropertyInternal callers to use JS_KEY_* instead of JS_ATOM_*
|
||||
|
||||
### JS_KEY_* Macros Added
|
||||
|
||||
Compile-time immediate ASCII string constants (≤7 chars):
|
||||
```c
|
||||
JS_KEY_empty, JS_KEY_name, JS_KEY_message, JS_KEY_stack,
|
||||
JS_KEY_errors, JS_KEY_Error, JS_KEY_cause, JS_KEY_length,
|
||||
JS_KEY_value, JS_KEY_get, JS_KEY_set, JS_KEY_raw,
|
||||
JS_KEY_flags, JS_KEY_source, JS_KEY_exec, JS_KEY_toJSON,
|
||||
JS_KEY_eval, JS_KEY_this, JS_KEY_true, JS_KEY_false,
|
||||
JS_KEY_null, JS_KEY_NaN, JS_KEY_default, JS_KEY_index,
|
||||
JS_KEY_input, JS_KEY_groups, JS_KEY_indices, JS_KEY_let,
|
||||
JS_KEY_var, JS_KEY_new, JS_KEY_of, JS_KEY_yield,
|
||||
JS_KEY_async, JS_KEY_target, JS_KEY_from, JS_KEY_meta,
|
||||
JS_KEY_as, JS_KEY_with
|
||||
```
|
||||
|
||||
Runtime macro for strings >7 chars:
|
||||
```c
|
||||
#define JS_KEY_STR(ctx, str) JS_NewStringLen((ctx), (str), sizeof(str) - 1)
|
||||
```
|
||||
|
||||
Helper function for comparing JSValue with C string literals:
|
||||
```c
|
||||
static JS_BOOL js_key_equal_str(JSValue a, const char *str);
|
||||
```
|
||||
|
||||
### Remaining Work
|
||||
|
||||
#### 5.3 Update js_parse_property_name() ✓
|
||||
- [x] Change return type from JSAtom* to JSValue*
|
||||
- [x] Update all callers (js_parse_object_literal, etc.)
|
||||
- [x] Updated get_lvalue(), put_lvalue(), js_parse_destructuring_element()
|
||||
|
||||
#### 5.4 Replace remaining emit_atom() calls with emit_key() ✓
|
||||
- [x] Removed emit_atom wrapper function
|
||||
- [x] Changed last emit_atom(JS_ATOM_this) to emit_key(JS_KEY_this)
|
||||
|
||||
#### 5.5 Update Variable Opcode Format in quickjs-opcode.h
|
||||
- [ ] Change `atom` format opcodes to `key` format
|
||||
- [ ] Change `atom_u8` and `atom_u16` to `key_u8` and `key_u16`
|
||||
|
||||
#### 5.6 Update VM Opcode Handlers ✓
|
||||
These now read cpool indices and look up JSValue:
|
||||
- [x] OP_check_var, OP_get_var_undef, OP_get_var
|
||||
- [x] OP_put_var, OP_put_var_init, OP_put_var_strict
|
||||
- [x] OP_set_name, OP_make_var_ref, OP_delete_var
|
||||
- [x] OP_define_var, OP_define_func, OP_throw_error
|
||||
- [x] OP_make_loc_ref, OP_make_arg_ref
|
||||
- [x] OP_define_method, OP_define_method_computed
|
||||
|
||||
#### 5.7 Update resolve_scope_var() ✓
|
||||
- [x] Changed signature to use JSValue var_name
|
||||
- [x] Updated all comparisons to use js_key_equal()/js_key_equal_str()
|
||||
- [x] Updated var_object_test() to use JSValue
|
||||
- [x] Updated optimize_scope_make_global_ref() to use JSValue
|
||||
- [x] Updated resolve_variables() callers to read from cpool
|
||||
|
||||
#### 5.8 Convert Remaining JS_ATOM_* Usages
|
||||
Categories remaining:
|
||||
- Some debug/print functions still use JSAtom
|
||||
- Some function signatures not yet converted
|
||||
- Will be addressed in Phase 7 cleanup
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Update Bytecode Serialization ✓
|
||||
|
||||
### 6.1 JS_WriteObjectTag Changes ✓
|
||||
- [x] Changed JS_WriteObjectTag to use bc_put_key() directly for property keys
|
||||
- [x] Removed JS_ValueToAtom/bc_put_atom path (was broken anyway)
|
||||
- [x] cpool values serialized via JS_WriteObjectRec()
|
||||
|
||||
### 6.2 JS_ReadObject Changes ✓
|
||||
- [x] Changed JS_ReadObjectTag to use bc_get_key() for property keys
|
||||
- [x] Uses JS_SetPropertyInternal with JSValue keys
|
||||
|
||||
### 6.3 Opcode Format Updates ✓
|
||||
- [x] Added OP_FMT_key_u8, OP_FMT_key_u16, OP_FMT_key_label_u16 formats
|
||||
- [x] Updated variable opcodes to use key formats instead of atom formats
|
||||
- [x] Updated bc_byte_swap() to handle new key formats
|
||||
- [x] Updated JS_WriteFunctionBytecode() to skip key format opcodes
|
||||
- [x] Updated JS_ReadFunctionBytecode() to skip key format opcodes
|
||||
|
||||
### 6.4 Version Bump ✓
|
||||
- [x] Incremented BC_VERSION from 5 to 6
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Final Cleanup ✓
|
||||
|
||||
### 7.1 Additional Parser/Compiler Fixes ✓
|
||||
- [x] Fixed TOK_IDENT case to use JSValue name, JS_DupValue, emit_key
|
||||
- [x] Fixed TOK_TRY catch clause to use JSValue name
|
||||
- [x] Fixed js_parse_statement_or_decl label_name to use JSValue
|
||||
- [x] Fixed OP_scope_get_var "eval" check to use js_key_equal_str
|
||||
- [x] Fixed js_parse_delete to use JSValue for name comparison
|
||||
- [x] Fixed JSON parsing to use js_key_from_string for property names
|
||||
- [x] Added js_key_from_string() helper function
|
||||
- [x] Added JS_KEY__ret_, JS_KEY__eval_, JS_KEY__var_ for internal names
|
||||
- [x] Updated add_closure_var, get_closure_var2, get_closure_var to use JSValue var_name
|
||||
- [x] Updated set_closure_from_var to use JS_DupValue
|
||||
- [x] Updated add_closure_variables to use JS_DupValue
|
||||
- [x] Updated instantiate_hoisted_definitions to use fd_cpool_add for keys
|
||||
- [x] Updated resolve_variables to use js_key_equal and fd_cpool_add
|
||||
|
||||
### 7.1.1 Property Access and Runtime Fixes ✓
|
||||
- [x] Fixed JS_GetPropertyValue to use js_key_from_string instead of JS_ValueToAtom
|
||||
- [x] Fixed JS_GetPropertyKey to use js_key_from_string for string keys
|
||||
- [x] Fixed JS_SetPropertyKey to use js_key_from_string for string keys
|
||||
- [x] Fixed JS_HasPropertyKey to use js_key_from_string for string keys
|
||||
- [x] Fixed JS_DeletePropertyKey to use js_key_from_string for string keys
|
||||
- [x] Updated JS_HasProperty signature to take JSValue prop
|
||||
- [x] Fixed OP_get_ref_value handler to use JSValue key
|
||||
- [x] Fixed OP_put_ref_value handler to use JSValue key
|
||||
- [x] Updated free_func_def to use JS_FreeValue for JSValue fields
|
||||
|
||||
### 7.2 Remove JSAtom Type and Functions ✓
|
||||
- [x] Removed most JS_ATOM_* constants (kept JS_ATOM_NULL, JS_ATOM_END for BC compat)
|
||||
- [x] JS_NewAtomString now returns JSValue using js_key_new
|
||||
- [x] JS_FreeAtom, JS_DupAtom are stubs (no-op for backward compat)
|
||||
- [x] JS_AtomToValue, JS_ValueToAtom are stubs (minimal BC compat)
|
||||
- [x] Replaced JS_ATOM_* usages with JS_KEY_* or JS_GetPropertyStr
|
||||
|
||||
### 7.3 Additional Runtime Fixes ✓
|
||||
- [x] Fixed free_function_bytecode to use JS_FreeValueRT for JSValue fields
|
||||
- [x] Fixed JS_SetPropertyFunctionList to use JSValue keys via find_key()
|
||||
- [x] Fixed JS_InstantiateFunctionListItem to use JSValue keys
|
||||
- [x] Fixed internalize_json_property to use JSValue names
|
||||
- [x] Fixed emit_break and push_break_entry to use JSValue label_name
|
||||
- [x] Implemented JS_Invoke to use JSValue method key
|
||||
|
||||
### 7.4 Remaining Stubs (kept for bytecode backward compatibility)
|
||||
- JSAtom typedef (uint32_t) - used in BC serialization
|
||||
- JS_ATOM_NULL, JS_ATOM_END - bytecode format markers
|
||||
- JS_FreeAtom, JS_DupAtom - no-op stubs
|
||||
- JS_FreeAtomRT, JS_DupAtomRT - no-op stubs
|
||||
- Legacy BC reader (idx_to_atom array) - for reading old bytecode
|
||||
|
||||
---
|
||||
|
||||
## Current Build Status
|
||||
|
||||
**Build: SUCCEEDS** with warnings (unused variables, labels)
|
||||
|
||||
**Statistics:**
|
||||
- JS_ATOM_* usages: Minimal (only BC serialization compat)
|
||||
- Property access uses JS_KEY_* macros or JS_GetPropertyStr
|
||||
- BC_VERSION: 6 (updated for new key-based format)
|
||||
|
||||
**What Works:**
|
||||
- All property access via JSValue keys
|
||||
- Keyword detection via string comparison
|
||||
- Function declaration parsing with JSValue names
|
||||
- Variable definition with JSValue names
|
||||
- Closure variable tracking with JSValue names
|
||||
- VM opcode handlers read cpool indices and look up JSValue
|
||||
- resolve_scope_var() uses JSValue throughout
|
||||
- js_parse_property_name() returns JSValue
|
||||
- Bytecode serialization uses bc_put_key/bc_get_key for property keys
|
||||
- Variable opcodes use key format (cpool indices)
|
||||
- JSON parsing uses JSValue keys
|
||||
- Internal variable names use JS_KEY__ret_, JS_KEY__eval_, JS_KEY__var_
|
||||
- JS_SetPropertyFunctionList uses JSValue keys
|
||||
- JS_Invoke uses JSValue method keys
|
||||
- break/continue labels use JSValue
|
||||
|
||||
---
|
||||
|
||||
## Phase 8: Migrate to New Tagging System (IN PROGRESS)
|
||||
|
||||
**Problem**: `JS_VALUE_GET_TAG` returns `JS_TAG_PTR` for all pointers, but ~200 places check for obsolete tags like `JS_TAG_OBJECT`, `JS_TAG_STRING`, `JS_TAG_FUNCTION`, etc., which are never returned. This causes crashes.
|
||||
|
||||
**Target Design** (from memory.md):
|
||||
- JSValue tags: Only `JS_TAG_INT`, `JS_TAG_PTR`, `JS_TAG_SHORT_FLOAT`, `JS_TAG_SPECIAL`
|
||||
- Pointer types determined by `objhdr_t` header at offset 8 in heap objects
|
||||
- mist_obj_type: `OBJ_ARRAY(0)`, `OBJ_BLOB(1)`, `OBJ_TEXT(2)`, `OBJ_RECORD(3)`, `OBJ_FUNCTION(4)`, etc.
|
||||
|
||||
### 8.1 Unified Heap Object Layout ✓
|
||||
- [x] Updated mist_text structure to have objhdr_t at offset 8:
|
||||
```c
|
||||
typedef struct mist_text {
|
||||
JSRefCountHeader _dummy_header; /* unused, for offset alignment */
|
||||
uint32_t _pad; /* padding to align objhdr_t to offset 8 */
|
||||
objhdr_t hdr; /* NOW at offset 8, like JSString */
|
||||
word_t length;
|
||||
word_t packed[];
|
||||
} mist_text;
|
||||
```
|
||||
- [x] JSString already has objhdr_t at offset 8
|
||||
|
||||
### 8.2 Type-Checking Helper Functions ✓
|
||||
Added lowercase internal helpers (to avoid conflict with quickjs.h declarations):
|
||||
```c
|
||||
static inline JS_BOOL js_is_gc_object(JSValue v) { return JS_IsPtr(v); }
|
||||
static inline JSGCObjectTypeEnum js_get_gc_type(JSValue v) {
|
||||
return ((JSGCObjectHeader *)JS_VALUE_GET_PTR(v))->gc_obj_type;
|
||||
}
|
||||
static inline JS_BOOL js_is_record(JSValue v) {
|
||||
if (!JS_IsPtr(v)) return FALSE;
|
||||
return js_get_gc_type(v) == JS_GC_OBJ_TYPE_RECORD;
|
||||
}
|
||||
static inline JS_BOOL js_is_array(JSValue v) {
|
||||
if (!JS_IsPtr(v)) return FALSE;
|
||||
return js_get_gc_type(v) == JS_GC_OBJ_TYPE_ARRAY;
|
||||
}
|
||||
static inline JS_BOOL js_is_function(JSValue v) {
|
||||
if (!JS_IsPtr(v)) return FALSE;
|
||||
return js_get_gc_type(v) == JS_GC_OBJ_TYPE_FUNCTION;
|
||||
}
|
||||
static inline JS_BOOL js_is_object(JSValue v) {
|
||||
if (!JS_IsPtr(v)) return FALSE;
|
||||
JSGCObjectTypeEnum t = js_get_gc_type(v);
|
||||
return t == JS_GC_OBJ_TYPE_RECORD || t == JS_GC_OBJ_TYPE_ARRAY;
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 Updated Core Functions ✓
|
||||
- [x] Updated JS_IsString to read objhdr_t from offset 8
|
||||
- [x] Updated js_key_hash to read objhdr_t from offset 8
|
||||
- [x] Updated js_key_equal to read objhdr_t from offset 8
|
||||
- [x] Updated __JS_FreeValueRT to use objhdr_type for type dispatch
|
||||
- [x] Updated JS_MarkValue, JS_MarkValueEdgeEx for GC
|
||||
- [x] Added JS_SetPropertyValue function
|
||||
- [x] Changed quickjs.h JS_IsFunction/JS_IsObject from inline to extern declarations
|
||||
|
||||
### 8.4 Tag Check Migration (PARTIAL)
|
||||
Updated some critical tag checks:
|
||||
- [x] Some JS_TAG_OBJECT checks → js_is_object() or js_is_record()
|
||||
- [ ] Many more JS_TAG_OBJECT checks remain (~200 total)
|
||||
- [ ] JS_TAG_FUNCTION checks → js_is_function()
|
||||
- [ ] JS_TAG_STRING checks (some already use JS_IsString)
|
||||
|
||||
### 8.5 Remaining Work
|
||||
- [ ] Fix ASAN memory corruption error (attempting free on address not malloc'd)
|
||||
- Crash occurs in js_def_realloc during js_realloc_array
|
||||
- Address is 112 bytes inside a JSFunctionDef allocation
|
||||
- [ ] Complete remaining ~200 tag check migrations
|
||||
- [ ] Add mist_hdr to JSFunction (optional, gc_obj_type already works)
|
||||
- [ ] Remove obsolete tag definitions from quickjs.h:
|
||||
- JS_TAG_STRING = -8
|
||||
- JS_TAG_ARRAY = -6
|
||||
- JS_TAG_FUNCTION = -5
|
||||
- JS_TAG_FUNCTION_BYTECODE = -2
|
||||
- JS_TAG_OBJECT = -1
|
||||
|
||||
### Current Status
|
||||
|
||||
**Build: SUCCEEDS** with warnings
|
||||
|
||||
**Runtime: CRASHES** with ASAN error:
|
||||
```
|
||||
==16122==ERROR: AddressSanitizer: attempting free on address which was not malloc()-ed
|
||||
```
|
||||
The crash occurs during test execution in `js_def_realloc` called from `js_realloc_array`.
|
||||
Root cause not yet identified - likely a pointer being passed to realloc that wasn't allocated with malloc.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- JSVarDef.var_name is JSValue
|
||||
- JSClosureVar.var_name is JSValue
|
||||
- JSGlobalVar.var_name is JSValue
|
||||
- JSFunctionDef.func_name is JSValue
|
||||
- BlockEnv.label_name is JSValue
|
||||
- OP_get_field/put_field/define_field already use cpool index format
|
||||
- JSRecord with open addressing is fully implemented
|
||||
- js_key_hash and js_key_equal work with both immediate and heap text
|
||||
- js_key_equal_str enables comparison with C string literals for internal names
|
||||
@@ -145,12 +145,12 @@ static void encode_js_value(json_encoder *enc, JSContext *js, JSValue val) {
|
||||
double d = js2number(js, val);
|
||||
if (d == (int)d) enc->writeInt(enc, (int)d);
|
||||
else enc->writeDouble(enc, d);
|
||||
} else if (JS_IsString(val)) {
|
||||
} else if (JS_IsText(val)) {
|
||||
size_t len;
|
||||
const char *str = JS_ToCStringLen(js, &len, val);
|
||||
enc->writeString(enc, str, len);
|
||||
JS_FreeCString(js, str);
|
||||
} else if (JS_IsArray(js, val)) {
|
||||
} else if (JS_IsArray(val)) {
|
||||
encode_js_array(enc, js, val);
|
||||
} else if (JS_IsObject(val)) {
|
||||
encode_js_object(enc, js, val);
|
||||
|
||||
8
qop.c
8
qop.c
@@ -8,10 +8,10 @@ static void js_qop_archive_finalizer(JSRuntime *rt, JSValue val) {
|
||||
qop_desc *qop = JS_GetOpaque(val, js_qop_archive_class_id);
|
||||
if (qop) {
|
||||
if (qop->hashmap) {
|
||||
js_free_rt(rt, qop->hashmap);
|
||||
js_free_rt(qop->hashmap);
|
||||
}
|
||||
qop_close(qop);
|
||||
js_free_rt(rt, qop);
|
||||
js_free_rt(qop);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ static void js_qop_writer_finalizer(JSRuntime *rt, JSValue val) {
|
||||
qop_writer *w = JS_GetOpaque(val, js_qop_writer_class_id);
|
||||
if (w) {
|
||||
if (w->fh) fclose(w->fh);
|
||||
if (w->files) js_free_rt(rt, w->files);
|
||||
js_free_rt(rt, w);
|
||||
if (w->files) js_free_rt(w->files);
|
||||
js_free_rt(w);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
243
source/cell.c
243
source/cell.c
@@ -2,7 +2,6 @@
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#define WOTA_IMPLEMENTATION
|
||||
#include "wota.h"
|
||||
|
||||
#define STB_DS_IMPLEMENTATION
|
||||
@@ -15,10 +14,15 @@
|
||||
#define CELL_SHOP_DIR ".cell"
|
||||
#define CELL_CORE_DIR "packages/core"
|
||||
|
||||
#include <math.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
/* Test suite declarations */
|
||||
int run_c_test_suite(JSContext *ctx);
|
||||
static int run_test_suite(size_t heap_size);
|
||||
|
||||
cell_rt *root_cell = NULL;
|
||||
static char *core_path = NULL;
|
||||
|
||||
@@ -114,20 +118,15 @@ void actor_disrupt(cell_rt *crt)
|
||||
|
||||
JSValue js_os_use(JSContext *js);
|
||||
JSValue js_math_use(JSContext *js);
|
||||
JSValue js_json_use(JSContext *js);
|
||||
JSValue js_nota_use(JSContext *js);
|
||||
JSValue js_wota_use(JSContext *js);
|
||||
|
||||
void script_startup(cell_rt *prt)
|
||||
{
|
||||
JSRuntime *rt;
|
||||
|
||||
rt = JS_NewRuntime();
|
||||
|
||||
JSContext *js = JS_NewContextRaw(rt);
|
||||
JS_SetInterruptHandler(rt, actor_interrupt_cb, prt);
|
||||
|
||||
JS_AddIntrinsicBaseObjects(js);
|
||||
JS_AddIntrinsicEval(js);
|
||||
JS_AddIntrinsicRegExp(js);
|
||||
JS_AddIntrinsicJSON(js);
|
||||
JSRuntime *rt = JS_NewRuntime();
|
||||
JS_SetInterruptHandler(rt, (JSInterruptHandler *)actor_interrupt_cb, prt);
|
||||
JSContext *js = JS_NewContext(rt);
|
||||
|
||||
JS_SetContextOpaque(js, prt);
|
||||
prt->context = js;
|
||||
@@ -135,38 +134,7 @@ void script_startup(cell_rt *prt)
|
||||
cell_rt *crt = JS_GetContextOpaque(js);
|
||||
JS_FreeValue(js, js_blob_use(js));
|
||||
|
||||
JSValue globalThis = JS_GetGlobalObject(js);
|
||||
|
||||
JSValue cell = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js,globalThis,"cell", cell);
|
||||
|
||||
JSValue hidden_fn = JS_NewObject(js);
|
||||
|
||||
JS_SetPropertyStr(js, cell, "hidden", hidden_fn);
|
||||
JS_SetPropertyStr(js, hidden_fn, "os", js_os_use(js));
|
||||
|
||||
crt->actor_sym = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, hidden_fn, "actorsym", JS_DupValue(js,crt->actor_sym));
|
||||
|
||||
if (crt->init_wota) {
|
||||
JS_SetPropertyStr(js, hidden_fn, "init", wota2value(js, crt->init_wota));
|
||||
// init wota can now be freed
|
||||
free(crt->init_wota);
|
||||
crt->init_wota = NULL;
|
||||
}
|
||||
|
||||
// Store the core path for scripts to use
|
||||
JSValue js_cell = JS_GetPropertyStr(js, globalThis, "cell");
|
||||
JSValue hidden = JS_GetPropertyStr(js, js_cell, "hidden");
|
||||
if (core_path) {
|
||||
JS_SetPropertyStr(js, hidden, "core_path", JS_NewString(js, core_path));
|
||||
}
|
||||
JS_FreeValue(js, hidden);
|
||||
JS_FreeValue(js, js_cell);
|
||||
|
||||
JS_FreeValue(js, globalThis);
|
||||
|
||||
// Load engine.cm from the core directory
|
||||
// Load and compile engine.cm
|
||||
size_t engine_size;
|
||||
char *data = load_core_file(ENGINE, &engine_size);
|
||||
if (!data) {
|
||||
@@ -174,9 +142,42 @@ void script_startup(cell_rt *prt)
|
||||
return;
|
||||
}
|
||||
|
||||
crt->state = ACTOR_RUNNING;
|
||||
JSValue v = JS_Eval(js, data, engine_size, ENGINE, 0);
|
||||
JSValue bytecode = JS_Compile(js, data, engine_size, ENGINE);
|
||||
free(data);
|
||||
if (JS_IsException(bytecode)) {
|
||||
uncaught_exception(js, bytecode);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create hidden environment
|
||||
JSValue hidden_env = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, hidden_env, "os", js_os_use(js));
|
||||
JS_SetPropertyStr(js, hidden_env, "json", js_json_use(js));
|
||||
JS_SetPropertyStr(js, hidden_env, "nota", js_nota_use(js));
|
||||
JS_SetPropertyStr(js, hidden_env, "wota", js_wota_use(js));
|
||||
|
||||
crt->actor_sym = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, hidden_env, "actorsym", JS_DupValue(js, crt->actor_sym));
|
||||
|
||||
// Always set init (even if null)
|
||||
if (crt->init_wota) {
|
||||
JS_SetPropertyStr(js, hidden_env, "init", wota2value(js, crt->init_wota));
|
||||
free(crt->init_wota);
|
||||
crt->init_wota = NULL;
|
||||
} else {
|
||||
JS_SetPropertyStr(js, hidden_env, "init", JS_NULL);
|
||||
}
|
||||
|
||||
if (core_path) {
|
||||
JS_SetPropertyStr(js, hidden_env, "core_path", JS_NewString(js, core_path));
|
||||
}
|
||||
|
||||
// Stone the environment
|
||||
hidden_env = JS_Stone(js, hidden_env);
|
||||
|
||||
// Integrate and run
|
||||
crt->state = ACTOR_RUNNING;
|
||||
JSValue v = JS_Integrate(js, bytecode, hidden_env);
|
||||
uncaught_exception(js, v);
|
||||
crt->state = ACTOR_IDLE;
|
||||
set_actor_state(crt);
|
||||
@@ -200,8 +201,158 @@ static void signal_handler(int sig)
|
||||
exit_handler();
|
||||
}
|
||||
|
||||
/* Run the C test suite with minimal runtime setup */
|
||||
static int run_test_suite(size_t heap_size)
|
||||
{
|
||||
JSRuntime *rt = JS_NewRuntime();
|
||||
if (!rt) {
|
||||
printf("Failed to create JS runtime\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
JSContext *ctx = JS_NewContextWithHeapSize(rt, heap_size);
|
||||
if (!ctx) {
|
||||
printf("Failed to create JS context\n");
|
||||
JS_FreeRuntime(rt);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int result = run_c_test_suite(ctx);
|
||||
|
||||
JS_FreeContext(ctx);
|
||||
JS_FreeRuntime(rt);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Run an immediate script string */
|
||||
static int run_eval(const char *script_or_file, int print_bytecode, int use_bootstrap_env)
|
||||
{
|
||||
if (!find_cell_shop()) return 1;
|
||||
|
||||
/* Check if argument is a file path */
|
||||
struct stat st;
|
||||
char *script = NULL;
|
||||
char *allocated_script = NULL;
|
||||
const char *filename = "<eval>";
|
||||
|
||||
if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) {
|
||||
/* It's a file, read its contents */
|
||||
FILE *f = fopen(script_or_file, "r");
|
||||
if (!f) {
|
||||
printf("Failed to open file: %s\n", script_or_file);
|
||||
return 1;
|
||||
}
|
||||
allocated_script = malloc(st.st_size + 1);
|
||||
if (!allocated_script) {
|
||||
fclose(f);
|
||||
printf("Failed to allocate memory for script\n");
|
||||
return 1;
|
||||
}
|
||||
size_t read_size = fread(allocated_script, 1, st.st_size, f);
|
||||
fclose(f);
|
||||
allocated_script[read_size] = '\0';
|
||||
script = allocated_script;
|
||||
filename = script_or_file;
|
||||
} else {
|
||||
/* Treat as inline script */
|
||||
script = (char *)script_or_file;
|
||||
}
|
||||
|
||||
JSRuntime *rt = JS_NewRuntime();
|
||||
if (!rt) {
|
||||
printf("Failed to create JS runtime\n");
|
||||
free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
|
||||
JSContext *ctx = JS_NewContext(rt);
|
||||
if (!ctx) {
|
||||
printf("Failed to create JS context\n");
|
||||
JS_FreeRuntime(rt);
|
||||
free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
|
||||
JSGCRef bytecode_ref;
|
||||
JS_PushGCRef(ctx, &bytecode_ref);
|
||||
bytecode_ref.val = JS_Compile(ctx, script, strlen(script), filename);
|
||||
if (JS_IsException(bytecode_ref.val)) {
|
||||
uncaught_exception(ctx, bytecode_ref.val);
|
||||
JS_PopGCRef(ctx, &bytecode_ref);
|
||||
result = 1;
|
||||
} else {
|
||||
if (print_bytecode) {
|
||||
printf("=== Compiled Bytecode ===\n");
|
||||
JS_DumpFunctionBytecode(ctx, bytecode_ref.val);
|
||||
}
|
||||
JSValue env = JS_NULL;
|
||||
if (use_bootstrap_env) {
|
||||
JSGCRef env_ref, json_ref, nota_ref, wota_ref;
|
||||
JS_PushGCRef(ctx, &env_ref);
|
||||
JS_PushGCRef(ctx, &json_ref);
|
||||
JS_PushGCRef(ctx, ¬a_ref);
|
||||
JS_PushGCRef(ctx, &wota_ref);
|
||||
env_ref.val = JS_NewObject(ctx);
|
||||
/* Create modules with GC rooting, then stone them */
|
||||
json_ref.val = js_json_use(ctx);
|
||||
nota_ref.val = js_nota_use(ctx);
|
||||
wota_ref.val = js_wota_use(ctx);
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "json", JS_Stone(ctx, json_ref.val));
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "nota", JS_Stone(ctx, nota_ref.val));
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "wota", JS_Stone(ctx, wota_ref.val));
|
||||
env = JS_Stone(ctx, env_ref.val);
|
||||
JS_PopGCRef(ctx, &wota_ref);
|
||||
JS_PopGCRef(ctx, ¬a_ref);
|
||||
JS_PopGCRef(ctx, &json_ref);
|
||||
JS_PopGCRef(ctx, &env_ref);
|
||||
}
|
||||
JSValue v = JS_Integrate(ctx, bytecode_ref.val, env);
|
||||
JS_PopGCRef(ctx, &bytecode_ref);
|
||||
if (JS_IsException(v)) {
|
||||
uncaught_exception(ctx, v);
|
||||
result = 1;
|
||||
} else {
|
||||
JS_FreeValue(ctx, v);
|
||||
}
|
||||
}
|
||||
|
||||
JS_FreeContext(ctx);
|
||||
JS_FreeRuntime(rt);
|
||||
free(allocated_script);
|
||||
return result;
|
||||
}
|
||||
|
||||
int cell_init(int argc, char **argv)
|
||||
{
|
||||
/* Check for --test flag to run C test suite */
|
||||
if (argc >= 2 && strcmp(argv[1], "--test") == 0) {
|
||||
size_t heap_size = 64 * 1024; /* 64KB default */
|
||||
if (argc >= 3) {
|
||||
heap_size = strtoull(argv[2], NULL, 0);
|
||||
/* Round up to power of 2 for buddy allocator */
|
||||
size_t p = 1;
|
||||
while (p < heap_size) p <<= 1;
|
||||
heap_size = p;
|
||||
}
|
||||
return run_test_suite(heap_size);
|
||||
}
|
||||
|
||||
/* Check for -e or --eval flag to run immediate script */
|
||||
/* Also check for -p flag to print bytecode */
|
||||
/* -s / --serializers flag provides json, nota, wota in env */
|
||||
if (argc >= 3 && (strcmp(argv[1], "-e") == 0 || strcmp(argv[1], "--eval") == 0)) {
|
||||
return run_eval(argv[2], 0, 0);
|
||||
}
|
||||
if (argc >= 3 && (strcmp(argv[1], "-p") == 0 || strcmp(argv[1], "--print-bytecode") == 0)) {
|
||||
return run_eval(argv[2], 1, 0);
|
||||
}
|
||||
if (argc >= 3 && (strcmp(argv[1], "-s") == 0 || strcmp(argv[1], "--serializers") == 0)) {
|
||||
return run_eval(argv[2], 0, 1);
|
||||
}
|
||||
|
||||
int script_start = 1;
|
||||
|
||||
/* Find the cell shop at ~/.cell */
|
||||
|
||||
@@ -28,6 +28,13 @@ JSValue number2js(JSContext *js, double g);
|
||||
JSValue wota2value(JSContext *js, void *v);
|
||||
void *value2wota(JSContext *js, JSValue v, JSValue replacer, size_t *bytes);
|
||||
|
||||
JSValue nota2value(JSContext *js, void *nota);
|
||||
void *value2nota(JSContext *js, JSValue v);
|
||||
|
||||
JSValue js_json_use(JSContext *js);
|
||||
JSValue js_nota_use(JSContext *js);
|
||||
JSValue js_wota_use(JSContext *js);
|
||||
|
||||
#define CELL_HOOK_ENTER 1
|
||||
#define CELL_HOOK_EXIT 2
|
||||
typedef void (*cell_hook)(const char *name, int type);
|
||||
|
||||
@@ -1,396 +0,0 @@
|
||||
#include "cell.h"
|
||||
#include "cell_internal.h"
|
||||
|
||||
#include "wota.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct ObjectRef {
|
||||
void *ptr;
|
||||
struct ObjectRef *next;
|
||||
} ObjectRef;
|
||||
|
||||
typedef struct WotaEncodeContext {
|
||||
JSContext *ctx;
|
||||
ObjectRef *visited_stack;
|
||||
WotaBuffer wb;
|
||||
int cycle;
|
||||
JSValue replacer;
|
||||
} WotaEncodeContext;
|
||||
|
||||
static void wota_stack_push(WotaEncodeContext *enc, JSValueConst val)
|
||||
{
|
||||
if (!JS_IsObject(val)) return;
|
||||
|
||||
ObjectRef *ref = malloc(sizeof(ObjectRef));
|
||||
if (!ref) return;
|
||||
|
||||
ref->ptr = JS_VALUE_GET_PTR(val);
|
||||
ref->next = enc->visited_stack;
|
||||
enc->visited_stack = ref;
|
||||
}
|
||||
|
||||
static void wota_stack_pop(WotaEncodeContext *enc)
|
||||
{
|
||||
if (!enc->visited_stack) return;
|
||||
|
||||
ObjectRef *top = enc->visited_stack;
|
||||
enc->visited_stack = top->next;
|
||||
free(top);
|
||||
}
|
||||
|
||||
static int wota_stack_has(WotaEncodeContext *enc, JSValueConst val)
|
||||
{
|
||||
if (!JS_IsObject(val)) return 0;
|
||||
|
||||
void *ptr = JS_VALUE_GET_PTR(val);
|
||||
ObjectRef *current = enc->visited_stack;
|
||||
|
||||
while (current) {
|
||||
if (current->ptr == ptr) return 1;
|
||||
current = current->next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void wota_stack_free(WotaEncodeContext *enc)
|
||||
{
|
||||
while (enc->visited_stack) {
|
||||
wota_stack_pop(enc);
|
||||
}
|
||||
}
|
||||
|
||||
static JSValue apply_replacer(WotaEncodeContext *enc, JSValueConst holder, JSAtom key, JSValueConst val)
|
||||
{
|
||||
if (JS_IsNull(enc->replacer)) return JS_DupValue(enc->ctx, val);
|
||||
JSValue key_val = (key == JS_ATOM_NULL) ? JS_NULL : JS_AtomToValue(enc->ctx, key);
|
||||
JSValue args[2] = { key_val, JS_DupValue(enc->ctx, val) };
|
||||
JSValue result = JS_Call(enc->ctx, enc->replacer, holder, 2, args);
|
||||
JS_FreeValue(enc->ctx, args[0]);
|
||||
JS_FreeValue(enc->ctx, args[1]);
|
||||
if (JS_IsException(result)) return JS_DupValue(enc->ctx, val);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSAtom key);
|
||||
|
||||
static void encode_object_properties(WotaEncodeContext *enc, JSValueConst val, JSValueConst holder)
|
||||
{
|
||||
JSContext *ctx = enc->ctx;
|
||||
JSPropertyEnum *ptab;
|
||||
uint32_t plen;
|
||||
if (JS_GetOwnPropertyNames(ctx, &ptab, &plen, val, JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK) < 0) {
|
||||
wota_write_sym(&enc->wb, WOTA_NULL);
|
||||
return;
|
||||
}
|
||||
uint32_t non_function_count = 0;
|
||||
JSValue props[plen];
|
||||
JSAtom atoms[plen];
|
||||
|
||||
for (uint32_t i = 0; i < plen; i++) {
|
||||
JSValue prop_val = JS_GetProperty(ctx, val, ptab[i].atom);
|
||||
if (!JS_IsFunction(prop_val)) {
|
||||
atoms[non_function_count] = ptab[i].atom;
|
||||
props[non_function_count++] = prop_val;
|
||||
} else
|
||||
JS_FreeValue(ctx, prop_val);
|
||||
}
|
||||
wota_write_record(&enc->wb, non_function_count);
|
||||
for (uint32_t i = 0; i < non_function_count; i++) {
|
||||
size_t plen;
|
||||
const char *prop_name = JS_AtomToCStringLen(ctx, &plen, atoms[i]);
|
||||
JSValue prop_val = props[i];
|
||||
wota_write_text_len(&enc->wb, prop_name, plen);
|
||||
wota_encode_value(enc, prop_val, val, atoms[i]);
|
||||
JS_FreeCString(ctx, prop_name);
|
||||
JS_FreeValue(ctx, prop_val);
|
||||
}
|
||||
|
||||
for (int i = 0; i < plen; i++)
|
||||
JS_FreeAtom(ctx, ptab[i].atom);
|
||||
|
||||
js_free(ctx, ptab);
|
||||
}
|
||||
|
||||
static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSAtom key)
|
||||
{
|
||||
JSContext *ctx = enc->ctx;
|
||||
JSValue replaced;
|
||||
if (!JS_IsNull(enc->replacer) && key != JS_ATOM_NULL)
|
||||
replaced = apply_replacer(enc, holder, key, val);
|
||||
else
|
||||
replaced = JS_DupValue(enc->ctx, val);
|
||||
|
||||
int tag = JS_VALUE_GET_TAG(replaced);
|
||||
switch (tag) {
|
||||
case JS_TAG_INT: {
|
||||
int32_t d;
|
||||
JS_ToInt32(ctx, &d, replaced);
|
||||
wota_write_int_word(&enc->wb, d);
|
||||
break;
|
||||
}
|
||||
case JS_TAG_FLOAT64: {
|
||||
double d;
|
||||
if (JS_ToFloat64(ctx, &d, replaced) < 0) {
|
||||
wota_write_sym(&enc->wb, WOTA_NULL);
|
||||
break;
|
||||
}
|
||||
wota_write_float_word(&enc->wb, d);
|
||||
break;
|
||||
}
|
||||
case JS_TAG_STRING: {
|
||||
size_t plen;
|
||||
const char *str = JS_ToCStringLen(ctx, &plen, replaced);
|
||||
wota_write_text_len(&enc->wb, str ? str : "", str ? plen : 0);
|
||||
JS_FreeCString(ctx, str);
|
||||
break;
|
||||
}
|
||||
case JS_TAG_BOOL:
|
||||
wota_write_sym(&enc->wb, JS_VALUE_GET_BOOL(replaced) ? WOTA_TRUE : WOTA_FALSE);
|
||||
break;
|
||||
case JS_TAG_NULL:
|
||||
wota_write_sym(&enc->wb, WOTA_NULL);
|
||||
break;
|
||||
case JS_TAG_OBJECT: {
|
||||
if (js_is_blob(ctx, replaced)) {
|
||||
size_t buf_len;
|
||||
void *buf_data = js_get_blob_data(ctx, &buf_len, replaced);
|
||||
if (buf_data == (void *)-1) {
|
||||
JS_FreeValue(ctx, replaced);
|
||||
return; // JS_EXCEPTION will be handled by caller
|
||||
}
|
||||
if (buf_len == 0) {
|
||||
wota_write_blob(&enc->wb, 0, "");
|
||||
} else {
|
||||
wota_write_blob(&enc->wb, (unsigned long long)buf_len * 8, (const char *)buf_data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (JS_IsArray(ctx, replaced)) {
|
||||
if (wota_stack_has(enc, replaced)) {
|
||||
enc->cycle = 1;
|
||||
break;
|
||||
}
|
||||
wota_stack_push(enc, replaced);
|
||||
int64_t arr_len;
|
||||
JS_GetLength(ctx, replaced, &arr_len);
|
||||
wota_write_array(&enc->wb, arr_len);
|
||||
for (int64_t i = 0; i < arr_len; i++) {
|
||||
JSValue elem_val = JS_GetPropertyUint32(ctx, replaced, i);
|
||||
JSAtom idx_atom = JS_NewAtomUInt32(ctx, (uint32_t)i);
|
||||
wota_encode_value(enc, elem_val, replaced, idx_atom);
|
||||
JS_FreeAtom(ctx, idx_atom);
|
||||
JS_FreeValue(ctx, elem_val);
|
||||
}
|
||||
wota_stack_pop(enc);
|
||||
break;
|
||||
}
|
||||
cell_rt *crt = JS_GetContextOpaque(ctx);
|
||||
// JSValue adata = JS_GetProperty(ctx, replaced, crt->actor_sym);
|
||||
JSValue adata = JS_NULL;
|
||||
if (!JS_IsNull(adata)) {
|
||||
wota_write_sym(&enc->wb, WOTA_PRIVATE);
|
||||
wota_encode_value(enc, adata, replaced, JS_ATOM_NULL);
|
||||
JS_FreeValue(ctx, adata);
|
||||
break;
|
||||
}
|
||||
JS_FreeValue(ctx, adata);
|
||||
if (wota_stack_has(enc, replaced)) {
|
||||
enc->cycle = 1;
|
||||
break;
|
||||
}
|
||||
wota_stack_push(enc, replaced);
|
||||
JSValue to_json = JS_GetPropertyStr(ctx, replaced, "toJSON");
|
||||
if (JS_IsFunction(to_json)) {
|
||||
JSValue result = JS_Call(ctx, to_json, replaced, 0, NULL);
|
||||
JS_FreeValue(ctx, to_json);
|
||||
if (!JS_IsException(result)) {
|
||||
wota_encode_value(enc, result, holder, key);
|
||||
JS_FreeValue(ctx, result);
|
||||
} else
|
||||
wota_write_sym(&enc->wb, WOTA_NULL);
|
||||
wota_stack_pop(enc);
|
||||
break;
|
||||
}
|
||||
JS_FreeValue(ctx, to_json);
|
||||
encode_object_properties(enc, replaced, holder);
|
||||
wota_stack_pop(enc);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
wota_write_sym(&enc->wb, WOTA_NULL);
|
||||
break;
|
||||
}
|
||||
JS_FreeValue(ctx, replaced);
|
||||
}
|
||||
|
||||
static char *decode_wota_value(JSContext *ctx, char *data_ptr, JSValue *out_val, JSValue holder, JSAtom key, JSValue reviver)
|
||||
{
|
||||
uint64_t first_word = *(uint64_t *)data_ptr;
|
||||
int type = (int)(first_word & 0xffU);
|
||||
switch (type) {
|
||||
case WOTA_INT: {
|
||||
long long val;
|
||||
data_ptr = wota_read_int(&val, data_ptr);
|
||||
*out_val = JS_NewInt64(ctx, val);
|
||||
break;
|
||||
}
|
||||
case WOTA_FLOAT: {
|
||||
double d;
|
||||
data_ptr = wota_read_float(&d, data_ptr);
|
||||
*out_val = JS_NewFloat64(ctx, d);
|
||||
break;
|
||||
}
|
||||
case WOTA_SYM: {
|
||||
int scode;
|
||||
data_ptr = wota_read_sym(&scode, data_ptr);
|
||||
if (scode == WOTA_PRIVATE) {
|
||||
JSValue inner = JS_NULL;
|
||||
data_ptr = decode_wota_value(ctx, data_ptr, &inner, holder, JS_ATOM_NULL, reviver);
|
||||
JSValue obj = JS_NewObject(ctx);
|
||||
cell_rt *crt = JS_GetContextOpaque(ctx);
|
||||
// JS_SetProperty(ctx, obj, crt->actor_sym, inner);
|
||||
*out_val = obj;
|
||||
} else if (scode == WOTA_NULL) *out_val = JS_NULL;
|
||||
else if (scode == WOTA_FALSE) *out_val = JS_NewBool(ctx, 0);
|
||||
else if (scode == WOTA_TRUE) *out_val = JS_NewBool(ctx, 1);
|
||||
else *out_val = JS_NULL;
|
||||
break;
|
||||
}
|
||||
case WOTA_BLOB: {
|
||||
long long blen;
|
||||
char *bdata = NULL;
|
||||
data_ptr = wota_read_blob(&blen, &bdata, data_ptr);
|
||||
*out_val = bdata ? js_new_blob_stoned_copy(ctx, (uint8_t *)bdata, (size_t)blen) : js_new_blob_stoned_copy(ctx, NULL, 0);
|
||||
if (bdata) free(bdata);
|
||||
break;
|
||||
}
|
||||
case WOTA_TEXT: {
|
||||
char *utf8 = NULL;
|
||||
data_ptr = wota_read_text(&utf8, data_ptr);
|
||||
*out_val = JS_NewString(ctx, utf8 ? utf8 : "");
|
||||
if (utf8) free(utf8);
|
||||
break;
|
||||
}
|
||||
case WOTA_ARR: {
|
||||
long long c;
|
||||
data_ptr = wota_read_array(&c, data_ptr);
|
||||
JSValue arr = JS_NewArrayLen(ctx, c);
|
||||
for (long long i = 0; i < c; i++) {
|
||||
JSValue elem_val = JS_NULL;
|
||||
JSAtom idx_atom = JS_NewAtomUInt32(ctx, (uint32_t)i);
|
||||
data_ptr = decode_wota_value(ctx, data_ptr, &elem_val, arr, idx_atom, reviver);
|
||||
JS_SetPropertyUint32(ctx, arr, i, elem_val);
|
||||
JS_FreeAtom(ctx, idx_atom);
|
||||
}
|
||||
*out_val = arr;
|
||||
break;
|
||||
}
|
||||
case WOTA_REC: {
|
||||
long long c;
|
||||
data_ptr = wota_read_record(&c, data_ptr);
|
||||
JSValue obj = JS_NewObject(ctx);
|
||||
for (long long i = 0; i < c; i++) {
|
||||
char *tkey = NULL;
|
||||
size_t key_len;
|
||||
data_ptr = wota_read_text_len(&key_len, &tkey, data_ptr);
|
||||
if (!tkey) continue; // invalid key
|
||||
JSAtom prop_key = JS_NewAtomLen(ctx, tkey, key_len);
|
||||
JSValue sub_val = JS_NULL;
|
||||
data_ptr = decode_wota_value(ctx, data_ptr, &sub_val, obj, prop_key, reviver);
|
||||
JS_SetProperty(ctx, obj, prop_key, sub_val);
|
||||
JS_FreeAtom(ctx, prop_key);
|
||||
free(tkey);
|
||||
}
|
||||
*out_val = obj;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
data_ptr += 8;
|
||||
*out_val = JS_NULL;
|
||||
break;
|
||||
}
|
||||
if (!JS_IsNull(reviver)) {
|
||||
JSValue key_val = (key == JS_ATOM_NULL) ? JS_NULL : JS_AtomToValue(ctx, key);
|
||||
JSValue args[2] = { key_val, JS_DupValue(ctx, *out_val) };
|
||||
JSValue revived = JS_Call(ctx, reviver, holder, 2, args);
|
||||
JS_FreeValue(ctx, args[0]);
|
||||
JS_FreeValue(ctx, args[1]);
|
||||
if (!JS_IsException(revived)) {
|
||||
JS_FreeValue(ctx, *out_val);
|
||||
*out_val = revived;
|
||||
} else
|
||||
JS_FreeValue(ctx, revived);
|
||||
}
|
||||
return data_ptr;
|
||||
}
|
||||
|
||||
void *value2wota(JSContext *ctx, JSValue v, JSValue replacer, size_t *bytes)
|
||||
{
|
||||
WotaEncodeContext enc_s, *enc = &enc_s;
|
||||
|
||||
enc->ctx = ctx;
|
||||
enc->visited_stack = NULL;
|
||||
enc->cycle = 0;
|
||||
enc->replacer = replacer;
|
||||
wota_buffer_init(&enc->wb, 16);
|
||||
wota_encode_value(enc, v, JS_NULL, JS_ATOM_NULL);
|
||||
if (enc->cycle) {
|
||||
wota_stack_free(enc);
|
||||
wota_buffer_free(&enc->wb);
|
||||
return NULL;
|
||||
}
|
||||
wota_stack_free(enc);
|
||||
size_t total_bytes = enc->wb.size * sizeof(uint64_t);
|
||||
void *wota = realloc(enc->wb.data, total_bytes);
|
||||
if (bytes) *bytes = total_bytes;
|
||||
return wota;
|
||||
}
|
||||
|
||||
JSValue wota2value(JSContext *ctx, void *wota)
|
||||
{
|
||||
JSValue result = JS_NULL;
|
||||
JSValue holder = JS_NewObject(ctx);
|
||||
decode_wota_value(ctx, wota, &result, holder, JS_ATOM_NULL, JS_NULL);
|
||||
JS_FreeValue(ctx, holder);
|
||||
return result;
|
||||
}
|
||||
|
||||
static JSValue js_wota_encode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
if (argc < 1) return JS_ThrowTypeError(ctx, "wota.encode requires at least 1 argument");
|
||||
size_t total_bytes;
|
||||
void *wota = value2wota(ctx, argv[0], JS_IsFunction(argv[1]) ? argv[1] : JS_NULL, &total_bytes);
|
||||
JSValue ret = js_new_blob_stoned_copy(ctx, wota, total_bytes);
|
||||
free(wota);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static JSValue js_wota_decode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
if (argc < 1) return JS_NULL;
|
||||
size_t len;
|
||||
uint8_t *buf = js_get_blob_data(ctx, &len, argv[0]);
|
||||
if (buf == (uint8_t *)-1) return JS_EXCEPTION;
|
||||
if (!buf || len == 0) return JS_ThrowTypeError(ctx, "No blob data present");
|
||||
JSValue reviver = (argc > 1 && JS_IsFunction(argv[1])) ? argv[1] : JS_NULL;
|
||||
char *data_ptr = (char *)buf;
|
||||
JSValue result = JS_NULL;
|
||||
JSValue holder = JS_NewObject(ctx);
|
||||
JSAtom empty_atom = JS_NewAtom(ctx, "");
|
||||
decode_wota_value(ctx, data_ptr, &result, holder, empty_atom, reviver);
|
||||
JS_FreeAtom(ctx, empty_atom);
|
||||
JS_FreeValue(ctx, holder);
|
||||
return result;
|
||||
}
|
||||
|
||||
static const JSCFunctionListEntry js_wota_funcs[] = {
|
||||
JS_CFUNC_DEF("encode", 2, js_wota_encode),
|
||||
JS_CFUNC_DEF("decode", 2, js_wota_decode),
|
||||
};
|
||||
|
||||
JSValue js_wota_use(JSContext *ctx)
|
||||
{
|
||||
JSValue exports = JS_NewObject(ctx);
|
||||
JS_SetPropertyFunctionList(ctx, exports, js_wota_funcs, sizeof(js_wota_funcs)/sizeof(js_wota_funcs[0]));
|
||||
return exports;
|
||||
}
|
||||
@@ -28,7 +28,6 @@ FMT(none)
|
||||
FMT(none_int)
|
||||
FMT(none_loc)
|
||||
FMT(none_arg)
|
||||
FMT(none_var_ref)
|
||||
FMT(u8)
|
||||
FMT(i8)
|
||||
FMT(loc8)
|
||||
@@ -42,17 +41,16 @@ FMT(npopx)
|
||||
FMT(npop_u16)
|
||||
FMT(loc)
|
||||
FMT(arg)
|
||||
FMT(var_ref)
|
||||
FMT(u32)
|
||||
FMT(i32)
|
||||
FMT(const)
|
||||
FMT(label)
|
||||
FMT(atom)
|
||||
FMT(atom_u8)
|
||||
FMT(atom_u16)
|
||||
FMT(atom_label_u8)
|
||||
FMT(atom_label_u16)
|
||||
FMT(label_u16)
|
||||
FMT(key)
|
||||
FMT(key_u8)
|
||||
FMT(key_u16)
|
||||
FMT(key_label_u16)
|
||||
FMT(u8_u16) /* 1 byte + 2 bytes for upvalue access */
|
||||
#undef FMT
|
||||
#endif /* FMT */
|
||||
|
||||
@@ -68,7 +66,6 @@ DEF(invalid, 1, 0, 0, none) /* never emitted */
|
||||
DEF( push_i32, 5, 0, 1, i32)
|
||||
DEF( push_const, 5, 0, 1, const)
|
||||
DEF( fclosure, 5, 0, 1, const) /* must follow push_const */
|
||||
DEF(push_atom_value, 5, 0, 1, atom)
|
||||
DEF( null, 1, 0, 1, none)
|
||||
DEF( push_this, 1, 0, 1, none) /* only used at the start of a function */
|
||||
DEF( push_false, 1, 0, 1, none)
|
||||
@@ -104,40 +101,38 @@ DEF( array_from, 3, 0, 1, npop) /* arguments are not counted in n_pop */
|
||||
DEF( return, 1, 1, 0, none)
|
||||
DEF( return_undef, 1, 0, 0, none)
|
||||
DEF( throw, 1, 1, 0, none)
|
||||
DEF( throw_error, 6, 0, 0, atom_u8)
|
||||
DEF( eval, 5, 1, 1, npop_u16) /* func args... -> ret_val */
|
||||
DEF( throw_error, 6, 0, 0, key_u8)
|
||||
DEF( regexp, 1, 2, 1, none) /* create a RegExp object from the pattern and a
|
||||
bytecode string */
|
||||
|
||||
DEF( check_var, 5, 0, 1, atom) /* check if a variable exists */
|
||||
DEF( get_var_undef, 5, 0, 1, atom) /* push undefined if the variable does not exist */
|
||||
DEF( get_var, 5, 0, 1, atom) /* throw an exception if the variable does not exist */
|
||||
DEF( put_var, 5, 1, 0, atom) /* must come after get_var */
|
||||
DEF( put_var_init, 5, 1, 0, atom) /* must come after put_var. Used to initialize a global lexical variable */
|
||||
DEF( put_var_strict, 5, 2, 0, atom) /* for strict mode variable write */
|
||||
/* Global variable access - resolved by linker to get/set_global_slot */
|
||||
DEF( check_var, 5, 0, 1, key) /* check if a variable exists - resolved by linker */
|
||||
DEF( get_var_undef, 5, 0, 1, key) /* resolved by linker to get_global_slot */
|
||||
DEF( get_var, 5, 0, 1, key) /* resolved by linker to get_global_slot */
|
||||
DEF( put_var, 5, 1, 0, key) /* resolved by linker to set_global_slot */
|
||||
DEF( put_var_init, 5, 1, 0, key) /* resolved by linker to set_global_slot */
|
||||
DEF( put_var_strict, 5, 2, 0, key) /* resolved by linker to set_global_slot */
|
||||
|
||||
DEF( get_ref_value, 1, 2, 3, none)
|
||||
DEF( put_ref_value, 1, 3, 0, none)
|
||||
|
||||
DEF( define_var, 6, 0, 0, atom_u8)
|
||||
DEF(check_define_var, 6, 0, 0, atom_u8)
|
||||
DEF( define_func, 6, 1, 0, atom_u8)
|
||||
DEF( get_field, 5, 1, 1, atom)
|
||||
DEF( get_field2, 5, 1, 2, atom)
|
||||
DEF( put_field, 5, 2, 0, atom)
|
||||
/* Global variable opcodes - resolved by linker to get/set_global_slot */
|
||||
DEF( define_var, 6, 0, 0, key_u8)
|
||||
DEF(check_define_var, 6, 0, 0, key_u8)
|
||||
DEF( define_func, 6, 1, 0, key_u8)
|
||||
DEF( get_field, 5, 1, 1, key)
|
||||
DEF( get_field2, 5, 1, 2, key)
|
||||
DEF( put_field, 5, 2, 0, key)
|
||||
DEF( get_array_el, 1, 2, 1, none)
|
||||
DEF( get_array_el2, 1, 2, 2, none) /* obj prop -> obj value */
|
||||
DEF( get_array_el3, 1, 2, 3, none) /* obj prop -> obj prop1 value */
|
||||
DEF( put_array_el, 1, 3, 0, none)
|
||||
DEF( define_field, 5, 2, 1, atom)
|
||||
DEF( set_name, 5, 1, 1, atom)
|
||||
DEF( define_field, 5, 2, 1, key)
|
||||
DEF( set_name, 5, 1, 1, key)
|
||||
DEF(set_name_computed, 1, 2, 2, none)
|
||||
DEF(define_array_el, 1, 3, 2, none)
|
||||
DEF(copy_data_properties, 2, 3, 3, u8)
|
||||
DEF( define_method, 6, 2, 1, atom_u8)
|
||||
DEF( define_method, 6, 2, 1, key_u8)
|
||||
DEF(define_method_computed, 2, 3, 1, u8) /* must come after define_method */
|
||||
DEF( define_class, 6, 2, 2, atom_u8) /* parent ctor -> ctor proto */
|
||||
DEF( define_class_computed, 6, 3, 3, atom_u8) /* field_name parent ctor -> field_name ctor proto (class with computed name) */
|
||||
DEF( define_class, 6, 2, 2, key_u8) /* parent ctor -> ctor proto */
|
||||
DEF( define_class_computed, 6, 3, 3, key_u8) /* field_name parent ctor -> field_name ctor proto (class with computed name) */
|
||||
|
||||
DEF( get_loc, 3, 0, 1, loc)
|
||||
DEF( put_loc, 3, 1, 0, loc) /* must come after get_loc */
|
||||
@@ -145,18 +140,11 @@ DEF( set_loc, 3, 1, 1, loc) /* must come after put_loc */
|
||||
DEF( get_arg, 3, 0, 1, arg)
|
||||
DEF( put_arg, 3, 1, 0, arg) /* must come after get_arg */
|
||||
DEF( set_arg, 3, 1, 1, arg) /* must come after put_arg */
|
||||
DEF( get_var_ref, 3, 0, 1, var_ref)
|
||||
DEF( put_var_ref, 3, 1, 0, var_ref) /* must come after get_var_ref */
|
||||
DEF( set_var_ref, 3, 1, 1, var_ref) /* must come after put_var_ref */
|
||||
DEF(set_loc_uninitialized, 3, 0, 0, loc)
|
||||
DEF( get_loc_check, 3, 0, 1, loc)
|
||||
DEF( put_loc_check, 3, 1, 0, loc) /* must come after get_loc_check */
|
||||
DEF( put_loc_check_init, 3, 1, 0, loc)
|
||||
DEF(get_loc_checkthis, 3, 0, 1, loc)
|
||||
DEF(get_var_ref_check, 3, 0, 1, var_ref)
|
||||
DEF(put_var_ref_check, 3, 1, 0, var_ref) /* must come after get_var_ref_check */
|
||||
DEF(put_var_ref_check_init, 3, 1, 0, var_ref)
|
||||
DEF( close_loc, 3, 0, 0, loc)
|
||||
DEF( if_false, 5, 1, 0, label)
|
||||
DEF( if_true, 5, 1, 0, label) /* must come after if_false */
|
||||
DEF( goto, 5, 0, 0, label) /* must come after if_true */
|
||||
@@ -165,15 +153,8 @@ DEF( gosub, 5, 0, 0, label) /* used to execute the finally block */
|
||||
DEF( ret, 1, 1, 0, none) /* used to return from the finally block */
|
||||
DEF( nip_catch, 1, 2, 1, none) /* catch ... a -> a */
|
||||
|
||||
DEF( to_object, 1, 1, 1, none)
|
||||
//DEF( to_string, 1, 1, 1, none)
|
||||
DEF( to_propkey, 1, 1, 1, none)
|
||||
|
||||
DEF( make_loc_ref, 7, 0, 2, atom_u16)
|
||||
DEF( make_arg_ref, 7, 0, 2, atom_u16)
|
||||
DEF(make_var_ref_ref, 7, 0, 2, atom_u16)
|
||||
DEF( make_var_ref, 5, 0, 2, atom)
|
||||
|
||||
/* arithmetic/logic operations */
|
||||
DEF( neg, 1, 1, 1, none)
|
||||
DEF( plus, 1, 1, 1, none)
|
||||
@@ -187,7 +168,7 @@ DEF( add_loc, 2, 1, 0, loc8)
|
||||
DEF( not, 1, 1, 1, none)
|
||||
DEF( lnot, 1, 1, 1, none)
|
||||
DEF( delete, 1, 2, 1, none)
|
||||
DEF( delete_var, 5, 0, 1, atom)
|
||||
DEF( delete_var, 5, 0, 1, key) /* deprecated - global object is immutable */
|
||||
|
||||
DEF( mul, 1, 2, 1, none)
|
||||
DEF( mul_float, 1, 2, 1, none)
|
||||
@@ -212,8 +193,21 @@ DEF( strict_neq, 1, 2, 1, none)
|
||||
DEF( and, 1, 2, 1, none)
|
||||
DEF( xor, 1, 2, 1, none)
|
||||
DEF( or, 1, 2, 1, none)
|
||||
/* template literal concatenation - pops N parts, pushes concatenated string */
|
||||
DEF(template_concat, 3, 0, 1, npop_u16)
|
||||
/* format template - format_string_cpool_idx(u32), expr_count(u16)
|
||||
Note: n_push=2 ensures stack has room for temp [format_str, arr] pair,
|
||||
even though we only leave 1 value (the result) on the stack. */
|
||||
DEF(format_template, 7, 0, 1, npop_u16)
|
||||
|
||||
/* Upvalue access (closures via outer_frame chain) */
|
||||
DEF( get_up, 4, 0, 1, u8_u16) /* depth:u8, slot:u16 -> value */
|
||||
DEF( set_up, 4, 1, 0, u8_u16) /* value, depth:u8, slot:u16 -> */
|
||||
|
||||
/* Name resolution with bytecode patching */
|
||||
DEF( get_name, 5, 0, 1, const) /* cpool_idx -> value, patches itself */
|
||||
DEF( get_env_slot, 3, 0, 1, u16) /* slot -> value (patched from get_name) */
|
||||
DEF( set_env_slot, 3, 1, 0, u16) /* value -> slot (patched from put_var) */
|
||||
DEF(get_global_slot, 3, 0, 1, u16) /* slot -> value (patched from get_var) */
|
||||
DEF(set_global_slot, 3, 1, 0, u16) /* value -> slot (patched from put_var) */
|
||||
|
||||
/* must be the last non short and non temporary opcode */
|
||||
DEF( nop, 1, 0, 0, none)
|
||||
@@ -227,15 +221,13 @@ def( label, 5, 0, 0, label) /* emitted in phase 1, removed in phase 3 *
|
||||
|
||||
/* the following opcodes must be in the same order as the 'with_x' and
|
||||
get_var_undef, get_var and put_var opcodes */
|
||||
def(scope_get_var_undef, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */
|
||||
def( scope_get_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */
|
||||
def( scope_put_var, 7, 1, 0, atom_u16) /* emitted in phase 1, removed in phase 2 */
|
||||
def(scope_delete_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */
|
||||
def( scope_make_ref, 11, 0, 2, atom_label_u16) /* emitted in phase 1, removed in phase 2 */
|
||||
def( scope_get_ref, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */
|
||||
def(scope_put_var_init, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */
|
||||
def(scope_get_var_checkthis, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2, only used to return 'this' in derived class constructors */
|
||||
def(get_field_opt_chain, 5, 1, 1, atom) /* emitted in phase 1, removed in phase 2 */
|
||||
def(scope_get_var_undef, 7, 0, 1, key_u16) /* emitted in phase 1, removed in phase 2 */
|
||||
def( scope_get_var, 7, 0, 1, key_u16) /* emitted in phase 1, removed in phase 2 */
|
||||
def( scope_put_var, 7, 1, 0, key_u16) /* emitted in phase 1, removed in phase 2 */
|
||||
def(scope_delete_var, 7, 0, 1, key_u16) /* emitted in phase 1, removed in phase 2 */
|
||||
def(scope_put_var_init, 7, 0, 2, key_u16) /* emitted in phase 1, removed in phase 2 */
|
||||
def(scope_get_var_checkthis, 7, 0, 1, key_u16) /* emitted in phase 1, removed in phase 2, only used to return 'this' in derived class constructors */
|
||||
def(get_field_opt_chain, 5, 1, 1, key) /* emitted in phase 1, removed in phase 2 */
|
||||
def(get_array_el_opt_chain, 1, 2, 1, none) /* emitted in phase 1, removed in phase 2 */
|
||||
def( set_class_name, 5, 1, 1, u32) /* emitted in phase 1, removed in phase 2 */
|
||||
|
||||
@@ -285,18 +277,6 @@ DEF( set_arg0, 1, 1, 1, none_arg)
|
||||
DEF( set_arg1, 1, 1, 1, none_arg)
|
||||
DEF( set_arg2, 1, 1, 1, none_arg)
|
||||
DEF( set_arg3, 1, 1, 1, none_arg)
|
||||
DEF( get_var_ref0, 1, 0, 1, none_var_ref)
|
||||
DEF( get_var_ref1, 1, 0, 1, none_var_ref)
|
||||
DEF( get_var_ref2, 1, 0, 1, none_var_ref)
|
||||
DEF( get_var_ref3, 1, 0, 1, none_var_ref)
|
||||
DEF( put_var_ref0, 1, 1, 0, none_var_ref)
|
||||
DEF( put_var_ref1, 1, 1, 0, none_var_ref)
|
||||
DEF( put_var_ref2, 1, 1, 0, none_var_ref)
|
||||
DEF( put_var_ref3, 1, 1, 0, none_var_ref)
|
||||
DEF( set_var_ref0, 1, 1, 1, none_var_ref)
|
||||
DEF( set_var_ref1, 1, 1, 1, none_var_ref)
|
||||
DEF( set_var_ref2, 1, 1, 1, none_var_ref)
|
||||
DEF( set_var_ref3, 1, 1, 1, none_var_ref)
|
||||
|
||||
DEF( if_false8, 2, 1, 0, label8)
|
||||
DEF( if_true8, 2, 1, 0, label8) /* must come after if_false8 */
|
||||
|
||||
50866
source/quickjs.c
50866
source/quickjs.c
File diff suppressed because it is too large
Load Diff
1703
source/quickjs.h
1703
source/quickjs.h
File diff suppressed because it is too large
Load Diff
@@ -122,7 +122,7 @@ void actor_free(cell_rt *actor)
|
||||
JS_FreeValue(js, actor->message_handle);
|
||||
JS_FreeValue(js, actor->on_exception);
|
||||
JS_FreeValue(js, actor->unneeded);
|
||||
JS_FreeAtom(js, actor->actor_sym);
|
||||
JS_FreeValue(js, actor->actor_sym);
|
||||
|
||||
/* Free timer callbacks stored in actor */
|
||||
for (int i = 0; i < hmlen(actor->timers); i++) {
|
||||
@@ -332,7 +332,7 @@ cell_rt *create_actor(void *wota)
|
||||
actor->message_handle = JS_NULL;
|
||||
actor->unneeded = JS_NULL;
|
||||
actor->on_exception = JS_NULL;
|
||||
actor->actor_sym = JS_ATOM_NULL;
|
||||
actor->actor_sym = JS_NULL;
|
||||
|
||||
arrsetcap(actor->letters, 5);
|
||||
|
||||
|
||||
2356
source/suite.c
Normal file
2356
source/suite.c
Normal file
File diff suppressed because it is too large
Load Diff
94
status.md
Normal file
94
status.md
Normal file
@@ -0,0 +1,94 @@
|
||||
QuickJS Mist Memory Format Refactoring
|
||||
Current Status
|
||||
The codebase is partially refactored but doesn't compile due to missing KeyId type definitions.
|
||||
|
||||
Incremental Refactoring Tasks
|
||||
Phase 0: Fix Compilation (Prerequisite)
|
||||
Define missing KeyId type as transitional typedef (will be replaced by JSValue later)
|
||||
Define K_EMPTY, K_TOMB,
|
||||
key_text()
|
||||
,
|
||||
key_is_text()
|
||||
,
|
||||
key_payload()
|
||||
macros/functions
|
||||
Verify build compiles and tests pass
|
||||
Phase 1: New JSValue Encoding in quickjs.h
|
||||
Add new LSB-based tag constants alongside existing tags
|
||||
Add JS_TAG_SHORT_FLOAT for 61-bit truncated double
|
||||
Add JS_TAG_STRING_ASCII for immediate 7-byte ASCII strings
|
||||
Add new value extraction/creation macros
|
||||
Add type check inline functions
|
||||
Keep existing NaN-boxing code active (compile-time switch)
|
||||
Phase 2: Short Float Implementation
|
||||
Implement JS_NewFloat64_ShortFloat() with range checking
|
||||
Implement JS_VALUE_GET_FLOAT64_ShortFloat() for decoding
|
||||
Out-of-range values return JS_NULL
|
||||
Prefer integer encoding when exact
|
||||
Phase 3: Immediate ASCII String
|
||||
Phase 3: Immediate ASCII String
|
||||
Implement JS_TryNewImmediateASCII() for strings up to 7 chars
|
||||
Implement JS_IsImmediateASCII() type check
|
||||
Implement JS_GetImmediateASCIILen() and JS_GetImmediateASCIIChar()
|
||||
Integrate with
|
||||
JS_NewStringLen()
|
||||
to try immediate first
|
||||
Phase 4: Remove JSStringRope
|
||||
Delete JSStringRope structure
|
||||
Remove JS_TAG_STRING_ROPE handling
|
||||
Update string concatenation to create immediate mist_text objects
|
||||
Remove rope-related iterator functions
|
||||
Phase 5: Refactor JSString to UTF-32 (mist_text)
|
||||
Modify struct JSString to store UTF-32 characters only
|
||||
Remove is_wide_char flag and 8.16 unions
|
||||
Update
|
||||
js_alloc_string
|
||||
to allocate UTF-32 buffer
|
||||
Update string creation functions (
|
||||
js_new_string8
|
||||
, etc.)
|
||||
Update all string accessors to use UTF-32
|
||||
Implement immediate-to-UTF32 conversion helper
|
||||
Update string operations (
|
||||
concat
|
||||
,
|
||||
compare
|
||||
) to work on UTF-32
|
||||
Phase 6: Replace KeyId with JSValue in Records
|
||||
Change JSRecordEntry.key from KeyId to JSValue
|
||||
Update
|
||||
rec_hash_key()
|
||||
to hash JSValue keys directly
|
||||
Update
|
||||
rec_find_slot()
|
||||
for JSValue key comparison
|
||||
Update
|
||||
rec_get_own()
|
||||
,
|
||||
rec_get()
|
||||
,
|
||||
rec_set_own()
|
||||
for JSValue keys
|
||||
Remove KeyId typedef and related functions
|
||||
Phase 7: Consolidate JSObject → JSRecord
|
||||
Remove JSShape and JSShapeProperty structures
|
||||
Remove shape hash table from JSRuntime
|
||||
Update all property access to use JSRecord
|
||||
Migrate JSObject users to JSRecord
|
||||
Remove JSObject structure
|
||||
Phase 8: Update GC for New Format
|
||||
Update mark_children for JSRecord with JSValue keys
|
||||
Update free_record for JSValue keys
|
||||
Handle immediate values correctly (no marking needed)
|
||||
Test for cycles and correct collection
|
||||
Phase 9: C Class Storage in Slot 0
|
||||
Implement slot 0 reservation for class_id and opaque pointer
|
||||
Update JS_SetOpaque() / JS_GetOpaque()
|
||||
Migrate existing class storage
|
||||
Verification Checklist
|
||||
Build compiles without errors
|
||||
Existing tests pass
|
||||
Property access works correctly
|
||||
GC correctly handles cycles
|
||||
Short float encoding/decoding verified
|
||||
Immediate ASCII strings work
|
||||
190
tests/suite.cm
190
tests/suite.cm
@@ -1244,15 +1244,16 @@ return {
|
||||
// EDGE CASES AND SPECIAL VALUES
|
||||
// ============================================================================
|
||||
|
||||
test_infinity: function() {
|
||||
test_division_by_zero_is_null: function() {
|
||||
var inf = 1 / 0
|
||||
if (!(inf > 1000000)) throw "infinity failed"
|
||||
if (!(-inf < -1000000)) throw "negative infinity failed"
|
||||
if (inf != null) throw "division by zero should be null"
|
||||
var ninf = -1 / 0
|
||||
if (ninf != null) throw "negative division by zero should be null"
|
||||
},
|
||||
|
||||
test_nan: function() {
|
||||
test_zero_div_zero_is_null: function() {
|
||||
var nan = 0 / 0
|
||||
if (nan == nan) throw "NaN should not equal itself"
|
||||
if (nan != null) throw "0/0 should be null"
|
||||
},
|
||||
|
||||
test_max_safe_integer: function() {
|
||||
@@ -1403,17 +1404,36 @@ return {
|
||||
|
||||
test_number_division_by_zero: function() {
|
||||
var result = 1 / 0
|
||||
if (!(result > 1000000)) throw "division by zero should give infinity"
|
||||
if (result != null) throw "division by zero should give null"
|
||||
},
|
||||
|
||||
test_number_negative_division_by_zero: function() {
|
||||
var result = -1 / 0
|
||||
if (!(result < -1000000)) throw "negative division by zero should give -infinity"
|
||||
if (result != null) throw "negative division by zero should give null"
|
||||
},
|
||||
|
||||
test_zero_division_by_zero: function() {
|
||||
var result = 0 / 0
|
||||
if (result == result) throw "0/0 should give NaN"
|
||||
if (result != null) throw "0/0 should give null"
|
||||
},
|
||||
|
||||
test_negative_zero_normalized: function() {
|
||||
var nz = -0
|
||||
if (nz != 0) throw "-0 should equal 0"
|
||||
var mul_nz = 0 * -1
|
||||
if (mul_nz != 0) throw "0 * -1 should be 0"
|
||||
var neg_zero = -(0)
|
||||
if (neg_zero != 0) throw "-(0) should be 0"
|
||||
},
|
||||
|
||||
test_overflow_is_null: function() {
|
||||
var result = 1e38 * 1e38
|
||||
if (result != null) throw "overflow should give null"
|
||||
},
|
||||
|
||||
test_modulo_by_zero_is_null: function() {
|
||||
var result = 5 % 0
|
||||
if (result != null) throw "modulo by zero should give null"
|
||||
},
|
||||
|
||||
// ============================================================================
|
||||
@@ -3506,4 +3526,158 @@ return {
|
||||
if (obj.beta[1] != obj) throw "text key cycle failed"
|
||||
},
|
||||
|
||||
// ============================================================================
|
||||
// OBJECT INTRINSIC TESTS
|
||||
// ============================================================================
|
||||
|
||||
test_object_shallow_copy: function() {
|
||||
var orig = {a: 1, b: 2, c: 3}
|
||||
var copy = object(orig)
|
||||
if (copy.a != 1) throw "object copy a failed"
|
||||
if (copy.b != 2) throw "object copy b failed"
|
||||
if (copy.c != 3) throw "object copy c failed"
|
||||
copy.a = 99
|
||||
if (orig.a != 1) throw "object copy should not mutate original"
|
||||
},
|
||||
|
||||
test_object_combine: function() {
|
||||
var obj1 = {a: 1, b: 2}
|
||||
var obj2 = {c: 3, d: 4}
|
||||
var combined = object(obj1, obj2)
|
||||
if (combined.a != 1) throw "object combine a failed"
|
||||
if (combined.b != 2) throw "object combine b failed"
|
||||
if (combined.c != 3) throw "object combine c failed"
|
||||
if (combined.d != 4) throw "object combine d failed"
|
||||
},
|
||||
|
||||
test_object_combine_override: function() {
|
||||
var obj1 = {a: 1, b: 2}
|
||||
var obj2 = {b: 99, c: 3}
|
||||
var combined = object(obj1, obj2)
|
||||
if (combined.a != 1) throw "object combine override a failed"
|
||||
if (combined.b != 99) throw "object combine should override with second arg"
|
||||
if (combined.c != 3) throw "object combine override c failed"
|
||||
},
|
||||
|
||||
test_object_select_keys: function() {
|
||||
var orig = {a: 1, b: 2, c: 3, d: 4}
|
||||
var selected = object(orig, ["a", "c"])
|
||||
if (selected.a != 1) throw "object select a failed"
|
||||
if (selected.c != 3) throw "object select c failed"
|
||||
if (selected.b != null) throw "object select should not include b"
|
||||
if (selected.d != null) throw "object select should not include d"
|
||||
},
|
||||
|
||||
test_object_from_keys_true: function() {
|
||||
var keys = ["x", "y", "z"]
|
||||
var obj = object(keys)
|
||||
if (obj.x != true) throw "object from keys x failed"
|
||||
if (obj.y != true) throw "object from keys y failed"
|
||||
if (obj.z != true) throw "object from keys z failed"
|
||||
},
|
||||
|
||||
test_object_from_keys_function: function() {
|
||||
var keys = ["a", "b", "c"]
|
||||
var obj = object(keys, function(k) { return k + "_val" })
|
||||
if (obj.a != "a_val") throw "object from keys func a failed"
|
||||
if (obj.b != "b_val") throw "object from keys func b failed"
|
||||
if (obj.c != "c_val") throw "object from keys func c failed"
|
||||
},
|
||||
|
||||
// ============================================================================
|
||||
// SPLAT INTRINSIC TESTS
|
||||
// ============================================================================
|
||||
|
||||
test_splat_prototype_flattening: function() {
|
||||
var proto = {x: 10, y: 20}
|
||||
var obj = {z: 30}
|
||||
obj.__proto__ = proto
|
||||
var flat = splat(obj)
|
||||
if (flat.x != 10) throw "splat x failed"
|
||||
if (flat.y != 20) throw "splat y failed"
|
||||
if (flat.z != 30) throw "splat z failed"
|
||||
},
|
||||
|
||||
// ============================================================================
|
||||
// REVERSE INTRINSIC TESTS
|
||||
// ============================================================================
|
||||
|
||||
test_reverse_array: function() {
|
||||
var arr = [1, 2, 3, 4, 5]
|
||||
var rev = reverse(arr)
|
||||
if (rev[0] != 5) throw "reverse[0] failed"
|
||||
if (rev[1] != 4) throw "reverse[1] failed"
|
||||
if (rev[2] != 3) throw "reverse[2] failed"
|
||||
if (rev[3] != 2) throw "reverse[3] failed"
|
||||
if (rev[4] != 1) throw "reverse[4] failed"
|
||||
if (arr[0] != 1) throw "reverse should not mutate original"
|
||||
},
|
||||
|
||||
// ============================================================================
|
||||
// APPLY INTRINSIC TESTS
|
||||
// ============================================================================
|
||||
|
||||
test_apply_with_array_args: function() {
|
||||
def sum = function(a, b, c) { return a + b + c }
|
||||
var result = fn.apply(sum, [1, 2, 3])
|
||||
if (result != 6) throw "apply with array args failed"
|
||||
},
|
||||
|
||||
test_apply_with_no_args: function() {
|
||||
def ret42 = function() { return 42 }
|
||||
var result = fn.apply(ret42)
|
||||
if (result != 42) throw "apply with no args failed"
|
||||
},
|
||||
|
||||
test_apply_with_single_value: function() {
|
||||
def double = function(x) { return x * 2 }
|
||||
var result = fn.apply(double, 10)
|
||||
if (result != 20) throw "apply with single value failed"
|
||||
},
|
||||
|
||||
// ============================================================================
|
||||
// GC STRESS TESTS FOR FIXED INTRINSICS
|
||||
// ============================================================================
|
||||
|
||||
test_gc_reverse_under_pressure: function() {
|
||||
// Create GC pressure by making many arrays, then reverse
|
||||
var arrays = []
|
||||
for (var i = 0; i < 100; i = i + 1) {
|
||||
arrays[i] = [i, i+1, i+2, i+3, i+4]
|
||||
}
|
||||
// Now reverse each one - this tests re-chase after allocation
|
||||
for (var i = 0; i < 100; i = i + 1) {
|
||||
var rev = reverse(arrays[i])
|
||||
if (rev[0] != i+4) throw "gc reverse stress failed at " + text(i)
|
||||
}
|
||||
},
|
||||
|
||||
test_gc_object_select_under_pressure: function() {
|
||||
// Create GC pressure
|
||||
var objs = []
|
||||
for (var i = 0; i < 100; i = i + 1) {
|
||||
objs[i] = {a: i, b: i+1, c: i+2, d: i+3}
|
||||
}
|
||||
// Select keys - tests re-chase in loop
|
||||
for (var i = 0; i < 100; i = i + 1) {
|
||||
var selected = object(objs[i], ["a", "c"])
|
||||
if (selected.a != i) throw "gc object select stress failed at " + text(i)
|
||||
if (selected.c != i+2) throw "gc object select stress c failed at " + text(i)
|
||||
}
|
||||
},
|
||||
|
||||
test_gc_object_from_keys_function_under_pressure: function() {
|
||||
// Create GC pressure
|
||||
var keysets = []
|
||||
for (var i = 0; i < 50; i = i + 1) {
|
||||
keysets[i] = ["k" + text(i), "j" + text(i), "m" + text(i)]
|
||||
}
|
||||
// Create objects with function - tests JS_PUSH/POP and re-chase
|
||||
for (var i = 0; i < 50; i = i + 1) {
|
||||
var obj = object(keysets[i], function(k) { return k + "_value" })
|
||||
var expected = "k" + text(i) + "_value"
|
||||
if (obj["k" + text(i)] != expected) throw "gc object from keys func stress failed at " + text(i)
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user