Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions self_hosted/domain/builtins/primitives.av
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ fn callInt(name: String, args: List<Val>) -> Result<Val, String>
"Int.fromString" -> builtinIntFromString(args)
"Int.abs" -> builtinIntAbs(args)
"Int.mod" -> builtinIntMod(args)
"Int.div" -> builtinIntDiv(args)
"Int.max" -> builtinIntMax(args)
"Int.min" -> builtinIntMin(args)
_ -> Result.Err("unknown int builtin: {name}")
Expand Down Expand Up @@ -157,6 +158,29 @@ verify builtinIntModInner
builtinIntModInner(Val.ValInt(5), Val.ValInt(0)) => Result.Ok(Val.ValErr(Val.ValStr("modulo by zero")))
builtinIntModInner(Val.ValStr("x"), Val.ValInt(3)) => Result.Err("expected int argument")

fn builtinIntDiv(args: List<Val>) -> Result<Val, String>
? "Int.div(a, b) -> a div b as a Result value."
pair = Domain.Builtins.Helpers.twoArgs(args)?
match pair
(aV, bV) -> builtinIntDivInner(aV, bV)

verify builtinIntDiv
builtinIntDiv([Val.ValInt(7), Val.ValInt(2)]) => Result.Ok(Val.ValOk(Val.ValInt(3)))
builtinIntDiv([Val.ValInt(1)]) => Result.Err("expected 2 arguments")

fn builtinIntDivInner(aV: Val, bV: Val) -> Result<Val, String>
? "Inner impl of Int.div — Euclidean, Err on zero divisor or overflow."
a = Domain.Builtins.Helpers.expectInt(aV)?
b = Domain.Builtins.Helpers.expectInt(bV)?
match Int.div(a, b)
Result.Ok(q) -> Result.Ok(Val.ValOk(Val.ValInt(q)))
Result.Err(e) -> Result.Ok(Val.ValErr(Val.ValStr(e)))

verify builtinIntDivInner
builtinIntDivInner(Val.ValInt(7), Val.ValInt(2)) => Result.Ok(Val.ValOk(Val.ValInt(3)))
builtinIntDivInner(Val.ValInt(5), Val.ValInt(0)) => Result.Ok(Val.ValErr(Val.ValStr("division by zero")))
builtinIntDivInner(Val.ValStr("x"), Val.ValInt(3)) => Result.Err("expected int argument")

fn builtinStringLen(args: List<Val>) -> Result<Val, String>
? "String.len(s) -> length as Int."
v = Domain.Builtins.Helpers.oneArg(args)?
Expand Down
35 changes: 33 additions & 2 deletions self_hosted/domain/lexer.av
Original file line number Diff line number Diff line change
Expand Up @@ -328,12 +328,43 @@ verify tokenizeInterpString
tokenizeInterpString("hi\"}", 0, "") => [Token.TkStr("hi"), Token.TkInterpEnd, Token.TkStr(""), Token.TkEof]

fn tokenizeInterpDigit(src: String, pos: Int) -> List<Token>
? "Read number inside interpolation."
? "Read number inside interpolation; may be an int or a float literal."
match Domain.Lexer.Chars.readNumber(src, pos, 0)
(n, newPos) -> List.prepend(Token.TkInt(n), tokenizeInterpExpr(src, newPos))
(n, newPos) -> tokenizeInterpAfterInt(src, newPos, n)

verify tokenizeInterpDigit
tokenizeInterpDigit("42}", 0) => [Token.TkInt(42), Token.TkInterpEnd, Token.TkStr(""), Token.TkEof]
tokenizeInterpDigit("3.5}", 0) => [Token.TkFloat(3.5), Token.TkInterpEnd, Token.TkStr(""), Token.TkEof]

fn tokenizeInterpAfterInt(src: String, pos: Int, n: Int) -> List<Token>
? "After integer part inside interpolation, check for a decimal point."
match String.charAt(src, pos)
Option.Some(c) -> match c == "."
true -> tokenizeInterpAfterIntDot(src, pos, n)
false -> List.prepend(Token.TkInt(n), tokenizeInterpExpr(src, pos))
Option.None -> List.prepend(Token.TkInt(n), tokenizeInterpExpr(src, pos))

fn tokenizeInterpAfterIntDot(src: String, pos: Int, n: Int) -> List<Token>
? "After integer and dot inside interpolation: digit -> float, else int + dot."
nextPos = pos + 1
match String.charAt(src, nextPos)
Option.Some(d) -> match Domain.Lexer.Chars.isDigit(d)
true -> tokenizeInterpFloat(src, nextPos, n)
false -> List.prepend(Token.TkInt(n), tokenizeInterpExpr(src, pos))
Option.None -> List.prepend(Token.TkInt(n), tokenizeInterpExpr(src, pos))

fn tokenizeInterpFloat(src: String, pos: Int, intPart: Int) -> List<Token>
? "Read decimal digits and build a float token inside interpolation."
match Domain.Lexer.Chars.readNumber(src, pos, 0)
(decPart, newPos) -> tokenizeInterpBuildFloat(src, newPos, intPart, decPart, newPos - pos)

