Merge branch 'mach' into pitweb
This commit is contained in:
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.mach binary merge=ours
|
||||
Binary file not shown.
9
parse.cm
9
parse.cm
@@ -391,7 +391,7 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
ast_node_end(param)
|
||||
if (tok.kind == "=" || tok.kind == "|") {
|
||||
advance()
|
||||
param.expression = parse_expr()
|
||||
param.expression = parse_assign_expr()
|
||||
}
|
||||
push(params, param)
|
||||
} else {
|
||||
@@ -426,7 +426,12 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
function_nr = function_nr + 1
|
||||
ast_node_end(fn)
|
||||
pair.right = fn
|
||||
} else if (!(is_ident && (tok.kind == "," || tok.kind == "}"))) {
|
||||
} else if (is_ident && (tok.kind == "," || tok.kind == "}")) {
|
||||
right = ast_node("name", pair.left)
|
||||
right.name = pair.left.name
|
||||
ast_node_end(right)
|
||||
pair.right = right
|
||||
} else {
|
||||
parse_error(tok, "expected ':' after property name")
|
||||
}
|
||||
push(list, pair)
|
||||
|
||||
BIN
parse.mach
BIN
parse.mach
Binary file not shown.
171
source/mach.c
171
source/mach.c
@@ -93,6 +93,16 @@ typedef struct MachCompState {
|
||||
int loop_break; /* instruction index to patch, or -1 */
|
||||
int loop_continue; /* instruction index to patch, or -1 */
|
||||
|
||||
/* Named label stack for labeled break/continue */
|
||||
#define MACH_MAX_LABELS 8
|
||||
struct {
|
||||
const char *name;
|
||||
int break_chain;
|
||||
int continue_chain;
|
||||
} labels[MACH_MAX_LABELS];
|
||||
int label_count;
|
||||
int pending_label; /* label index associated with next loop, or -1 */
|
||||
|
||||
/* Parent for nested function compilation */
|
||||
struct MachCompState *parent;
|
||||
|
||||
@@ -1160,6 +1170,7 @@ static int mach_compile_expr(MachCompState *cs, cJSON *node, int dest) {
|
||||
child.scopes = cs->scopes;
|
||||
child.filename = cs->filename;
|
||||
child.freereg = 1; /* slot 0 = this */
|
||||
child.pending_label = -1;
|
||||
|
||||
/* Read function_nr from AST node */
|
||||
cJSON *fn_nr_node = cJSON_GetObjectItemCaseSensitive(node, "function_nr");
|
||||
@@ -1513,6 +1524,9 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) {
|
||||
cJSON *cond = cJSON_GetObjectItemCaseSensitive(stmt, "expression");
|
||||
if (!cond) cond = cJSON_GetObjectItemCaseSensitive(stmt, "condition");
|
||||
|
||||
int my_label = cs->pending_label;
|
||||
cs->pending_label = -1;
|
||||
|
||||
int old_break = cs->loop_break;
|
||||
int old_continue = cs->loop_continue;
|
||||
cs->loop_break = -1;
|
||||
@@ -1550,6 +1564,17 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) {
|
||||
cs->code[cp] = MACH_sJ(MACH_JMP, off);
|
||||
cp = prev;
|
||||
}
|
||||
/* Also patch labeled continue chain */
|
||||
if (my_label >= 0) {
|
||||
cp = cs->labels[my_label].continue_chain;
|
||||
while (cp >= 0) {
|
||||
int prev = MACH_GET_sJ(cs->code[cp]);
|
||||
int off = loop_top - (cp + 1);
|
||||
cs->code[cp] = MACH_sJ(MACH_JMP, off);
|
||||
cp = prev;
|
||||
}
|
||||
cs->labels[my_label].continue_chain = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Jump back to loop top */
|
||||
@@ -1573,6 +1598,87 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Do-while loop */
|
||||
if (strcmp(kind, "do") == 0) {
|
||||
cJSON *cond = cJSON_GetObjectItemCaseSensitive(stmt, "expression");
|
||||
if (!cond) cond = cJSON_GetObjectItemCaseSensitive(stmt, "condition");
|
||||
|
||||
int my_label = cs->pending_label;
|
||||
cs->pending_label = -1;
|
||||
|
||||
int old_break = cs->loop_break;
|
||||
int old_continue = cs->loop_continue;
|
||||
cs->loop_break = -1;
|
||||
cs->loop_continue = -1;
|
||||
|
||||
int body_top = mach_current_pc(cs);
|
||||
|
||||
/* Compile body first (always executes at least once) */
|
||||
{
|
||||
cJSON *body = cJSON_GetObjectItemCaseSensitive(stmt, "block");
|
||||
if (!body) body = cJSON_GetObjectItemCaseSensitive(stmt, "body");
|
||||
cJSON *stmts = body ? cJSON_GetObjectItemCaseSensitive(body, "statements") : NULL;
|
||||
if (!stmts) stmts = cJSON_GetObjectItemCaseSensitive(stmt, "statements");
|
||||
if (stmts && cJSON_IsArray(stmts)) {
|
||||
int count = cJSON_GetArraySize(stmts);
|
||||
for (int i = 0; i < count; i++)
|
||||
mach_compile_stmt(cs, cJSON_GetArrayItem(stmts, i));
|
||||
} else if (body) {
|
||||
mach_compile_stmt(cs, body);
|
||||
}
|
||||
}
|
||||
|
||||
/* Patch continue chain to condition check */
|
||||
int cond_pc = mach_current_pc(cs);
|
||||
{
|
||||
int cp = cs->loop_continue;
|
||||
while (cp >= 0) {
|
||||
int prev = MACH_GET_sJ(cs->code[cp]);
|
||||
int off = cond_pc - (cp + 1);
|
||||
cs->code[cp] = MACH_sJ(MACH_JMP, off);
|
||||
cp = prev;
|
||||
}
|
||||
/* Also patch labeled continue chain */
|
||||
if (my_label >= 0) {
|
||||
cp = cs->labels[my_label].continue_chain;
|
||||
while (cp >= 0) {
|
||||
int prev = MACH_GET_sJ(cs->code[cp]);
|
||||
int off = cond_pc - (cp + 1);
|
||||
cs->code[cp] = MACH_sJ(MACH_JMP, off);
|
||||
cp = prev;
|
||||
}
|
||||
cs->labels[my_label].continue_chain = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Condition check — jump back to body if true */
|
||||
int save = cs->freereg;
|
||||
int cr = mach_compile_expr(cs, cond, -1);
|
||||
int jmpfalse_pc = mach_current_pc(cs);
|
||||
mach_emit(cs, MACH_AsBx(MACH_JMPFALSE, cr, 0));
|
||||
mach_free_reg_to(cs, save);
|
||||
|
||||
/* Jump back to body top */
|
||||
int offset = body_top - (mach_current_pc(cs) + 1);
|
||||
mach_emit(cs, MACH_sJ(MACH_JMP, offset));
|
||||
|
||||
/* Patch jmpfalse to after loop */
|
||||
offset = mach_current_pc(cs) - (jmpfalse_pc + 1);
|
||||
cs->code[jmpfalse_pc] = MACH_AsBx(MACH_JMPFALSE, cr, (int16_t)offset);
|
||||
|
||||
/* Patch break chain */
|
||||
int bp = cs->loop_break;
|
||||
while (bp >= 0) {
|
||||
int prev = MACH_GET_sJ(cs->code[bp]);
|
||||
offset = mach_current_pc(cs) - (bp + 1);
|
||||
cs->code[bp] = MACH_sJ(MACH_JMP, offset);
|
||||
bp = prev;
|
||||
}
|
||||
cs->loop_break = old_break;
|
||||
cs->loop_continue = old_continue;
|
||||
return;
|
||||
}
|
||||
|
||||
/* For loop */
|
||||
if (strcmp(kind, "for") == 0) {
|
||||
cJSON *init = cJSON_GetObjectItemCaseSensitive(stmt, "init");
|
||||
@@ -1581,6 +1687,9 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) {
|
||||
cJSON *body = cJSON_GetObjectItemCaseSensitive(stmt, "block");
|
||||
if (!body) body = cJSON_GetObjectItemCaseSensitive(stmt, "body");
|
||||
|
||||
int my_label = cs->pending_label;
|
||||
cs->pending_label = -1;
|
||||
|
||||
int old_break = cs->loop_break;
|
||||
int old_continue = cs->loop_continue;
|
||||
cs->loop_break = -1;
|
||||
@@ -1624,6 +1733,17 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) {
|
||||
cs->code[cp] = MACH_sJ(MACH_JMP, off);
|
||||
cp = prev;
|
||||
}
|
||||
/* Also patch labeled continue chain */
|
||||
if (my_label >= 0) {
|
||||
cp = cs->labels[my_label].continue_chain;
|
||||
while (cp >= 0) {
|
||||
int prev = MACH_GET_sJ(cs->code[cp]);
|
||||
int off = continue_target - (cp + 1);
|
||||
cs->code[cp] = MACH_sJ(MACH_JMP, off);
|
||||
cp = prev;
|
||||
}
|
||||
cs->labels[my_label].continue_chain = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update — assignment expressions must be compiled as statements */
|
||||
@@ -1656,8 +1776,46 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Label statement */
|
||||
if (strcmp(kind, "label") == 0) {
|
||||
const char *name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(stmt, "name"));
|
||||
cJSON *inner = cJSON_GetObjectItemCaseSensitive(stmt, "statement");
|
||||
if (inner && cs->label_count < MACH_MAX_LABELS) {
|
||||
int li = cs->label_count++;
|
||||
cs->labels[li].name = name;
|
||||
cs->labels[li].break_chain = -1;
|
||||
cs->labels[li].continue_chain = -1;
|
||||
cs->pending_label = li;
|
||||
mach_compile_stmt(cs, inner);
|
||||
/* Patch labeled break chain to after the statement */
|
||||
int bp = cs->labels[li].break_chain;
|
||||
while (bp >= 0) {
|
||||
int prev = MACH_GET_sJ(cs->code[bp]);
|
||||
int offset = mach_current_pc(cs) - (bp + 1);
|
||||
cs->code[bp] = MACH_sJ(MACH_JMP, offset);
|
||||
bp = prev;
|
||||
}
|
||||
cs->label_count--;
|
||||
} else if (inner) {
|
||||
mach_compile_stmt(cs, inner);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Break */
|
||||
if (strcmp(kind, "break") == 0) {
|
||||
const char *name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(stmt, "name"));
|
||||
if (name) {
|
||||
/* Labeled break — find the label */
|
||||
for (int i = cs->label_count - 1; i >= 0; i--) {
|
||||
if (cs->labels[i].name && strcmp(cs->labels[i].name, name) == 0) {
|
||||
int pc = mach_current_pc(cs);
|
||||
mach_emit(cs, MACH_sJ(MACH_JMP, cs->labels[i].break_chain));
|
||||
cs->labels[i].break_chain = pc;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
int pc = mach_current_pc(cs);
|
||||
mach_emit(cs, MACH_sJ(MACH_JMP, cs->loop_break));
|
||||
cs->loop_break = pc;
|
||||
@@ -1666,6 +1824,18 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) {
|
||||
|
||||
/* Continue */
|
||||
if (strcmp(kind, "continue") == 0) {
|
||||
const char *name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(stmt, "name"));
|
||||
if (name) {
|
||||
/* Labeled continue — find the label */
|
||||
for (int i = cs->label_count - 1; i >= 0; i--) {
|
||||
if (cs->labels[i].name && strcmp(cs->labels[i].name, name) == 0) {
|
||||
int pc = mach_current_pc(cs);
|
||||
mach_emit(cs, MACH_sJ(MACH_JMP, cs->labels[i].continue_chain));
|
||||
cs->labels[i].continue_chain = pc;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
int pc = mach_current_pc(cs);
|
||||
mach_emit(cs, MACH_sJ(MACH_JMP, cs->loop_continue));
|
||||
cs->loop_continue = pc;
|
||||
@@ -1797,6 +1967,7 @@ MachCode *JS_CompileMachTree(cJSON *ast) {
|
||||
MachCompState cs = {0};
|
||||
cs.freereg = 1; /* slot 0 = this */
|
||||
cs.maxreg = 1;
|
||||
cs.pending_label = -1;
|
||||
|
||||
MachCode *code = mach_compile_program(&cs, ast);
|
||||
|
||||
|
||||
591
vm_suite.ce
591
vm_suite.ce
@@ -3671,6 +3671,597 @@ run("functino >>>! unsigned shift right", function() {
|
||||
assert_eq(>>>!(-1, 28), 15, ">>>! unsigned shift right")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// FUNCTION DEFAULT PARAMETERS
|
||||
// ============================================================================
|
||||
|
||||
run("default param basic", function() {
|
||||
var fn = function(a = 3, b = 5) { return a + b }
|
||||
assert_eq(fn(), 8, "both defaults")
|
||||
assert_eq(fn(10), 15, "first provided")
|
||||
assert_eq(fn(10, 20), 30, "both provided")
|
||||
})
|
||||
|
||||
run("default param null triggers default", function() {
|
||||
var fn = function(a = 99) { return a }
|
||||
assert_eq(fn(null), 99, "null should trigger default")
|
||||
assert_eq(fn(), 99, "missing should trigger default")
|
||||
assert_eq(fn(0), 0, "zero should not trigger default")
|
||||
assert_eq(fn(false), false, "false should not trigger default")
|
||||
assert_eq(fn(""), "", "empty string should not trigger default")
|
||||
})
|
||||
|
||||
run("default param pipe syntax", function() {
|
||||
var fn = function(a | 3, b | 5) { return a + b }
|
||||
assert_eq(fn(), 8, "pipe both defaults")
|
||||
assert_eq(fn(10), 15, "pipe first provided")
|
||||
assert_eq(fn(10, 20), 30, "pipe both provided")
|
||||
})
|
||||
|
||||
run("default param object literal", function() {
|
||||
var fn = function(a = {x: 1, y: 2}) { return a.x + a.y }
|
||||
assert_eq(fn(), 3, "object default")
|
||||
assert_eq(fn({x: 10, y: 20}), 30, "object provided")
|
||||
})
|
||||
|
||||
run("default param array literal", function() {
|
||||
var fn = function(a = [10, 20, 30]) { return a[1] }
|
||||
assert_eq(fn(), 20, "array default")
|
||||
assert_eq(fn([99]), null, "array provided")
|
||||
})
|
||||
|
||||
run("default param nested object", function() {
|
||||
var fn = function(a = {inner: {val: 42}}) { return a.inner.val }
|
||||
assert_eq(fn(), 42, "nested object default")
|
||||
})
|
||||
|
||||
run("default param expression", function() {
|
||||
var fn = function(a = 1 + 2, b = 3 * 4) { return a + b }
|
||||
assert_eq(fn(), 15, "expression defaults")
|
||||
})
|
||||
|
||||
run("default param negative number", function() {
|
||||
var fn = function(a = -1) { return a }
|
||||
assert_eq(fn(), -1, "negative default")
|
||||
})
|
||||
|
||||
run("default param function call", function() {
|
||||
var double = function(x) { return x * 2 }
|
||||
var fn = function(a = double(21)) { return a }
|
||||
assert_eq(fn(), 42, "function call default")
|
||||
})
|
||||
|
||||
run("default param function value", function() {
|
||||
var fn = function(f = function() { return 42 }) { return f() }
|
||||
assert_eq(fn(), 42, "function as default")
|
||||
})
|
||||
|
||||
run("default param arrow function", function() {
|
||||
var fn = function(f = (x) => x * 2) { return f(5) }
|
||||
assert_eq(fn(), 10, "arrow function as default")
|
||||
})
|
||||
|
||||
run("default param ternary in default", function() {
|
||||
var fn = function(a = true ? 10 : 20) { return a }
|
||||
assert_eq(fn(), 10, "ternary default")
|
||||
})
|
||||
|
||||
run("default param string with comma", function() {
|
||||
var fn = function(a = "hello, world") { return a }
|
||||
assert_eq(fn(), "hello, world", "string comma default")
|
||||
})
|
||||
|
||||
run("default param empty object", function() {
|
||||
var fn = function(a = {}) { return is_object(a) }
|
||||
assert_eq(fn(), true, "empty object default")
|
||||
})
|
||||
|
||||
run("default param empty array", function() {
|
||||
var fn = function(a = []) { return is_array(a) }
|
||||
assert_eq(fn(), true, "empty array default")
|
||||
})
|
||||
|
||||
run("default param multiple complex", function() {
|
||||
var fn = function(a = {k: 1}, b = [4, 5], c = "hi") {
|
||||
return text(a.k) + text(b[0]) + c
|
||||
}
|
||||
assert_eq(fn(), "14hi", "multiple complex defaults")
|
||||
assert_eq(fn({k: 9}), "94hi", "first overridden")
|
||||
assert_eq(fn({k: 9}, [7]), "97hi", "first two overridden")
|
||||
assert_eq(fn({k: 9}, [7], "yo"), "97yo", "all overridden")
|
||||
})
|
||||
|
||||
run("default param independent defaults", function() {
|
||||
var fn = function(a = 1, b = 2) { return a + b }
|
||||
assert_eq(fn(), 3, "both defaults")
|
||||
assert_eq(fn(10), 12, "first provided")
|
||||
})
|
||||
|
||||
run("default param arrow function syntax", function() {
|
||||
var fn = (a = 7, b = 8) => a + b
|
||||
assert_eq(fn(), 15, "arrow defaults")
|
||||
assert_eq(fn(100), 108, "arrow first provided")
|
||||
assert_eq(fn(100, 200), 300, "arrow both provided")
|
||||
})
|
||||
|
||||
run("default param single arrow", function() {
|
||||
var fn = (a = 42) => a
|
||||
assert_eq(fn(), 42, "single arrow default")
|
||||
assert_eq(fn(0), 0, "single arrow zero")
|
||||
})
|
||||
|
||||
run("default param method shorthand", function() {
|
||||
var obj = {
|
||||
calc(a = 10, b = 20) {
|
||||
return a + b
|
||||
}
|
||||
}
|
||||
assert_eq(obj.calc(), 30, "method defaults")
|
||||
assert_eq(obj.calc(1), 21, "method first provided")
|
||||
assert_eq(obj.calc(1, 2), 3, "method both provided")
|
||||
})
|
||||
|
||||
run("default param each fresh object", function() {
|
||||
var fn = function(a = {count: 0}) {
|
||||
a.count = a.count + 1
|
||||
return a.count
|
||||
}
|
||||
assert_eq(fn(), 1, "first call fresh object")
|
||||
assert_eq(fn(), 1, "second call fresh object")
|
||||
})
|
||||
|
||||
run("default param each fresh array", function() {
|
||||
var fn = function(a = []) {
|
||||
a[] = 1
|
||||
return length(a)
|
||||
}
|
||||
assert_eq(fn(), 1, "first call fresh array")
|
||||
assert_eq(fn(), 1, "second call fresh array")
|
||||
})
|
||||
|
||||
run("default param nested parens", function() {
|
||||
var fn = function(a = (1 + 2) * 3) { return a }
|
||||
assert_eq(fn(), 9, "nested parens default")
|
||||
})
|
||||
|
||||
run("default param closure over outer", function() {
|
||||
var base = 100
|
||||
var fn = function(a = base) { return a }
|
||||
assert_eq(fn(), 100, "closure over outer")
|
||||
base = 200
|
||||
assert_eq(fn(), 200, "closure sees updated outer")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// DO-WHILE LOOPS
|
||||
// ============================================================================
|
||||
|
||||
run("do-while basic", function() {
|
||||
var i = 0
|
||||
do {
|
||||
i = i + 1
|
||||
} while (i < 3)
|
||||
assert_eq(i, 3, "do-while counted to 3")
|
||||
})
|
||||
|
||||
run("do-while executes body once when false", function() {
|
||||
var count = 0
|
||||
do {
|
||||
count = count + 1
|
||||
} while (false)
|
||||
assert_eq(count, 1, "body ran once")
|
||||
})
|
||||
|
||||
run("do-while break", function() {
|
||||
var i = 0
|
||||
do {
|
||||
if (i == 2) break
|
||||
i = i + 1
|
||||
} while (i < 10)
|
||||
assert_eq(i, 2, "break at 2")
|
||||
})
|
||||
|
||||
run("do-while continue", function() {
|
||||
var sum = 0
|
||||
var j = 0
|
||||
do {
|
||||
j = j + 1
|
||||
if (j == 3) continue
|
||||
sum = sum + j
|
||||
} while (j < 5)
|
||||
assert_eq(sum, 12, "skip 3: 1+2+4+5")
|
||||
})
|
||||
|
||||
run("do-while nested", function() {
|
||||
var result = 0
|
||||
var i = 0
|
||||
var j = 0
|
||||
do {
|
||||
j = 0
|
||||
do {
|
||||
result = result + 1
|
||||
j = j + 1
|
||||
} while (j < 2)
|
||||
i = i + 1
|
||||
} while (i < 3)
|
||||
assert_eq(result, 6, "3 outer * 2 inner")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// LABELED BREAK AND CONTINUE
|
||||
// ============================================================================
|
||||
|
||||
run("labeled break exits outer for loop", function() {
|
||||
var result = 0
|
||||
var j = 0
|
||||
var k = 0
|
||||
outer: for (j = 0; j < 3; j = j + 1) {
|
||||
for (k = 0; k < 3; k = k + 1) {
|
||||
if (k == 1) break outer
|
||||
result = result + 1
|
||||
}
|
||||
}
|
||||
assert_eq(result, 1, "only one iteration before break outer")
|
||||
assert_eq(j, 0, "outer loop did not advance")
|
||||
})
|
||||
|
||||
run("labeled continue skips to outer iteration", function() {
|
||||
var result = 0
|
||||
var a = 0
|
||||
var b = 0
|
||||
outer: for (a = 0; a < 3; a = a + 1) {
|
||||
for (b = 0; b < 3; b = b + 1) {
|
||||
if (b == 1) continue outer
|
||||
result = result + 1
|
||||
}
|
||||
}
|
||||
assert_eq(result, 3, "3 outer iters, each runs b=0 only")
|
||||
})
|
||||
|
||||
run("labeled break from while", function() {
|
||||
var x = 0
|
||||
top: while (true) {
|
||||
x = x + 1
|
||||
if (x == 5) break top
|
||||
}
|
||||
assert_eq(x, 5, "broke out at 5")
|
||||
})
|
||||
|
||||
run("labeled break triple nested", function() {
|
||||
var r = 0
|
||||
var c = 0
|
||||
var d = 0
|
||||
var e = 0
|
||||
outer: for (c = 0; c < 3; c = c + 1) {
|
||||
for (d = 0; d < 3; d = d + 1) {
|
||||
for (e = 0; e < 3; e = e + 1) {
|
||||
if (e == 1) break outer
|
||||
r = r + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq(r, 1, "broke out from 3 levels")
|
||||
})
|
||||
|
||||
run("labeled continue with do-while", function() {
|
||||
var total = 0
|
||||
var i = 0
|
||||
var j = 0
|
||||
outer: for (i = 0; i < 3; i = i + 1) {
|
||||
j = 0
|
||||
do {
|
||||
if (j == 1) continue outer
|
||||
total = total + 1
|
||||
j = j + 1
|
||||
} while (j < 3)
|
||||
}
|
||||
assert_eq(total, 3, "3 outer iters, each does j=0 only")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// SHORTHAND PROPERTY SYNTAX
|
||||
// ============================================================================
|
||||
|
||||
run("shorthand property basic", function() {
|
||||
var name = "Alice"
|
||||
var age = 30
|
||||
var obj = {name, age}
|
||||
assert_eq(obj.name, "Alice", "name shorthand")
|
||||
assert_eq(obj.age, 30, "age shorthand")
|
||||
})
|
||||
|
||||
run("shorthand property mixed with regular", function() {
|
||||
var x = 10
|
||||
var obj = {x, y: 20}
|
||||
assert_eq(obj.x, 10, "shorthand x")
|
||||
assert_eq(obj.y, 20, "regular y")
|
||||
})
|
||||
|
||||
run("shorthand property with function value", function() {
|
||||
var greet = function() { return "hi" }
|
||||
var obj = {greet}
|
||||
assert_eq(obj.greet(), "hi", "shorthand fn")
|
||||
})
|
||||
|
||||
run("shorthand property single", function() {
|
||||
var val = 42
|
||||
var obj = {val}
|
||||
assert_eq(obj.val, 42, "single shorthand")
|
||||
})
|
||||
|
||||
run("shorthand property with null", function() {
|
||||
var x = null
|
||||
var obj = {x}
|
||||
assert_eq(obj.x, null, "null shorthand")
|
||||
})
|
||||
|
||||
run("shorthand property many", function() {
|
||||
var a = 1
|
||||
var b = 2
|
||||
var c = 3
|
||||
var d = 4
|
||||
var e = 5
|
||||
var f = 6
|
||||
var g = 7
|
||||
var h = 8
|
||||
var i = 9
|
||||
var j = 10
|
||||
var obj = {a, b, c, d, e, f, g, h, i, j}
|
||||
assert_eq(obj.a, 1, "a")
|
||||
assert_eq(obj.e, 5, "e")
|
||||
assert_eq(obj.j, 10, "j")
|
||||
})
|
||||
|
||||
run("shorthand property with method", function() {
|
||||
var z = 300
|
||||
var obj = {z, double(n) { return n * 2 }}
|
||||
assert_eq(obj.z, 300, "shorthand z")
|
||||
assert_eq(obj.double(5), 10, "method double")
|
||||
})
|
||||
|
||||
run("shorthand property is a snapshot", function() {
|
||||
var val = "original"
|
||||
var obj = {val}
|
||||
val = "changed"
|
||||
assert_eq(obj.val, "original", "object keeps original")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// SHARED CLOSURES
|
||||
// ============================================================================
|
||||
|
||||
run("closures share captured variable", function() {
|
||||
var x = 0
|
||||
var inc = function() { x = x + 1 }
|
||||
var get = function() { return x }
|
||||
inc()
|
||||
assert_eq(get(), 1, "after one inc")
|
||||
inc()
|
||||
assert_eq(get(), 2, "after two incs")
|
||||
})
|
||||
|
||||
run("closure factory returns shared state", function() {
|
||||
var make = function() {
|
||||
var count = 0
|
||||
return {
|
||||
inc: function() { count = count + 1 },
|
||||
get: function() { return count }
|
||||
}
|
||||
}
|
||||
var c = make()
|
||||
c.inc()
|
||||
c.inc()
|
||||
c.inc()
|
||||
assert_eq(c.get(), 3, "factory counter")
|
||||
})
|
||||
|
||||
run("closure set and get", function() {
|
||||
var make = function() {
|
||||
var val = null
|
||||
return {
|
||||
set: function(v) { val = v },
|
||||
get: function() { return val }
|
||||
}
|
||||
}
|
||||
var o = make()
|
||||
o.set("hello")
|
||||
assert_eq(o.get(), "hello", "set then get")
|
||||
o.set(42)
|
||||
assert_eq(o.get(), 42, "overwrite")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// STRING COMPARISON OPERATORS
|
||||
// ============================================================================
|
||||
|
||||
run("string less than", function() {
|
||||
assert_eq("apple" < "banana", true, "apple < banana")
|
||||
assert_eq("banana" < "apple", false, "banana < apple")
|
||||
assert_eq("abc" < "abd", true, "abc < abd")
|
||||
})
|
||||
|
||||
run("string greater than", function() {
|
||||
assert_eq("banana" > "apple", true, "banana > apple")
|
||||
assert_eq("apple" > "banana", false, "apple > banana")
|
||||
})
|
||||
|
||||
run("string less than or equal", function() {
|
||||
assert_eq("abc" <= "abc", true, "equal strings")
|
||||
assert_eq("abc" <= "abd", true, "abc <= abd")
|
||||
assert_eq("abd" <= "abc", false, "abd <= abc")
|
||||
})
|
||||
|
||||
run("string greater than or equal", function() {
|
||||
assert_eq("abc" >= "abc", true, "equal strings")
|
||||
assert_eq("abd" >= "abc", true, "abd >= abc")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// CROSS-TYPE STRICT EQUALITY
|
||||
// ============================================================================
|
||||
|
||||
run("strict equality different types", function() {
|
||||
assert_eq(5 == "5", false, "number != string")
|
||||
assert_eq(true == 1, false, "bool != number")
|
||||
assert_eq(false == 0, false, "false != zero")
|
||||
assert_eq(null == false, false, "null != false")
|
||||
assert_eq("" == false, false, "empty string != false")
|
||||
assert_eq(0 == null, false, "zero != null")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// COMPOUND ASSIGNMENT OPERATORS (&&= and ||=)
|
||||
// ============================================================================
|
||||
|
||||
run("logical and assign", function() {
|
||||
var x = true
|
||||
x &&= false
|
||||
assert_eq(x, false, "true &&= false")
|
||||
var y = false
|
||||
y &&= true
|
||||
assert_eq(y, false, "false &&= true")
|
||||
})
|
||||
|
||||
run("logical or assign", function() {
|
||||
var x = false
|
||||
x ||= true
|
||||
assert_eq(x, true, "false ||= true")
|
||||
var y = true
|
||||
y ||= false
|
||||
assert_eq(y, true, "true ||= false")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// EVERY/SOME ON EMPTY ARRAYS
|
||||
// ============================================================================
|
||||
|
||||
run("every on empty array", function() {
|
||||
var result = every([], function(x) { return false })
|
||||
assert_eq(result, true, "vacuous truth")
|
||||
})
|
||||
|
||||
run("some on empty array", function() {
|
||||
var result = some([], function(x) { return true })
|
||||
assert_eq(result, false, "no elements match")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// CHAINED METHOD CALLS
|
||||
// ============================================================================
|
||||
|
||||
run("chained method calls", function() {
|
||||
var make_chain = function() {
|
||||
var obj = {
|
||||
val: 0,
|
||||
add(n) {
|
||||
obj.val = obj.val + n
|
||||
return obj
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
var c = make_chain()
|
||||
var result = c.add(1).add(2).add(3).val
|
||||
assert_eq(result, 6, "1+2+3 chained")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// TEMPLATE LITERALS WITH EXPRESSIONS
|
||||
// ============================================================================
|
||||
|
||||
run("template literal arithmetic", function() {
|
||||
var a = 3
|
||||
var b = 4
|
||||
assert_eq(`${a + b}`, "7", "addition in template")
|
||||
})
|
||||
|
||||
run("template literal nested calls", function() {
|
||||
var double = function(x) { return x * 2 }
|
||||
assert_eq(`result: ${double(5)}`, "result: 10", "fn call in template")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// COMMA-SEPARATED DECLARATIONS
|
||||
// ============================================================================
|
||||
|
||||
run("comma separated var declarations", function() {
|
||||
var a = 1, b = 2, c = 3
|
||||
assert_eq(a, 1, "first")
|
||||
assert_eq(b, 2, "second")
|
||||
assert_eq(c, 3, "third")
|
||||
})
|
||||
|
||||
run("comma separated def declarations", function() {
|
||||
def x = 10, y = 20
|
||||
assert_eq(x, 10, "def first")
|
||||
assert_eq(y, 20, "def second")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// DEF CONSTANTS
|
||||
// ============================================================================
|
||||
|
||||
run("def prevents reassignment", function() {
|
||||
var caught = false
|
||||
def x = 42
|
||||
assert_eq(x, 42, "def value")
|
||||
// reassignment should disrupt
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// RECURSIVE CLOSURES
|
||||
// ============================================================================
|
||||
|
||||
run("recursive closure fibonacci", function() {
|
||||
var fib = function(n) {
|
||||
if (n <= 1) return n
|
||||
return fib(n - 1) + fib(n - 2)
|
||||
}
|
||||
assert_eq(fib(0), 0, "fib(0)")
|
||||
assert_eq(fib(1), 1, "fib(1)")
|
||||
assert_eq(fib(6), 8, "fib(6)")
|
||||
assert_eq(fib(10), 55, "fib(10)")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// KEYWORD PROPERTY NAMES
|
||||
// ============================================================================
|
||||
|
||||
run("keyword property names", function() {
|
||||
var obj = {if: 1, while: 2, return: 3}
|
||||
assert_eq(obj.if, 1, "if prop")
|
||||
assert_eq(obj.while, 2, "while prop")
|
||||
assert_eq(obj.return, 3, "return prop")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// NESTED RETURN FROM IF/ELSE
|
||||
// ============================================================================
|
||||
|
||||
run("nested return in if-else chain", function() {
|
||||
var classify = function(n) {
|
||||
if (n < 0) {
|
||||
return "negative"
|
||||
} else if (n == 0) {
|
||||
return "zero"
|
||||
} else {
|
||||
return "positive"
|
||||
}
|
||||
}
|
||||
assert_eq(classify(-5), "negative", "negative")
|
||||
assert_eq(classify(0), "zero", "zero")
|
||||
assert_eq(classify(10), "positive", "positive")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// IIFE (IMMEDIATELY INVOKED FUNCTION EXPRESSION)
|
||||
// ============================================================================
|
||||
|
||||
run("IIFE with arguments", function() {
|
||||
var result = (function(a, b) { return a + b })(10, 20)
|
||||
assert_eq(result, 30, "IIFE sum")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// SUMMARY
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user