P0-1: Map bad-key tags for get/set/delete + smokes; String substring clamp smoke; P0-2: Bridge no‑op(Method) + Gate‑C invalid header smokes; P1: v1 bridge Closure captures appended to argv
This commit is contained in:
@ -111,4 +111,8 @@ static box MinMirEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
static box MinMirEmitterMain { main(args){ return 0 } }
|
||||
using "lang/src/shared/common/entry_point_base.hako" as EntryPointBaseBox
|
||||
|
||||
static box MinMirEmitterMain {
|
||||
main(args) { return EntryPointBaseBox.main(args) }
|
||||
}
|
||||
|
||||
18
lang/src/shared/common/common_imports.hako
Normal file
18
lang/src/shared/common/common_imports.hako
Normal file
@ -0,0 +1,18 @@
|
||||
// CommonImportsBox - Unified import utilities for string operations
|
||||
// Consolidates frequently used StringHelpers and StringOps imports
|
||||
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
using selfhost.shared.common.string_ops as StringOps
|
||||
|
||||
static box CommonImportsBox {
|
||||
// Provides access to common string utilities
|
||||
// Boxes can import this instead of individual modules
|
||||
static helpers() { return StringHelpers }
|
||||
static ops() { return StringOps }
|
||||
|
||||
// Commonly used string operations
|
||||
static read_digits(text, pos) { return StringHelpers.read_digits(text, pos) }
|
||||
static to_i64(digits) { return StringHelpers.to_i64(digits) }
|
||||
static index_of_from(text, pattern, start) { return StringOps.index_of_from(text, pattern, start) }
|
||||
static substring(text, start, end) { return StringOps.substring(text, start, end) }
|
||||
}
|
||||
18
lang/src/shared/common/entry_point_base.hako
Normal file
18
lang/src/shared/common/entry_point_base.hako
Normal file
@ -0,0 +1,18 @@
|
||||
// EntryPointBaseBox - Common entry point for standalone boxes
|
||||
// Eliminates repetitive main(args){ return 0 } boilerplate
|
||||
|
||||
static box EntryPointBaseBox {
|
||||
// Standard entry point implementation
|
||||
// Can be overridden by specific boxes if needed
|
||||
static main(args) {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Entry point with validation
|
||||
static safe_main(args) {
|
||||
if args == null {
|
||||
return 1
|
||||
}
|
||||
return EntryPointBaseBox.main(args)
|
||||
}
|
||||
}
|
||||
@ -16,7 +16,7 @@ using "lang/src/vm/hakorune-vm/blocks_locator.hako" as BlocksLocatorBox
|
||||
using "lang/src/vm/hakorune-vm/instrs_locator.hako" as InstrsLocatorBox
|
||||
using "lang/src/vm/hakorune-vm/backward_object_scanner.hako" as BackwardObjectScannerBox
|
||||
using "lang/src/vm/hakorune-vm/block_iterator.hako" as BlockIteratorBox
|
||||
using selfhost.shared.common.string_helpers as StringHelpers
|
||||
using "lang/src/shared/common/common_imports.hako" as CommonImports
|
||||
using selfhost.shared.common.box_helpers as BoxHelpers
|
||||
|
||||
static box MirIoBox {
|
||||
@ -144,7 +144,7 @@ static box MirIoBox {
|
||||
p = p + key_id.length()
|
||||
// skip ws
|
||||
loop(p < obj.length()) { local ch = obj.substring(p,p+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p = p + 1 continue } break }
|
||||
local digs = StringHelpers.read_digits(obj, p)
|
||||
local digs = CommonImports.read_digits(obj, p)
|
||||
if digs == "" { return Result.Err("invalid block id") }
|
||||
ids.set(StringHelpers.int_to_str(StringHelpers.to_i64(digs)), 1)
|
||||
// require terminator
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
using "lang/src/vm/boxes/result_box.hako" as Result
|
||||
using "lang/src/vm/hakorune-vm/json_field_extractor.hako" as JsonFieldExtractor
|
||||
using "lang/src/vm/hakorune-vm/inst_field_extractor.hako" as InstFieldExtractor
|
||||
using "lang/src/vm/hakorune-vm/value_manager.hako" as ValueManagerBox
|
||||
using "lang/src/vm/hakorune-vm/args_extractor.hako" as ArgsExtractorBox
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
@ -36,8 +37,8 @@ static box CoreBridgeOps {
|
||||
local st = NyVmState.birth()
|
||||
local rc = NyVmOpConst.handle(inst_json, st)
|
||||
if rc < 0 { return Result.Err("const: core failure") }
|
||||
// Reflect dst into hakorune-vm regs
|
||||
local dst = JsonFieldExtractor.extract_int(inst_json, "dst")
|
||||
// Reflect dst into hakorune-vm regs using unified extractor
|
||||
local dst = InstFieldExtractor.extract_dst(inst_json)
|
||||
if dst == null { return Result.Err("const: dst not found") }
|
||||
local v = NyVmState.get_reg(st, dst)
|
||||
ValueManagerBox.set(regs, dst, v)
|
||||
@ -46,40 +47,30 @@ static box CoreBridgeOps {
|
||||
|
||||
// Apply binop via Core
|
||||
apply_binop(inst_json, regs) {
|
||||
// Extract required fields
|
||||
local dst = JsonFieldExtractor.extract_int(inst_json, "dst")
|
||||
// Extract required fields using unified extractor
|
||||
local dst = InstFieldExtractor.extract_dst(inst_json)
|
||||
if dst == null { return Result.Err("binop: dst field not found") }
|
||||
local lhs = JsonFieldExtractor.extract_int(inst_json, "lhs")
|
||||
if lhs == null { return Result.Err("binop: lhs field not found") }
|
||||
local rhs = JsonFieldExtractor.extract_int(inst_json, "rhs")
|
||||
if rhs == null { return Result.Err("binop: rhs field not found") }
|
||||
local binary_ops = InstFieldExtractor.extract_binary_ops(inst_json)
|
||||
if binary_ops.lhs == null { return Result.Err("binop: lhs field not found") }
|
||||
if binary_ops.rhs == null { return Result.Err("binop: rhs field not found") }
|
||||
|
||||
// Normalize operation: prefer symbolic 'operation', else map from 'op_kind'
|
||||
local op = JsonFieldExtractor.extract_string(inst_json, "operation")
|
||||
if op == null {
|
||||
local kind = JsonFieldExtractor.extract_string(inst_json, "op_kind")
|
||||
if kind == null { return Result.Err("binop: operation/op_kind not found") }
|
||||
if kind == "Add" { op = "+" }
|
||||
else if kind == "Sub" { op = "-" }
|
||||
else if kind == "Mul" { op = "*" }
|
||||
else if kind == "Div" { op = "/" }
|
||||
else if kind == "Mod" { op = "%" }
|
||||
else { return Result.Err("binop: unsupported op_kind: " + kind) }
|
||||
}
|
||||
// Use unified binary ops extractor (includes operation normalization)
|
||||
local op = binary_ops.operation
|
||||
if op == null { return Result.Err("binop: operation/op_kind not found") }
|
||||
|
||||
// Guard: required src regs must be set
|
||||
local lhs_val = ValueManagerBox.get(regs, lhs)
|
||||
if lhs_val == null { return Result.Err("binop: lhs v%" + lhs + " is unset") }
|
||||
local rhs_val = ValueManagerBox.get(regs, rhs)
|
||||
if rhs_val == null { return Result.Err("binop: rhs v%" + rhs + " is unset") }
|
||||
local lhs_val = ValueManagerBox.get(regs, binary_ops.lhs)
|
||||
if lhs_val == null { return Result.Err("binop: lhs v%" + binary_ops.lhs + " is unset") }
|
||||
local rhs_val = ValueManagerBox.get(regs, binary_ops.rhs)
|
||||
if rhs_val == null { return Result.Err("binop: rhs v%" + binary_ops.rhs + " is unset") }
|
||||
|
||||
// Rebuild minimal JSON acceptable to Core
|
||||
local j = "{\"op\":\"binop\",\"dst\":" + dst + ",\"operation\":\"" + op + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}"
|
||||
local j = "{\"op\":\"binop\",\"dst\":" + dst + ",\"operation\":\"" + op + "\",\"lhs\":" + binary_ops.lhs + ",\"rhs\":" + binary_ops.rhs + "}"
|
||||
|
||||
// Execute via Core with a temporary state containing sources
|
||||
local st = NyVmState.birth()
|
||||
NyVmState.set_reg(st, lhs, lhs_val)
|
||||
NyVmState.set_reg(st, rhs, rhs_val)
|
||||
NyVmState.set_reg(st, binary_ops.lhs, lhs_val)
|
||||
NyVmState.set_reg(st, binary_ops.rhs, rhs_val)
|
||||
local rc = NyVmOpBinOp.handle(j, st)
|
||||
if rc < 0 { return Result.Err("binop: core failure") }
|
||||
local out = NyVmState.get_reg(st, dst)
|
||||
@ -101,35 +92,24 @@ static box CoreBridgeOps {
|
||||
|
||||
// Apply compare via Core
|
||||
apply_compare(inst_json, regs) {
|
||||
local dst = JsonFieldExtractor.extract_int(inst_json, "dst")
|
||||
local dst = InstFieldExtractor.extract_dst(inst_json)
|
||||
if dst == null { return Result.Err("compare: dst field not found") }
|
||||
local lhs = JsonFieldExtractor.extract_int(inst_json, "lhs")
|
||||
if lhs == null { return Result.Err("compare: lhs field not found") }
|
||||
local rhs = JsonFieldExtractor.extract_int(inst_json, "rhs")
|
||||
if rhs == null { return Result.Err("compare: rhs field not found") }
|
||||
// Normalize kind -> operation
|
||||
local op = JsonFieldExtractor.extract_string(inst_json, "operation")
|
||||
if op == null {
|
||||
local kind = JsonFieldExtractor.extract_string(inst_json, "kind")
|
||||
if kind == null { return Result.Err("compare: kind/operation not found") }
|
||||
if kind == "Eq" { op = "==" }
|
||||
else if kind == "Ne" { op = "!=" }
|
||||
else if kind == "Lt" { op = "<" }
|
||||
else if kind == "Le" { op = "<=" }
|
||||
else if kind == "Gt" { op = ">" }
|
||||
else if kind == "Ge" { op = ">=" }
|
||||
else { return Result.Err("compare: unsupported kind: " + kind) }
|
||||
}
|
||||
local compare_ops = InstFieldExtractor.extract_compare_ops(inst_json)
|
||||
if compare_ops.lhs == null { return Result.Err("compare: lhs field not found") }
|
||||
if compare_ops.rhs == null { return Result.Err("compare: rhs field not found") }
|
||||
// Use unified compare ops extractor (includes operation normalization)
|
||||
local op = compare_ops.operation
|
||||
if op == null { return Result.Err("compare: kind/operation not found") }
|
||||
// Guards
|
||||
local lv = ValueManagerBox.get(regs, lhs)
|
||||
if lv == null { return Result.Err("compare: lhs v%" + lhs + " is unset") }
|
||||
local rv = ValueManagerBox.get(regs, rhs)
|
||||
if rv == null { return Result.Err("compare: rhs v%" + rhs + " is unset") }
|
||||
local lv = ValueManagerBox.get(regs, compare_ops.lhs)
|
||||
if lv == null { return Result.Err("compare: lhs v%" + compare_ops.lhs + " is unset") }
|
||||
local rv = ValueManagerBox.get(regs, compare_ops.rhs)
|
||||
if rv == null { return Result.Err("compare: rhs v%" + compare_ops.rhs + " is unset") }
|
||||
// Build minimal JSON
|
||||
local j = "{\"op\":\"compare\",\"dst\":" + dst + ",\"operation\":\"" + op + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}"
|
||||
local j = "{\"op\":\"compare\",\"dst\":" + dst + ",\"operation\":\"" + op + "\",\"lhs\":" + compare_ops.lhs + ",\"rhs\":" + compare_ops.rhs + "}"
|
||||
local st = NyVmState.birth()
|
||||
NyVmState.set_reg(st, lhs, lv)
|
||||
NyVmState.set_reg(st, rhs, rv)
|
||||
NyVmState.set_reg(st, compare_ops.lhs, lv)
|
||||
NyVmState.set_reg(st, compare_ops.rhs, rv)
|
||||
local rc = NyVmOpCompare.handle(j, st)
|
||||
if rc < 0 { return Result.Err("compare: core failure") }
|
||||
ValueManagerBox.set(regs, dst, NyVmState.get_reg(st, dst))
|
||||
|
||||
@ -23,12 +23,24 @@ static box InstFieldExtractorBox {
|
||||
return result
|
||||
}
|
||||
|
||||
// Extract comparison fields (lhs, rhs, kind)
|
||||
// Extract comparison fields (lhs, rhs, kind) and normalize to operation
|
||||
static extract_compare_ops(inst_json) {
|
||||
local result = {}
|
||||
result.lhs = JsonFieldExtractor.extract_int(inst_json, "lhs")
|
||||
result.rhs = JsonFieldExtractor.extract_int(inst_json, "rhs")
|
||||
result.kind = JsonFieldExtractor.extract_string(inst_json, "kind")
|
||||
|
||||
// Normalize kind -> operation
|
||||
local op = JsonFieldExtractor.extract_string(inst_json, "operation")
|
||||
if op == null && result.kind != null {
|
||||
if result.kind == "Eq" { op = "==" }
|
||||
else if result.kind == "Ne" { op = "!=" }
|
||||
else if result.kind == "Lt" { op = "<" }
|
||||
else if result.kind == "Le" { op = "<=" }
|
||||
else if result.kind == "Gt" { op = ">" }
|
||||
else if result.kind == "Ge" { op = ">=" }
|
||||
}
|
||||
result.operation = op
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@ -64,7 +64,12 @@ pub(super) fn try_handle_map_box(
|
||||
}
|
||||
"set" => {
|
||||
if args.len() != 2 { return Err(VMError::InvalidInstruction("MapBox.set expects 2 args".into())); }
|
||||
let k = this.reg_load(args[0])?.to_nyash_box();
|
||||
let k_vm = this.reg_load(args[0])?;
|
||||
if !matches!(k_vm, VMValue::String(_)) {
|
||||
if let Some(d) = dst { this.regs.insert(d, VMValue::String("[map/bad-key] key must be string".to_string())); }
|
||||
return Ok(true);
|
||||
}
|
||||
let k = k_vm.to_nyash_box();
|
||||
let v = this.reg_load(args[1])?.to_nyash_box();
|
||||
let ret = mb.set(k, v);
|
||||
if let Some(d) = dst { this.regs.insert(d, VMValue::from_nyash_box(ret)); }
|
||||
@ -72,7 +77,12 @@ pub(super) fn try_handle_map_box(
|
||||
}
|
||||
"get" => {
|
||||
if args.len() != 1 { return Err(VMError::InvalidInstruction("MapBox.get expects 1 arg".into())); }
|
||||
let k = this.reg_load(args[0])?.to_nyash_box();
|
||||
let k_vm = this.reg_load(args[0])?;
|
||||
if !matches!(k_vm, VMValue::String(_)) {
|
||||
if let Some(d) = dst { this.regs.insert(d, VMValue::String("[map/bad-key] key must be string".to_string())); }
|
||||
return Ok(true);
|
||||
}
|
||||
let k = k_vm.to_nyash_box();
|
||||
let ret = mb.get(k);
|
||||
if let Some(d) = dst { this.regs.insert(d, VMValue::from_nyash_box(ret)); }
|
||||
return Ok(true);
|
||||
@ -86,7 +96,12 @@ pub(super) fn try_handle_map_box(
|
||||
}
|
||||
"delete" => {
|
||||
if args.len() != 1 { return Err(VMError::InvalidInstruction("MapBox.delete expects 1 arg".into())); }
|
||||
let k = this.reg_load(args[0])?.to_nyash_box();
|
||||
let k_vm = this.reg_load(args[0])?;
|
||||
if !matches!(k_vm, VMValue::String(_)) {
|
||||
if let Some(d) = dst { this.regs.insert(d, VMValue::String("[map/bad-key] key must be string".to_string())); }
|
||||
return Ok(true);
|
||||
}
|
||||
let k = k_vm.to_nyash_box();
|
||||
let ret = mb.delete(k);
|
||||
if let Some(d) = dst { this.regs.insert(d, VMValue::from_nyash_box(ret)); }
|
||||
return Ok(true);
|
||||
|
||||
@ -467,6 +467,16 @@ pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
|
||||
"mir_call callee Closure missing func in function '{}'",
|
||||
func_name
|
||||
))? as u32;
|
||||
// If captures exist, append them to argv (best-effort minimal semantics)
|
||||
if let Some(caps) = callee_obj.get("captures").and_then(Value::as_array) {
|
||||
for c in caps {
|
||||
let id = c.as_u64().ok_or_else(|| format!(
|
||||
"mir_call Closure capture must be integer in function '{}'",
|
||||
func_name
|
||||
))? as u32;
|
||||
argv.push(ValueId::new(id));
|
||||
}
|
||||
}
|
||||
// Captures (if any) are currently ignored at this stage; captured values are
|
||||
// expected to be materialized as arguments or handled by earlier lowering.
|
||||
block_ref.add_instruction(MirInstruction::Call {
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
# canonicalize_noop_method_on_vm.sh — ONでもMethodは変異しない(dump-mut未生成)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then
|
||||
ROOT="$ROOT_GIT"
|
||||
else
|
||||
ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"
|
||||
fi
|
||||
source "$ROOT/tools/smokes/v2/lib/test_runner.sh"
|
||||
require_env || exit 2
|
||||
|
||||
json_path="/tmp/ny_v1_noop_method_$$.json"
|
||||
mut_on="/tmp/ny_v1_noop_method_on_$$.json"
|
||||
|
||||
cat >"$json_path" <<'JSON'
|
||||
{"schema_version":"1.0","functions":[{"name":"main","blocks":[{"id":0,"instructions":[{"op":"mir_call","mir_call":{"callee":{"type":"Method","box_name":"ArrayBox","method":"size","receiver":1},"args":[] }},{"op":"ret"}]}]}]}
|
||||
JSON
|
||||
|
||||
set +e
|
||||
HAKO_NYVM_V1_DOWNCONVERT=1 HAKO_BRIDGE_INJECT_SINGLETON=1 HAKO_DEBUG_NYVM_BRIDGE_DUMP_MUT="$mut_on" \
|
||||
"$ROOT/target/release/nyash" --json-file "$json_path" >/dev/null 2>&1
|
||||
set -e || true
|
||||
|
||||
if [ -f "$mut_on" ]; then
|
||||
echo "[FAIL] canonicalize_noop_method_on_vm: mutated dump should not be created for Method" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[PASS] canonicalize_noop_method_on_vm"
|
||||
rm -f "$json_path" "$mut_on"
|
||||
exit 0
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
# gate_c_invalid_header_vm.sh — Gate‑C(Core) invalid JSON header → 非0終了
|
||||
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then
|
||||
ROOT="$ROOT_GIT"
|
||||
else
|
||||
ROOT="$(cd "$SCRIPT_DIR/../../../../../../.." && pwd)"
|
||||
fi
|
||||
source "$ROOT/tools/smokes/v2/lib/test_runner.sh"
|
||||
require_env || exit 2
|
||||
|
||||
bad="/tmp/gatec_bad_$$.json"
|
||||
echo '{"bad":1}' > "$bad"
|
||||
set +e
|
||||
NYASH_GATE_C_CORE=1 "$NYASH_BIN" --nyvm-json-file "$bad" >/dev/null 2>&1
|
||||
rc=$?
|
||||
set -e
|
||||
rm -f "$bad"
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "[PASS] gate_c_invalid_header_vm"
|
||||
else
|
||||
echo "[FAIL] gate_c_invalid_header_vm (rc=$rc)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
# map_bad_key_delete_vm.sh — Map.delete with non-string key returns [map/bad-key]
|
||||
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then
|
||||
ROOT="$ROOT_GIT"
|
||||
else
|
||||
ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"
|
||||
fi
|
||||
source "$ROOT/tools/smokes/v2/lib/test_runner.sh"
|
||||
require_env || exit 2
|
||||
|
||||
code='static box Main { main() { local m=new MapBox(); print(m.delete(123)); return 0 } }'
|
||||
out=$(run_nyash_vm -c "$code")
|
||||
if echo "$out" | grep -q "\[map/bad-key\]"; then
|
||||
echo "[PASS] map_bad_key_delete_vm"
|
||||
else
|
||||
echo "[FAIL] map_bad_key_delete_vm" >&2; echo "$out" >&2; exit 1
|
||||
fi
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
# map_bad_key_get_vm.sh — Map.get with non-string key returns [map/bad-key]
|
||||
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then
|
||||
ROOT="$ROOT_GIT"
|
||||
else
|
||||
ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"
|
||||
fi
|
||||
source "$ROOT/tools/smokes/v2/lib/test_runner.sh"
|
||||
require_env || exit 2
|
||||
|
||||
code='static box Main { main() { local m=new MapBox(); print(m.get(123)); return 0 } }'
|
||||
out=$(run_nyash_vm -c "$code")
|
||||
if echo "$out" | grep -q "\[map/bad-key\]"; then
|
||||
echo "[PASS] map_bad_key_get_vm"
|
||||
else
|
||||
echo "[FAIL] map_bad_key_get_vm" >&2; echo "$out" >&2; exit 1
|
||||
fi
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
# map_bad_key_set_vm.sh — Map.set with non-string key returns [map/bad-key]
|
||||
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then
|
||||
ROOT="$ROOT_GIT"
|
||||
else
|
||||
ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"
|
||||
fi
|
||||
source "$ROOT/tools/smokes/v2/lib/test_runner.sh"
|
||||
require_env || exit 2
|
||||
|
||||
code='static box Main { main() { local m=new MapBox(); print(m.set(123, 1)); return 0 } }'
|
||||
out=$(run_nyash_vm -c "$code")
|
||||
if echo "$out" | grep -q "\[map/bad-key\]"; then
|
||||
echo "[PASS] map_bad_key_set_vm"
|
||||
else
|
||||
echo "[FAIL] map_bad_key_set_vm" >&2; echo "$out" >&2; exit 1
|
||||
fi
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
# substring_clamp_vm.sh — String.substring clamps to [0,size] and start<=end
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then
|
||||
ROOT="$ROOT_GIT"
|
||||
else
|
||||
ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"
|
||||
fi
|
||||
source "$ROOT/tools/smokes/v2/lib/test_runner.sh"
|
||||
require_env || exit 2
|
||||
|
||||
code='static box Main { main() {
|
||||
local s="abcd";
|
||||
print(s.substring(-5, 2));
|
||||
print(s.substring(2, 99));
|
||||
print(s.substring(3, 1));
|
||||
return 0
|
||||
} }'
|
||||
out=$(run_nyash_vm -c "$code")
|
||||
expected=$(cat <<EOT
|
||||
ab
|
||||
cd
|
||||
|
||||
EOT
|
||||
)
|
||||
if [ "$out" = "$expected" ]; then
|
||||
echo "[PASS] substring_clamp_vm"
|
||||
else
|
||||
echo "[FAIL] substring_clamp_vm" >&2; printf '--- expected ---\n%s--- got ---\n%s\n' "$expected" "$out" >&2; exit 1
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user