fn tokenizeInterpBuildFloat(src: String, pos: Int, intPart: Int, decPart: Int, decDigits: Int) -> List<Token>
? "Construct a float from integer and decimal parts inside interpolation."
f = Float.fromInt(intPart) + Float.fromInt(decPart) / pow10(decDigits)
List.prepend(Token.TkFloat(f), tokenizeInterpExpr(src, pos))

verify tokenizeInterpBuildFloat
tokenizeInterpBuildFloat("", 0, 3, 14, 2) => [Token.TkFloat(3.14), Token.TkInterpEnd, Token.TkEof]

fn tokenizeInterpAlpha(src: String, pos: Int) -> List<Token>
? "Read identifier inside interpolation."
Expand Down
1 change: 1 addition & 0 deletions self_hosted/domain/resolver/calls.av
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ fn isBuiltinCallName(name: String) -> Bool
"HttpServer.listen",
"HttpServer.listenWith",
"Int.abs",
"Int.div",
"Int.fromString",
"Int.max",
"Int.min",
Expand Down
21 changes: 19 additions & 2 deletions src/codegen/rust/replay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1432,6 +1432,23 @@ __POLICY_CHECK__
ScopeMode::Normal
}

fn canonical_unit(value: ReplayJson) -> ReplayJson {
// A Unit return serializes as JSON null in the VM / wasm-gc
// replay format; the self-host interpreter's value type wraps
// it as a `ValUnit` variant object. Map that variant back to
// null so a recording made by any backend replays cleanly on
// the self-host (and vice versa). Every other value passes
// through untouched.
if let ReplayJson::Object(ref map) = value {
if let Some(ReplayJson::Object(variant)) = map.get("$variant") {
if variant.get("name").and_then(|n| n.as_str()) == Some("ValUnit") {
return ReplayJson::Null;
}
}
}
value
}

fn finish_scope_success<T: ReplayValue>(value: &T) {
SCOPE_STATE.with(|cell| {
let mut state = cell.borrow_mut();
Expand All @@ -1442,7 +1459,7 @@ __POLICY_CHECK__
ScopeMode::Normal => {}
ScopeMode::Record { path, session } => {
session.output = RecordedOutcome::Value {
value: value.to_replay_json(),
value: canonical_unit(value.to_replay_json()),
};
write_recording(path, session);
}
Expand All @@ -1457,7 +1474,7 @@ __POLICY_CHECK__
session.effects.len().saturating_sub(*position)
);
}
let actual_json = value.to_replay_json();
let actual_json = canonical_unit(value.to_replay_json());
// Surface the live return value to the parent
// process via a stdout marker so the host
// (`run_self_host_replay` in
Expand Down
40 changes: 17 additions & 23 deletions src/self_host/aver_generated/domain/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2922,19 +2922,18 @@ pub fn ctorNameToTag(name: AverStr) -> i64 {
{
let __dispatch_subject = name.clone();
if &*__dispatch_subject == "Result.Ok" {
crate::aver_generated::domain::ast::tagResultOk()
1i64
} else {
if &*__dispatch_subject == "Result.Err" {
crate::aver_generated::domain::ast::tagResultErr()
2i64
} else {
if &*__dispatch_subject == "Option.Some" {
crate::aver_generated::domain::ast::tagOptionSome()
3i64
} else {
if &*__dispatch_subject == "Option.None" {
crate::aver_generated::domain::ast::tagOptionNone()
4i64
} else {
(crate::aver_generated::domain::ast::tagUserBase()
+ &crate::aver_generated::domain::ast::userCtorTagOffset(name))
(100i64 + crate::aver_generated::domain::ast::userCtorTagOffset(name))
}
}
}
Expand All @@ -2955,38 +2954,33 @@ pub fn userCtorTagOffsetLoop(mut name: AverStr, mut pos: i64, mut acc: i64) -> i
loop {
crate::cancel_checkpoint();
let accPlusOne = (acc + 1i64);
return if (pos >= (name.chars().count() as i64)) {
accPlusOne
} else {
if (pos < (name.chars().count() as i64)) {
match (name.chars().nth(pos as usize).map(|c| c.to_string())).into_aver() {
Some(ch) => {
let __tmp1 = (pos + 1i64);
let __tmp2 = crate::aver_generated::domain::ast::userCtorTagStep(
let __tco1 = (pos + 1i64);
let __tco2 = crate::aver_generated::domain::ast::userCtorTagStep(
acc,
(ch.chars().next().map(|c| c as i64).unwrap_or(0i64)),
);
pos = __tmp1;
acc = __tmp2;
pos = __tco1;
acc = __tco2;
continue;
}
None => accPlusOne,
None => {
return accPlusOne;
}
}
};
} else {
return accPlusOne;
}
}
}

/// Update the user constructor rolling hash and keep it in a bounded range.
pub fn userCtorTagStep(acc: i64, code: i64) -> i64 {
crate::cancel_checkpoint();
let next = ((acc * 131i64) + code);
{
let __b = crate::aver_generated::domain::ast::userTagSpan();
if __b == 0i64 {
0i64
} else {
(next).rem_euclid(__b)
}
}
(next).rem_euclid(1000003i64)
}

/// Map builtin function names to integer IDs for fast dispatch.
Expand Down
42 changes: 23 additions & 19 deletions src/self_host/aver_generated/domain/builtins/list/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,16 +157,17 @@ pub fn builtinListDropInner(lstV: &Val, nV: &Val) -> Result<Val, AverStr> {
pub fn listDrop(mut items: aver_rt::AverList<Val>, mut count: i64) -> aver_rt::AverList<Val> {
loop {
crate::cancel_checkpoint();
return if (count <= 0i64) {
items
} else {
aver_list_match!(items, [] => aver_rt::AverList::empty(), [_item, rest] => { {
let __tmp1 = (count - 1i64);
items = rest;
count = __tmp1;
if (count > 0i64) {
aver_list_match!(items, [] => { return aver_rt::AverList::empty(); }, [_item, rest] => { {
let __tco0 = rest;
let __tco1 = (count - 1i64);
items = __tco0;
count = __tco1;
continue;
} })
};
} else {
return items;
}
}
}

Expand Down Expand Up @@ -224,10 +225,11 @@ pub fn builtinListContainsInner(lstV: &Val, needle: &Val) -> Result<Val, AverStr
pub fn listContainsVal(mut items: aver_rt::AverList<Val>, mut needle: Val) -> bool {
loop {
crate::cancel_checkpoint();
return aver_list_match!(items, [] => false, [v, rest] => { if (crate::aver_generated::domain::value::valRepr(&v) == crate::aver_generated::domain::value::valRepr(&needle)) { true } else { {
items = rest;
aver_list_match!(items, [] => { return false; }, [v, rest] => { if (crate::aver_generated::domain::value::valRepr(&v) == crate::aver_generated::domain::value::valRepr(&needle)) { return true; } else { {
let __tco0 = rest;
items = __tco0;
continue;
} } });
} } })
}
}

Expand Down Expand Up @@ -272,13 +274,15 @@ pub fn zipListsAcc(
loop {
crate::cancel_checkpoint();
let reversed = acc.reverse();
return aver_list_match!(a, [] => reversed, [x, xs] => { aver_list_match!(b, [] => reversed, [y, ys] => { {
let __tmp2 = aver_rt::AverList::prepend(crate::aver_generated::domain::value::Val::ValTuple(aver_rt::AverList::from_vec(vec![x, y])), &acc);
a = xs;
b = ys;
acc = __tmp2;
aver_list_match!(a, [] => { return reversed; }, [x, xs] => { aver_list_match!(b, [] => { return reversed; }, [y, ys] => { {
let __tco0 = xs;
let __tco1 = ys;
let __tco2 = aver_rt::AverList::prepend(crate::aver_generated::domain::value::Val::ValTuple(aver_rt::AverList::from_vec(vec![x, y])), &acc);
a = __tco0;
b = __tco1;
acc = __tco2;
continue;
} }) });
} }) })
}
}

Expand All @@ -287,13 +291,13 @@ pub fn builtinListHead(args: &aver_rt::AverList<Val>) -> Result<Val, AverStr> {
crate::cancel_checkpoint();
let v = crate::aver_generated::domain::builtins::helpers::oneArg(args)?;
let items = crate::aver_generated::domain::builtins::helpers::expectList(&v)?;
aver_list_match!(items, [] => Ok(Val::ValNone.clone()), [h, rest] => Ok(crate::aver_generated::domain::value::Val::ValSome(std::sync::Arc::new(h))))
aver_list_match!(items, [] => Ok(crate::aver_generated::domain::value::Val::ValNone), [h, rest] => Ok(crate::aver_generated::domain::value::Val::ValSome(std::sync::Arc::new(h))))
}

/// List.tail(list) -> Option of rest.
pub fn builtinListTail(args: &aver_rt::AverList<Val>) -> Result<Val, AverStr> {
crate::cancel_checkpoint();
let v = crate::aver_generated::domain::builtins::helpers::oneArg(args)?;
let items = crate::aver_generated::domain::builtins::helpers::expectList(&v)?;
aver_list_match!(items, [] => Ok(Val::ValNone.clone()), [h, rest] => Ok(crate::aver_generated::domain::value::Val::ValSome(std::sync::Arc::new(crate::aver_generated::domain::value::Val::ValList(rest)))))
aver_list_match!(items, [] => Ok(crate::aver_generated::domain::value::Val::ValNone), [h, rest] => Ok(crate::aver_generated::domain::value::Val::ValSome(std::sync::Arc::new(crate::aver_generated::domain::value::Val::ValList(rest)))))
}
Loading
Loading