diff --git a/be-csharp/src/commonMain/resources/lang/temper/be/csharp/temper-core/Float64.cs b/be-csharp/src/commonMain/resources/lang/temper/be/csharp/temper-core/Float64.cs index b53d8220..104a1be0 100644 --- a/be-csharp/src/commonMain/resources/lang/temper/be/csharp/temper-core/Float64.cs +++ b/be-csharp/src/commonMain/resources/lang/temper/be/csharp/temper-core/Float64.cs @@ -73,7 +73,7 @@ public static bool Near( var rel = relTol ?? 1e-9; var abs = absTol ?? 0.0; var margin = Math.Max(Math.Max(Math.Abs(x), Math.Abs(y)) * rel, abs); - return Math.Abs(x - y) < margin; + return Math.Abs(x - y) <= margin; } public static double Sign(this double x) @@ -109,7 +109,7 @@ public static double ToFloat64(this string s) ? double.PositiveInfinity : trimmed == "-Infinity" ? double.NegativeInfinity - : double.Parse(s); + : double.Parse(trimmed); } public static int ToInt(double value) diff --git a/be-csharp/src/commonMain/resources/lang/temper/be/csharp/temper-core/StringUtil.cs b/be-csharp/src/commonMain/resources/lang/temper/be/csharp/temper-core/StringUtil.cs index c4b62a5b..8ce58331 100644 --- a/be-csharp/src/commonMain/resources/lang/temper/be/csharp/temper-core/StringUtil.cs +++ b/be-csharp/src/commonMain/resources/lang/temper/be/csharp/temper-core/StringUtil.cs @@ -62,7 +62,7 @@ public static int CountBetween(string s, int left, int right) for (int i = Math.Max(0, left); i < limit; ++i) { count += 1; - if (i + 1 < right) + if (i + 1 < limit) { char c = s[i]; if ('\uD800' <= c && c <= '\uDBFF') diff --git a/be-java/src/commonMain/resources/lang/temper/be/java/temper-core/src/main/java/temper/core/Core.java b/be-java/src/commonMain/resources/lang/temper/be/java/temper-core/src/main/java/temper/core/Core.java index 4d5dfcdd..b63daec1 100644 --- a/be-java/src/commonMain/resources/lang/temper/be/java/temper-core/src/main/java/temper/core/Core.java +++ b/be-java/src/commonMain/resources/lang/temper/be/java/temper-core/src/main/java/temper/core/Core.java @@ -318,7 +318,7 @@ public static boolean float64Near(double x, double y, Double relTol, Double absT double rel = relTol == null ? 1e-9 : relTol; double abs = absTol == null ? 0.0 : absTol; double margin = Math.max(Math.max(Math.abs(x), Math.abs(y)) * rel, abs); - return Math.abs(x - y) < margin; + return Math.abs(x - y) <= margin; } /** diff --git a/be-js/src/commonMain/resources/lang/temper/be/js/temper-core/float.js b/be-js/src/commonMain/resources/lang/temper/be/js/temper-core/float.js index f921f43c..d431d5f0 100644 --- a/be-js/src/commonMain/resources/lang/temper/be/js/temper-core/float.js +++ b/be-js/src/commonMain/resources/lang/temper/be/js/temper-core/float.js @@ -19,7 +19,7 @@ export const float64Near = (x, y, relTol, absTol) => { absTol = 0; } const margin = Math.max(Math.max(Math.abs(x), Math.abs(y)) * relTol, absTol); - return Math.abs(x - y) < margin; + return Math.abs(x - y) <= margin; } /** diff --git a/be-js/src/commonMain/resources/lang/temper/be/js/temper-core/listed.js b/be-js/src/commonMain/resources/lang/temper/be/js/temper-core/listed.js index bf05d9ff..a720953a 100644 --- a/be-js/src/commonMain/resources/lang/temper/be/js/temper-core/listed.js +++ b/be-js/src/commonMain/resources/lang/temper/be/js/temper-core/listed.js @@ -238,7 +238,7 @@ export const listBuilderReverse = (ls) => { * @param {T} newValue */ export const listBuilderSet = (ls, i, newValue) => { - if (0 <= i && i <= ls.length) { + if (0 <= i && i < ls.length) { ls[i] = newValue; } } diff --git a/be-js/src/commonMain/resources/lang/temper/be/js/temper-core/string.js b/be-js/src/commonMain/resources/lang/temper/be/js/temper-core/string.js index 91277c98..49b7aeea 100644 --- a/be-js/src/commonMain/resources/lang/temper/be/js/temper-core/string.js +++ b/be-js/src/commonMain/resources/lang/temper/be/js/temper-core/string.js @@ -159,7 +159,7 @@ export const stringHasAtLeast = (s, begin, end, minCount) => { * @returns {number} */ export const stringNext = (s, i) => { - let iNext = Math.min(s.length, i); + let iNext = Math.min(s.length, Math.max(0, i)); let cp = s.codePointAt(i); if (cp !== undefined) { iNext += 1 + !!(cp >>> 16); @@ -173,7 +173,7 @@ export const stringNext = (s, i) => { * @returns {number} */ export const stringPrev = (s, i) => { - let iPrev = Math.min(s.length, i); + let iPrev = Math.min(s.length, Math.max(0, i)); if (iPrev) { iPrev -= 1; if (iPrev && s.codePointAt(iPrev - 1) >>> 16) { diff --git a/be-lua/src/commonMain/kotlin/lang/temper/be/lua/LuaLocalRemover.kt b/be-lua/src/commonMain/kotlin/lang/temper/be/lua/LuaLocalRemover.kt index 4b49bb16..1d6897e1 100644 --- a/be-lua/src/commonMain/kotlin/lang/temper/be/lua/LuaLocalRemover.kt +++ b/be-lua/src/commonMain/kotlin/lang/temper/be/lua/LuaLocalRemover.kt @@ -3,11 +3,16 @@ package lang.temper.be.lua // Could go up to 200 in theory, but smaller numbers allow for larger closures. const val MAX_ALLOWABLE_LOCALS = 128 +// When wrapping is needed, keep first N locals as real locals for performance. +// The rest overflow into the env table. Budget: LOCALS_TO_KEEP + 1 (env table) < MAX_ALLOWABLE_LOCALS. +private const val LOCALS_TO_KEEP = 100 + class LuaLocalRemover { private var locals = mutableMapOf() private var luaName = LuaName("_G") private var numLocalTables = 0 - private var shouldRewrite = false + private var needsEnvTable = false + private var localsKept = 0 private var localsToDecl = mutableListOf() private fun allocLocalTableName(): LuaName = LuaName("env_t${++numLocalTables}") @@ -15,13 +20,15 @@ class LuaLocalRemover { private fun scoped(cb: () -> Lua.Chunk): Lua.Chunk { val lastLocals = locals.toMutableMap() val lastLuaName = luaName - val lastShouldRewrite = shouldRewrite + val lastNeedsEnvTable = needsEnvTable + val lastLocalsKept = localsKept val lastLocalsToDecl = localsToDecl localsToDecl = mutableListOf() val ret = cb() locals = lastLocals luaName = lastLuaName - shouldRewrite = lastShouldRewrite + needsEnvTable = lastNeedsEnvTable + localsKept = lastLocalsKept val localsToWrap = localsToDecl localsToDecl = lastLocalsToDecl return luaChunk( @@ -50,12 +57,12 @@ class LuaLocalRemover { ) } - private fun define(target: Lua.SetTarget) { + private fun define(target: Lua.SetTarget, asEnvField: Boolean) { when (target) { is Lua.DotSetTarget -> TODO() is Lua.IndexSetTarget -> TODO() is Lua.NameSetTarget -> { - if (shouldRewrite) { + if (asEnvField) { locals[target.target.id] = luaName } else { locals.remove(target.target.id) @@ -64,6 +71,10 @@ class LuaLocalRemover { } } + // Decide whether a batch of N new locals should overflow to the env table. + private fun shouldOverflow(count: Int): Boolean = + needsEnvTable && localsKept + count > LOCALS_TO_KEEP + private fun set(target: Lua.SetTarget): Lua.SetTarget = when (target) { is Lua.DotSetTarget -> when (val obj = target.obj) { is Lua.SetTarget -> Lua.DotSetTarget( @@ -230,8 +241,9 @@ class LuaLocalRemover { private fun wrapLocals(chunk: Lua.Chunk): List { luaName = allocLocalTableName() val numLocals = countLocalsDirectlyIn(chunk) - shouldRewrite = numLocals >= MAX_ALLOWABLE_LOCALS - if (shouldRewrite) { + needsEnvTable = numLocals >= MAX_ALLOWABLE_LOCALS + if (needsEnvTable) { + localsKept = 0 return listOf( Lua.LocalStmt( chunk.pos, @@ -355,17 +367,17 @@ class LuaLocalRemover { Lua.Name(stmt.name.pos, stmt.name.id), ) is Lua.LocalDeclStmt -> { - stmt.targets.targets.forEach(::define) - if (shouldRewrite) { - null - } else { + val overflow = shouldOverflow(stmt.targets.targets.size) + stmt.targets.targets.forEach { define(it, asEnvField = overflow) } + if (!overflow) { + if (needsEnvTable) localsKept += stmt.targets.targets.size stmt.targets.targets.forEach { localsToDecl.add((it as Lua.NameSetTarget).target.id) } - null } + null } - is Lua.LocalFunctionStmt -> if (shouldRewrite) { + is Lua.LocalFunctionStmt -> if (shouldOverflow(1)) { locals[stmt.name.id] = luaName Lua.SetStmt( stmt.pos, @@ -405,6 +417,7 @@ class LuaLocalRemover { ), ) } else { + if (needsEnvTable) localsKept++ localsToDecl.add(stmt.name.id) Lua.SetStmt( stmt.pos, @@ -440,7 +453,7 @@ class LuaLocalRemover { ), ) } - is Lua.LocalStmt -> if (shouldRewrite) { + is Lua.LocalStmt -> if (shouldOverflow(stmt.targets.targets.size)) { val ret = Lua.SetStmt( stmt.pos, Lua.SetTargets( @@ -461,9 +474,10 @@ class LuaLocalRemover { stmt.exprs.exprs.map(::scan), ), ) - stmt.targets.targets.forEach(::define) + stmt.targets.targets.forEach { define(it, asEnvField = true) } ret } else { + if (needsEnvTable) localsKept += stmt.targets.targets.size val ret = Lua.SetStmt( stmt.pos, Lua.SetTargets( @@ -479,7 +493,7 @@ class LuaLocalRemover { stmt.exprs.exprs.map(::scan), ), ) - stmt.targets.targets.forEach(::define) + stmt.targets.targets.forEach { define(it, asEnvField = false) } ret } is Lua.SetStmt -> { diff --git a/be-lua/src/commonMain/kotlin/lang/temper/be/lua/LuaSupportNetwork.kt b/be-lua/src/commonMain/kotlin/lang/temper/be/lua/LuaSupportNetwork.kt index c7737f63..f2829a88 100644 --- a/be-lua/src/commonMain/kotlin/lang/temper/be/lua/LuaSupportNetwork.kt +++ b/be-lua/src/commonMain/kotlin/lang/temper/be/lua/LuaSupportNetwork.kt @@ -35,7 +35,7 @@ internal fun operatorToName( BuiltinOperatorId.BitwiseAnd -> "band" BuiltinOperatorId.BitwiseOr -> "bor" BuiltinOperatorId.IsNull -> "is_null" - BuiltinOperatorId.NotNull -> TODO() + BuiltinOperatorId.NotNull -> "not_null" BuiltinOperatorId.DivFltFlt -> "fdiv" BuiltinOperatorId.DivIntInt -> "int32_div" BuiltinOperatorId.DivIntInt64 -> "int64_div" @@ -51,36 +51,36 @@ internal fun operatorToName( BuiltinOperatorId.MinusInt -> "int32_unm" BuiltinOperatorId.MinusInt64 -> "int64_unm" // TODO Just use `-x` because either standard int64 or metatabled? BuiltinOperatorId.MinusIntInt -> "int32_sub" - BuiltinOperatorId.MinusIntInt64 -> TODO() + BuiltinOperatorId.MinusIntInt64 -> "int64_sub" BuiltinOperatorId.PlusFltFlt -> "add" BuiltinOperatorId.PlusIntInt -> "int32_add" - BuiltinOperatorId.PlusIntInt64 -> TODO() + BuiltinOperatorId.PlusIntInt64 -> "int64_add" BuiltinOperatorId.PowFltFlt -> "pow" BuiltinOperatorId.TimesIntInt -> "int32_mul" - BuiltinOperatorId.TimesIntInt64 -> TODO() + BuiltinOperatorId.TimesIntInt64 -> "int64_mul" BuiltinOperatorId.TimesFltFlt -> "mul" BuiltinOperatorId.LtFltFlt -> "float_lt" - BuiltinOperatorId.LtIntInt -> TODO() + BuiltinOperatorId.LtIntInt -> "generic_lt" BuiltinOperatorId.LtStrStr -> "str_lt" BuiltinOperatorId.LtGeneric -> "generic_lt" BuiltinOperatorId.LeFltFlt -> "float_le" - BuiltinOperatorId.LeIntInt -> TODO() + BuiltinOperatorId.LeIntInt -> "generic_le" BuiltinOperatorId.LeStrStr -> "str_le" BuiltinOperatorId.LeGeneric -> "generic_le" BuiltinOperatorId.GtFltFlt -> "float_gt" - BuiltinOperatorId.GtIntInt -> TODO() + BuiltinOperatorId.GtIntInt -> "generic_gt" BuiltinOperatorId.GtStrStr -> "str_gt" BuiltinOperatorId.GtGeneric -> "generic_gt" BuiltinOperatorId.GeFltFlt -> "float_ge" - BuiltinOperatorId.GeIntInt -> TODO() + BuiltinOperatorId.GeIntInt -> "generic_ge" BuiltinOperatorId.GeStrStr -> "str_ge" BuiltinOperatorId.GeGeneric -> "generic_ge" BuiltinOperatorId.EqFltFlt -> "float_eq" - BuiltinOperatorId.EqIntInt -> TODO() + BuiltinOperatorId.EqIntInt -> "generic_eq" BuiltinOperatorId.EqStrStr -> "str_eq" BuiltinOperatorId.EqGeneric -> "generic_eq" BuiltinOperatorId.NeFltFlt -> "float_ne" - BuiltinOperatorId.NeIntInt -> TODO() + BuiltinOperatorId.NeIntInt -> "generic_ne" BuiltinOperatorId.NeStrStr -> "str_ne" BuiltinOperatorId.NeGeneric -> "generic_ne" BuiltinOperatorId.CmpFltFlt -> "float_cmp" @@ -91,7 +91,7 @@ internal fun operatorToName( BuiltinOperatorId.Print -> "print" BuiltinOperatorId.StrCat -> "concat" BuiltinOperatorId.Listify -> "listof" - BuiltinOperatorId.Async -> "TODO" // TODO + BuiltinOperatorId.Async -> "async" // should not be used with CoroutineStrategy.TranslateToGenerator BuiltinOperatorId.AdaptGeneratorFn, BuiltinOperatorId.SafeAdaptGeneratorFn, @@ -289,6 +289,92 @@ internal object LuaSupportNetwork : SupportNetwork { args[1], ) } + // Inline float arithmetic as native Lua operators (Lua numbers are doubles). + BuiltinOperatorId.PlusFltFlt -> InlineLua( + builtin.builtinOperatorId.toString(), + builtin.builtinOperatorId, + ) { pos, args -> + Lua.BinaryExpr( + pos, args[0], + Lua.BinaryOp(pos, BinaryOpEnum.Add, LuaOperatorDefinition.Add), + args[1], + ) + } + BuiltinOperatorId.MinusFltFlt -> InlineLua( + builtin.builtinOperatorId.toString(), + builtin.builtinOperatorId, + ) { pos, args -> + Lua.BinaryExpr( + pos, args[0], + Lua.BinaryOp(pos, BinaryOpEnum.Sub, LuaOperatorDefinition.Sub), + args[1], + ) + } + BuiltinOperatorId.TimesFltFlt -> InlineLua( + builtin.builtinOperatorId.toString(), + builtin.builtinOperatorId, + ) { pos, args -> + Lua.BinaryExpr( + pos, args[0], + Lua.BinaryOp(pos, BinaryOpEnum.Mul, LuaOperatorDefinition.Mul), + args[1], + ) + } + BuiltinOperatorId.DivFltFlt -> InlineLua( + builtin.builtinOperatorId.toString(), + builtin.builtinOperatorId, + ) { pos, args -> + Lua.BinaryExpr( + pos, args[0], + Lua.BinaryOp(pos, BinaryOpEnum.Div, LuaOperatorDefinition.Div), + args[1], + ) + } + BuiltinOperatorId.PowFltFlt -> InlineLua( + builtin.builtinOperatorId.toString(), + builtin.builtinOperatorId, + ) { pos, args -> + Lua.BinaryExpr( + pos, args[0], + Lua.BinaryOp(pos, BinaryOpEnum.Pow, LuaOperatorDefinition.Pow), + args[1], + ) + } + BuiltinOperatorId.MinusFlt -> InlineLua( + builtin.builtinOperatorId.toString(), + builtin.builtinOperatorId, + ) { pos, args -> + Lua.UnaryExpr( + pos, + Lua.UnaryOp(pos.leftEdge, UnaryOpEnum.UnaryAdd, LuaOperatorDefinition.Unm), + args[0], + ) + } + // Inline string comparisons as native Lua operators (lexicographic, same as Temper). + BuiltinOperatorId.EqStrStr -> inlineBinaryOp( + builtin.builtinOperatorId.toString(), BinaryOpEnum.Eq, LuaOperatorDefinition.Eq, + builtin.builtinOperatorId, + ) + BuiltinOperatorId.NeStrStr -> inlineBinaryOp( + builtin.builtinOperatorId.toString(), BinaryOpEnum.NotEq, LuaOperatorDefinition.Ne, + builtin.builtinOperatorId, + ) + BuiltinOperatorId.LtStrStr -> inlineBinaryOp( + builtin.builtinOperatorId.toString(), BinaryOpEnum.Lt, LuaOperatorDefinition.Lt, + builtin.builtinOperatorId, + ) + BuiltinOperatorId.LeStrStr -> inlineBinaryOp( + builtin.builtinOperatorId.toString(), BinaryOpEnum.LtEq, LuaOperatorDefinition.Le, + builtin.builtinOperatorId, + ) + BuiltinOperatorId.GtStrStr -> inlineBinaryOp( + builtin.builtinOperatorId.toString(), BinaryOpEnum.Gt, LuaOperatorDefinition.Gt, + builtin.builtinOperatorId, + ) + BuiltinOperatorId.GeStrStr -> inlineBinaryOp( + builtin.builtinOperatorId.toString(), BinaryOpEnum.GtEq, LuaOperatorDefinition.Ge, + builtin.builtinOperatorId, + ) else -> InlineLua(builtin.builtinOperatorId.toString(), builtin.builtinOperatorId) { pos, args -> Lua.FunctionCallExpr( pos, @@ -356,6 +442,75 @@ internal object LuaSupportNetwork : SupportNetwork { Lua.Args(pos, Lua.Exprs(pos, args)), ) } + + // Int32::toFloat64 is identity in Lua (all numbers are doubles). + "Int32::toFloat64" -> inlineIdentity(connectedKey) + + // Math functions → Lua's math.* standard library (available in 5.1+). + "Float64::abs" -> inlineGlobalCall(connectedKey, "math", "abs") + "Float64::ceil" -> inlineGlobalCall(connectedKey, "math", "ceil") + "Float64::floor" -> inlineGlobalCall(connectedKey, "math", "floor") + "Float64::sqrt" -> inlineGlobalCall(connectedKey, "math", "sqrt") + "Float64::sin" -> inlineGlobalCall(connectedKey, "math", "sin") + "Float64::cos" -> inlineGlobalCall(connectedKey, "math", "cos") + "Float64::tan" -> inlineGlobalCall(connectedKey, "math", "tan") + "Float64::asin" -> inlineGlobalCall(connectedKey, "math", "asin") + "Float64::acos" -> inlineGlobalCall(connectedKey, "math", "acos") + "Float64::atan" -> inlineGlobalCall(connectedKey, "math", "atan") + "Float64::exp" -> inlineGlobalCall(connectedKey, "math", "exp") + "Float64::log" -> inlineGlobalCall(connectedKey, "math", "log") + // Float64::max/min have NaN propagation semantics; cannot use math.max/min directly. + "Int32::max" -> inlineGlobalCall(connectedKey, "math", "max") + "Int32::min" -> inlineGlobalCall(connectedKey, "math", "min") + + // Math constants. + "Float64::pi" -> inlineGlobalProp(connectedKey, "math", "pi") + "Float64::infinity" -> inlineGlobalProp(connectedKey, "math", "huge") + + // String::isEmpty → #str == 0 (byte-length check, correct for empty strings). + "String::isEmpty" -> InlineLua(connectedKey) { pos, args -> + Lua.BinaryExpr( + pos, + Lua.UnaryExpr( + pos, + Lua.UnaryOp(pos, UnaryOpEnum.UnarySub, LuaOperatorDefinition.Unm), + args[0], + ), + Lua.BinaryOp(pos, BinaryOpEnum.Eq, LuaOperatorDefinition.Eq), + Lua.Num(pos, 0.0), + ) + } + + // List::isEmpty → #list == 0. + "List::isEmpty", + "Listed::isEmpty", + -> InlineLua(connectedKey) { pos, args -> + Lua.BinaryExpr( + pos, + Lua.UnaryExpr( + pos, + Lua.UnaryOp(pos, UnaryOpEnum.UnarySub, LuaOperatorDefinition.Unm), + args[0], + ), + Lua.BinaryOp(pos, BinaryOpEnum.Eq, LuaOperatorDefinition.Eq), + Lua.Num(pos, 0.0), + ) + } + + // Boolean::toString → tostring(). (Int32::toString has a radix param, can't inline.) + "Boolean::toString" -> inlineBuiltinCall(connectedKey, "tostring") + + // StringIndex comparisons → native Lua operators (they're just integers). + "StringIndexOption::compareTo" -> InlineLua(connectedKey) { pos, args -> + Lua.BinaryExpr(pos, args[0], Lua.BinaryOp(pos, BinaryOpEnum.Sub, LuaOperatorDefinition.Sub), args[1]) + } + "StringIndexOption::compareTo::eq" -> inlineBinaryOp(connectedKey, BinaryOpEnum.Eq, LuaOperatorDefinition.Eq) + "StringIndexOption::compareTo::ne" -> inlineBinaryOp(connectedKey, BinaryOpEnum.NotEq, LuaOperatorDefinition.Ne) + "StringIndexOption::compareTo::lt" -> inlineBinaryOp(connectedKey, BinaryOpEnum.Lt, LuaOperatorDefinition.Lt) + "StringIndexOption::compareTo::le" -> inlineBinaryOp(connectedKey, BinaryOpEnum.LtEq, LuaOperatorDefinition.Le) + "StringIndexOption::compareTo::gt" -> inlineBinaryOp(connectedKey, BinaryOpEnum.Gt, LuaOperatorDefinition.Gt) + "StringIndexOption::compareTo::ge" -> inlineBinaryOp(connectedKey, BinaryOpEnum.GtEq, LuaOperatorDefinition.Ge) + else -> temperMethod( connectedKey, connectedKey @@ -453,3 +608,52 @@ internal fun temperMethod( ) }, ) + +// --- Inline helper factories for systematic operator/method inlining --- + +/** Inline a binary operator: `args[0] op args[1]` */ +private fun inlineBinaryOp( + key: String, + op: BinaryOpEnum, + opDef: LuaOperatorDefinition, + builtinOp: BuiltinOperatorId? = null, +) = InlineLua(key, builtinOp) { pos, args -> + Lua.BinaryExpr(pos, args[0], Lua.BinaryOp(pos, op, opDef), args[1]) +} + +/** Inline a call to a Lua standard library function: `module.fn(args)` */ +private fun inlineGlobalCall( + key: String, + module: String, + fn: String, +) = InlineLua(key) { pos, args -> + Lua.FunctionCallExpr( + pos, + Lua.DotIndexExpr(pos, Lua.Name(pos, name(module)), Lua.Name(pos, name(fn))), + Lua.Args(pos, Lua.Exprs(pos, args)), + ) +} + +/** Inline a call to a Lua global function: `fn(args)` */ +private fun inlineBuiltinCall( + key: String, + fn: String, +) = InlineLua(key) { pos, args -> + Lua.FunctionCallExpr( + pos, + Lua.Name(pos, name(fn)), + Lua.Args(pos, Lua.Exprs(pos, args)), + ) +} + +/** Inline identity: returns `args[0]` unchanged. */ +private fun inlineIdentity(key: String) = InlineLua(key) { _, args -> args[0] } + +/** Inline a global property access: `module.prop` */ +private fun inlineGlobalProp( + key: String, + module: String, + prop: String, +) = InlineLua(key) { pos, _ -> + Lua.DotIndexExpr(pos, Lua.Name(pos, name(module)), Lua.Name(pos, name(prop))) +} diff --git a/be-lua/src/commonMain/resources/lang/temper/be/lua/temper-core/init.lua b/be-lua/src/commonMain/resources/lang/temper/be/lua/temper-core/init.lua index 98d348f9..24967b58 100644 --- a/be-lua/src/commonMain/resources/lang/temper/be/lua/temper-core/init.lua +++ b/be-lua/src/commonMain/resources/lang/temper/be/lua/temper-core/init.lua @@ -93,7 +93,7 @@ function temper.codepoint_fallback(s, i) b1 = string.byte(s, i + 1) -- TODO: these checks should really be (b1 & 192) == 128 if b1 == nil or b1 >= 192 then return 0xFFFD; end - return (b0 - 192)*64 + b1 + return (b0 - 192)*64 + b1%64 end if b0 < 240 then @@ -429,7 +429,7 @@ function temper.float64_near(x, y, rel_tol, abs_tol) abs_tol = temper.null_to_nil(abs_tol) or 0.0 local scale = temper.float64_max(temper.float64_abs(x), temper.float64_abs(y)) local margin = temper.float64_max(scale * rel_tol, abs_tol) - return temper.float64_abs(x - y) < margin + return temper.float64_abs(x - y) <= margin end function temper.float64_round(x) @@ -1077,6 +1077,36 @@ function temper.generic_ge(a, b) return a >= b end +function temper.float_cmp(a, b) + if temper.float_lt(a, b) then + return -1 + elseif temper.float_gt(a, b) then + return 1 + else + return 0 + end +end + +function temper.int_cmp(a, b) + if a < b then + return -1 + elseif a > b then + return 1 + else + return 0 + end +end + +function temper.str_cmp(a, b) + if a < b then + return -1 + elseif a > b then + return 1 + else + return 0 + end +end + function temper.str_eq(a, b) return a == b end @@ -1470,7 +1500,7 @@ function temper.string_hasindex(str, i) end function temper.string_indexof(str, target, i) - return string.find(str, target, i, true) or 0 + return string.find(str, target, i, true) or -1 end function temper.string_next(str, i) @@ -1556,8 +1586,12 @@ function temper.require_string_index(i) end function temper.string_foreach(str, f) - for _, c in utf8.codes(str) do - f(c) + local i = 1 + local len = string_len(str) + while i <= len do + local cp = temper.codepoint_fallback(str, i) + f(cp) + i = i + utf8len(str, i) end end @@ -1635,7 +1669,14 @@ function temper.mapbuilder_remove(builder, key) if got == nil then temper.bubble("MapBuilder::remove key not found: " .. tostring(key)) end - builder[key] = nil + rawset(builder, key, nil) + local key_order_list = rawget(builder, map_key_order) + for i = #key_order_list, 1, -1 do + if key_order_list[i] == key then + table_remove(key_order_list, i) + break + end + end return got end @@ -1748,7 +1789,7 @@ do local sign if string_byte(str, 1) == 45 then sign = "-" - str = string_sub(pad, 2, string_len(str)) + str = string_sub(str, 2) else sign = "" end @@ -1985,4 +2026,17 @@ do end end +function temper.listed_mapdropping(list, f) + local ret = {} + local head = 1 + for i = 1, #list do + local ok, val = pcall(f, list[i]) + if ok then + ret[head] = val + head = head + 1 + end + end + return ret +end + return temper diff --git a/be-py/src/commonMain/resources/lang/temper/be/py/temper-core/temper_core/__init__.py b/be-py/src/commonMain/resources/lang/temper/be/py/temper-core/temper_core/__init__.py index 8f783880..b91313df 100644 --- a/be-py/src/commonMain/resources/lang/temper/be/py/temper-core/temper_core/__init__.py +++ b/be-py/src/commonMain/resources/lang/temper/be/py/temper-core/temper_core/__init__.py @@ -180,7 +180,7 @@ def float_gt_eq(left: float, right: float) -> bool: def float_gt(left: float, right: float) -> bool: - "Checks if left <= right, caring about sign of zeros." + "Checks if left > right, caring about sign of zeros." return float_cmp(left, right) > 0 @@ -216,7 +216,7 @@ def generic_lt_eq(left: C, right: C) -> bool: def generic_lt(left: C, right: C) -> bool: - "Checks if two left <=right, caring about the sign of zeros of floats." + "Checks if left < right, caring about the sign of zeros of floats." if isinstance(left, float) and isinstance(right, float): return float_lt(left, right) return left < right @@ -251,9 +251,12 @@ def arith_int_mod(dividend: int, divisor: int) -> int: arith_int_mod(5, 3) == 2 arith_int_mod(-5, -3) == -2 arith_int_mod(5, -3) == 2 - arith_int_mod(-5, -3) == -2 + arith_int_mod(-5, 3) == -2 """ - return dividend - divisor * int(dividend / divisor) + q = dividend // divisor + if (dividend ^ divisor) < 0 and q * divisor != dividend: + q += 1 + return dividend - divisor * q def isinstance_int(val: T) -> bool: @@ -387,7 +390,7 @@ def __init__(self, capacity: int): def __bool__(self) -> bool: "Test if any bit is set." - return bool(rb"\0" in self._bytearray) + return any(b != 0 for b in self._bytearray) def __bytes__(self) -> bytes: "Convert the bit vector into a read-only bytes value." @@ -496,7 +499,10 @@ def int64_div(a: int, b: int) -> int: # Mostly concerned with b == -1, but maybe evil `a` snuck in from outside? if a <= -0x8000_0000_0000_0000 and b < 0: return int64_clamp(int(a / b)) - return int(a / b) + r = a // b + if (a ^ b) < 0 and r * b != a: + r += 1 + return r def int64_mul(a: int, b: int) -> int: @@ -520,14 +526,14 @@ def int64_to_float64(value: int) -> float: raise OverflowError() -def int64_to_int32(value: int) -> float: +def int64_to_int32(value: int) -> int: "Implements connected method Int64::toInt32." if -0x8000_0000 <= value <= 0x7FFF_FFFF: return int(value) raise OverflowError() -def int64_to_int32_unsafe(value: int) -> float: +def int64_to_int32_unsafe(value: int) -> int: "Implements connected method Int64::toInt32Unsafe." return int_clamp(int(value)) @@ -549,7 +555,7 @@ def float64_near( y: float, rel_tol: Optional[float] = Unset, abs_tol: Optional[float] = Unset, -) -> float: +) -> bool: "Implements connected method Float64::near." # This exactly matches isclose behavior, but matching our forwarding our # optionals to python named args is awkward, so duplicate the logic. @@ -631,7 +637,7 @@ def float64_to_string(value: float) -> str: def boolean_to_string(value: bool) -> str: - "Turns a stirng into a boolean (lowercase like temper)." + "Turns a boolean into a string (lowercase like temper)." return "true" if value else "false" @@ -722,7 +728,7 @@ def require_string_index(i: int) -> int: def require_no_string_index(i: int) -> int: "Checked cast from i to NoStringIndex, a negative int" if i < 0: - return -1 + return i raise AssertionError(f"require_string_index; {i!r} not < 0 ") @@ -759,7 +765,7 @@ def string_to_float64(string: str) -> float: return result -def string_to_int32(string: str, radix: Optional[int] = None) -> float: +def string_to_int32(string: str, radix: Optional[int] = None) -> int: if radix == 0: # Other values we reject are checked already. raise ValueError() @@ -769,7 +775,7 @@ def string_to_int32(string: str, radix: Optional[int] = None) -> float: raise OverflowError() -def string_to_int64(string: str, radix: Optional[int] = None) -> float: +def string_to_int64(string: str, radix: Optional[int] = None) -> int: if radix == 0: # Other values we reject are checked already. raise ValueError() diff --git a/be-rust/src/commonMain/kotlin/lang/temper/be/rust/RustExt.kt b/be-rust/src/commonMain/kotlin/lang/temper/be/rust/RustExt.kt index a75e8540..09aa379f 100644 --- a/be-rust/src/commonMain/kotlin/lang/temper/be/rust/RustExt.kt +++ b/be-rust/src/commonMain/kotlin/lang/temper/be/rust/RustExt.kt @@ -381,7 +381,7 @@ internal fun Rust.GenericParam.toArg(): Rust.Id { return when (this) { is Rust.Id -> this is Rust.TypeParam -> id - is Rust.PathSegments -> TODO() // needed? + is Rust.PathSegments -> segments.last() as Rust.Id }.deepCopy() } diff --git a/be-rust/src/commonMain/kotlin/lang/temper/be/rust/RustOperator.kt b/be-rust/src/commonMain/kotlin/lang/temper/be/rust/RustOperator.kt index dd26412b..6ea6a3ad 100644 --- a/be-rust/src/commonMain/kotlin/lang/temper/be/rust/RustOperator.kt +++ b/be-rust/src/commonMain/kotlin/lang/temper/be/rust/RustOperator.kt @@ -7,6 +7,8 @@ enum class RustOperator( val operatorDefinition: RustOperatorDefinition, ) { Assign("=", RustOperatorDefinition.Assignment), + LogicalAnd("&&", RustOperatorDefinition.LogicalAnd), + LogicalOr("||", RustOperatorDefinition.LogicalOr), And("&", RustOperatorDefinition.And), Or("|", RustOperatorDefinition.InclusiveOr), As("as", RustOperatorDefinition.As), diff --git a/be-rust/src/commonMain/kotlin/lang/temper/be/rust/RustSupportNetwork.kt b/be-rust/src/commonMain/kotlin/lang/temper/be/rust/RustSupportNetwork.kt index 9d6d29e4..4bce933e 100644 --- a/be-rust/src/commonMain/kotlin/lang/temper/be/rust/RustSupportNetwork.kt +++ b/be-rust/src/commonMain/kotlin/lang/temper/be/rust/RustSupportNetwork.kt @@ -146,13 +146,13 @@ private fun supportCodeByOperatorId(builtinOperatorId: BuiltinOperatorId?): Supp BuiltinOperatorId.NeIntInt -> neIntInt BuiltinOperatorId.NeStrStr -> neStrStr BuiltinOperatorId.NeGeneric -> neGeneric - BuiltinOperatorId.CmpFltFlt -> TODO() - BuiltinOperatorId.CmpIntInt -> TODO() - BuiltinOperatorId.CmpStrStr -> TODO() + BuiltinOperatorId.CmpFltFlt -> cmpFltFlt + BuiltinOperatorId.CmpIntInt -> CmpIntInt + BuiltinOperatorId.CmpStrStr -> CmpStrStrOrdering BuiltinOperatorId.CmpGeneric -> CmpGeneric - BuiltinOperatorId.Bubble -> TODO() // bubble + BuiltinOperatorId.Bubble -> bubble BuiltinOperatorId.Panic -> panic - BuiltinOperatorId.Print -> TODO() + BuiltinOperatorId.Print -> print BuiltinOperatorId.StrCat -> StrCat BuiltinOperatorId.Listify -> Listify BuiltinOperatorId.Async -> async @@ -583,6 +583,53 @@ private object CmpGeneric : MethodCall( } } +private val cmpFltFlt = FunctionCall("CmpFltFlt", "temper_core::float64::cmp", BuiltinOperatorId.CmpFltFlt) + +private object CmpIntInt : RustInlineSupportCode("CmpIntInt", BuiltinOperatorId.CmpIntInt, cloneEvenIfFirst = true) { + override fun inlineToTree( + pos: Position, + arguments: List>, + returnType: Type2, + translator: RustTranslator, + ): Rust.Expr { + // (a).cmp(&b) as i32 + val left = arguments[0].expr as Rust.Expr + val right = (arguments[1].expr as Rust.Expr).ref() + return left.methodCall("cmp", listOf(right)).infix(RustOperator.As, "i32".toId(pos)) + } +} + +private object CmpStrStrOrdering : + RustInlineSupportCode("CmpStrStr", BuiltinOperatorId.CmpStrStr, cloneEvenIfFirst = true) { + override fun inlineToTree( + pos: Position, + arguments: List>, + returnType: Type2, + translator: RustTranslator, + ): Rust.Expr { + val left = (arguments[0].expr as Rust.Expr).methodCall("as_str") + val right = (arguments[1].expr as Rust.Expr).methodCall("as_str") + return left.methodCall("cmp", listOf(right.ref())).infix(RustOperator.As, "i32".toId(pos)) + } +} + +private val bubble = FunctionCall("Bubble", "panic!", BuiltinOperatorId.Bubble) + +private object PrintCode : RustInlineSupportCode("Print", BuiltinOperatorId.Print) { + override fun inlineToTree( + pos: Position, + arguments: List>, + returnType: Type2, + translator: RustTranslator, + ): Rust.Expr { + // Print takes a single string argument and outputs it. + val arg = arguments[0].expr as Rust.Expr + return "println!".toId(pos).call(listOf(Rust.StringLiteral(pos, "{}"), arg)) + } +} + +private val print: SupportCode = PrintCode + private val dateToday = FunctionCall("Date::today", "temper_std::temporal::today") private val denseBitVectorConstructor = FunctionCall("DenseBitVector::constructor", "temper_core::DenseBitVector::with_capacity") diff --git a/be-rust/src/commonMain/kotlin/lang/temper/be/rust/RustTranslator.kt b/be-rust/src/commonMain/kotlin/lang/temper/be/rust/RustTranslator.kt index 6e6d975b..5c8ed3b2 100644 --- a/be-rust/src/commonMain/kotlin/lang/temper/be/rust/RustTranslator.kt +++ b/be-rust/src/commonMain/kotlin/lang/temper/be/rust/RustTranslator.kt @@ -1985,7 +1985,7 @@ class RustTranslator( is TmpL.GetProperty -> translateGetProperty(expression, avoidClone = avoidClone) is TmpL.InstanceOfExpression -> translateInstanceOfExpression(expression) is TmpL.InfixOperation -> translateInfixOperation(expression) - is TmpL.PrefixOperation -> TODO() + is TmpL.PrefixOperation -> translatePrefixOperation(expression) is TmpL.Reference -> translateReference(expression, avoidClone = avoidClone) is TmpL.RestParameterCountExpression -> TODO() is TmpL.RestParameterExpression -> TODO() @@ -2090,9 +2090,12 @@ class RustTranslator( ).wrapArcType() } - private fun translateGarbage(garbage: TmpL.Garbage): Rust.Call { - val pos = garbage.pos - return "panic!".toId(pos).call(listOf(Rust.StringLiteral(pos, "Garbage"))) + private fun translateGarbage(garbage: TmpL.Garbage): Rust.Expr = run { + translateUnsupported(garbage.pos, garbage.toString()) + } + + private fun translateGarbageStatement(garbage: TmpL.GarbageStatement): Rust.ExprStatement = run { + translateUnsupportedStatement(garbage) } private fun translateGetProperty(expression: TmpL.GetProperty, avoidClone: Boolean): Rust.Expr { @@ -2339,8 +2342,8 @@ class RustTranslator( return Rust.Operator( op.pos, operator = when (op.tmpLOperator) { - TmpLOperator.AmpAmp -> TODO() // RustOperator.LogicalAnd - TmpLOperator.BarBar -> TODO() // RustOperator.LogicalOr + TmpLOperator.AmpAmp -> RustOperator.LogicalAnd + TmpLOperator.BarBar -> RustOperator.LogicalOr TmpLOperator.EqEqInt -> RustOperator.Equals TmpLOperator.GeInt -> RustOperator.GreaterEquals TmpLOperator.GtInt -> RustOperator.GreaterThan @@ -2351,6 +2354,18 @@ class RustTranslator( ) } + private fun translatePrefixOperation(expr: TmpL.PrefixOperation): Rust.Expr { + val operator = when (expr.op.tmpLOperator) { + TmpLOperator.Bang -> RustOperator.BoolComplement + } + return Rust.Operation( + expr.pos, + left = null, + operator = Rust.Operator(expr.op.pos, operator), + right = translateExpression(expr.operand), + ) + } + private fun translateInstanceOfExpression(expression: TmpL.InstanceOfExpression): Rust.Expr { // TODO Bubbly found. Maybe some map expression? We don't ever expect bubbly wanted. val found = expression.expr.type.described() @@ -2588,8 +2603,8 @@ class RustTranslator( return when (member) { is TmpL.Getter -> translateGetterId(member) is TmpL.Setter -> translateSetterId(member) - is TmpL.NormalMethod -> translateNormalishMethodId(member) - else -> TODO() + is TmpL.Method -> translateNormalishMethodId(member) + else -> error("unexpected member type for translateMethodId: $member") } } @@ -2603,7 +2618,7 @@ class RustTranslator( typePub: Rust.VisibilityPub? = null, ): List { return when (member) { - is TmpL.GarbageStatement -> TODO() + is TmpL.GarbageStatement -> translateGarbageStatement(member).toItem() // won't compile but that's ok // So far only bother with returnType forwarding for instance methods. Will we need more later? is TmpL.Getter -> translateGetter(member, block = block, forTrait = forTrait, returnType = returnType) is TmpL.Setter -> translateSetter(member, block = block, forTrait = forTrait) // don't expect return type @@ -2892,7 +2907,10 @@ class RustTranslator( return when (statement.left.property) { is TmpL.ExternalPropertyId -> { val ref = statement.left - val subject = translateExpression((ref.subject as? TmpL.Expression) ?: TODO(), avoidClone = true) + val subject = when (val subj = ref.subject) { + is TmpL.Expression -> translateExpression(subj, avoidClone = true) + is TmpL.TypeName -> translateTypeName(subj) // wrong but also shouldn't happen + } val setter = "set_${translatePropertyId(ref.property)}" subject.methodCall(setter, listOf(value)) } @@ -2919,27 +2937,33 @@ class RustTranslator( try { return when (statement) { is TmpL.Assignment -> return translateAssignment(statement) - is TmpL.BoilerplateCodeFoldEnd -> TODO() - is TmpL.BoilerplateCodeFoldStart -> TODO() + is TmpL.BoilerplateCodeFoldEnd -> translateUnsupportedStatement(statement) + is TmpL.BoilerplateCodeFoldStart -> translateUnsupportedStatement(statement) is TmpL.BreakStatement -> translateBreakStatement(statement) is TmpL.ContinueStatement -> translateContinueStatement(statement) - is TmpL.EmbeddedComment -> TODO() + is TmpL.EmbeddedComment -> translateUnsupportedStatement(statement) is TmpL.ExpressionStatement -> translateExpressionStatement(statement) - is TmpL.GarbageStatement -> TODO() + is TmpL.GarbageStatement -> translateGarbageStatement(statement) is TmpL.HandlerScope -> error("handled elsewhere") is TmpL.LocalDeclaration -> return translateModuleOrLocalDeclaration(statement) - is TmpL.LocalFunctionDeclaration -> TODO() // handled elsewhere + is TmpL.LocalFunctionDeclaration -> error("handled elsewhere") is TmpL.ModuleInitFailed -> translateModuleInitFailed(statement) is TmpL.BlockStatement -> translateBlock(statement) is TmpL.ComputedJumpStatement -> translateComputedJumpStatement(statement) is TmpL.IfStatement -> return translateIfStatement(statement) is TmpL.LabeledStatement -> return translateLabeledStatement(statement) - is TmpL.TryStatement -> TODO() + // TryStatement and ThrowStatement are only generated by CatchBubble strategy; + // Rust uses IfHandlerScopeVar, so these should never appear. + is TmpL.TryStatement -> error("unexpected TryStatement: Rust uses IfHandlerScopeVar, not CatchBubble") is TmpL.WhileStatement -> translateWhileStatement(statement) is TmpL.ReturnStatement -> return translateReturnStatement(statement) is TmpL.SetProperty -> translateSetProperty(statement) - is TmpL.ThrowStatement -> TODO() - is TmpL.YieldStatement -> TODO() + is TmpL.ThrowStatement -> + error("unexpected ThrowStatement: Rust uses IfHandlerScopeVar, not CatchBubble") + // YieldStatement is only generated by TranslateToGenerator strategy; + // Rust uses TranslateToRegularFunction, which deletes yields during conversion. + is TmpL.YieldStatement -> + error("unexpected YieldStatement: Rust uses TranslateToRegularFunction, not TranslateToGenerator") }.let { listOf(it) } } catch (_: NeverRefException) { // No statements referencing things that are typed never. @@ -3047,11 +3071,19 @@ class RustTranslator( // Otherwise handle non-connected types. return when (type) { is TmpL.FunctionType -> translateFunctionType(type) - is TmpL.TypeIntersection -> TODO() + is TmpL.TypeIntersection -> when { + // This branch is never expected, so some this translation is untested. + type.types.isEmpty() -> ANY_NAME.toId(pos) + type.types.size == 1 -> translateType(type.types.first(), inExpr = inExpr, isFlex = isFlex) + else -> Rust.ImplTraitType( + pos, + bounds = type.types.map { translateType(it, inExpr = inExpr, isFlex = isFlex) as Rust.Path }, + ) + } is TmpL.TypeUnion -> translateTypeUnion(type, isFlex = isFlex) is TmpL.GarbageType -> "()".toId(pos) is TmpL.NominalType -> translateTypeNominal(type, inExpr = inExpr, isFlex = isFlex) - is TmpL.BubbleType -> TODO() + is TmpL.BubbleType -> "()".toId(pos) is TmpL.NeverType -> "!".toId(pos) // except not actually supported in stable rust is TmpL.TopType -> ANY_NAME.toId(pos) } @@ -3309,6 +3341,18 @@ class RustTranslator( ) } + private fun translateUnsupported(pos: Position, diagnostic: String): Rust.Expr = run { + "panic!".toId(pos).call(listOf(Rust.StringLiteral(pos, "Unsupported: $diagnostic"))) + } + + private fun translateUnsupported(tree: TmpL.Tree): Rust.Expr = run { + translateUnsupported(tree.pos, tree.toString()) + } + + private fun translateUnsupportedStatement(statement: TmpL.Statement): Rust.ExprStatement = run { + Rust.ExprStatement(statement.pos, translateUnsupported(statement)) + } + private fun translateValueReference(expression: TmpL.ValueReference): Rust.Expr { val pos = expression.pos return when (expression.value.typeTag) { diff --git a/be-rust/src/commonMain/resources/lang/temper/be/rust/temper-core/src/float64.rs b/be-rust/src/commonMain/resources/lang/temper/be/rust/temper-core/src/float64.rs index dfdae2f6..0a4e90fd 100644 --- a/be-rust/src/commonMain/resources/lang/temper/be/rust/temper-core/src/float64.rs +++ b/be-rust/src/commonMain/resources/lang/temper/be/rust/temper-core/src/float64.rs @@ -44,7 +44,7 @@ pub fn near(x: f64, y: f64, rel_tol: Option, abs_tol: Option) -> bool let rel_tol = rel_tol.unwrap_or(1e-9); let abs_tol = abs_tol.unwrap_or(0.0); let margin = (x.abs().max(y.abs()) * rel_tol).max(abs_tol); - (x - y).abs() < margin + (x - y).abs() <= margin } pub fn rem(x: f64, y: f64) -> Result { diff --git a/be/src/commonMain/kotlin/lang/temper/be/tmpl/TmpLHelpers.kt b/be/src/commonMain/kotlin/lang/temper/be/tmpl/TmpLHelpers.kt index 7dc0a68b..4cb2ffbc 100644 --- a/be/src/commonMain/kotlin/lang/temper/be/tmpl/TmpLHelpers.kt +++ b/be/src/commonMain/kotlin/lang/temper/be/tmpl/TmpLHelpers.kt @@ -529,7 +529,31 @@ val TmpL.Type.withoutBubbleOrNull: TmpL.Type get() = this.withoutAtom { } fun TmpL.Type.withoutAtom(predicate: (TmpL.Type) -> Boolean): TmpL.Type = when (this) { - is TmpL.TypeIntersection -> this + is TmpL.TypeIntersection -> { + var hasDifferences = false + val typesWithout: List = buildList { + types.mapNotNullTo(this@buildList) { + val t = it.withoutAtom(predicate) + if (t is TmpL.NeverType) { + hasDifferences = true + null + } else { + if (t !== it) { hasDifferences = true } + t + } + } + } + + if (hasDifferences) { + when (typesWithout.size) { + 0 -> TmpL.NeverType(pos) + 1 -> typesWithout.first().deepCopy() + else -> TmpL.TypeIntersection(pos, typesWithout.map { it.deepCopy() }) + } + } else { + this + } + } is TmpL.TypeUnion -> { var hasDifferences = false val typesWithout: List = buildList { diff --git a/common/src/commonMain/kotlin/lang/temper/common/Memoized.kt b/common/src/commonMain/kotlin/lang/temper/common/Memoized.kt index 3e5fb790..d74bec6a 100644 --- a/common/src/commonMain/kotlin/lang/temper/common/Memoized.kt +++ b/common/src/commonMain/kotlin/lang/temper/common/Memoized.kt @@ -1,5 +1,7 @@ package lang.temper.common +import kotlin.jvm.Synchronized + /** * A thunk that calls its [function argument][f] at most once. * It may be called like a function, and the first time it is called, it delegates to [f], @@ -10,6 +12,7 @@ class Memoized( ) { private var cached: List? = null + @Synchronized operator fun invoke(): T = ( cached ?: run { diff --git a/compile/src/commonMain/kotlin/lang/temper/compile/fetch/GitCache.kt b/compile/src/commonMain/kotlin/lang/temper/compile/fetch/GitCache.kt index d87f9525..fc796738 100644 --- a/compile/src/commonMain/kotlin/lang/temper/compile/fetch/GitCache.kt +++ b/compile/src/commonMain/kotlin/lang/temper/compile/fetch/GitCache.kt @@ -116,7 +116,7 @@ internal fun makeWritable(root: Path) { var exception: Throwable? = null repeat(2) { runCatching { - Files.walk(root).forEach { it.toFile().setWritable(true, true) } + Files.walk(root).use { it.forEach { path -> path.toFile().setWritable(true, true) } } // It worked, so get out of here. return@makeWritable }.onFailure { exception = it } diff --git a/frontend/src/commonMain/kotlin/lang/temper/frontend/staging/ModuleAdvancer.kt b/frontend/src/commonMain/kotlin/lang/temper/frontend/staging/ModuleAdvancer.kt index 3cf55116..f2a66ebc 100644 --- a/frontend/src/commonMain/kotlin/lang/temper/frontend/staging/ModuleAdvancer.kt +++ b/frontend/src/commonMain/kotlin/lang/temper/frontend/staging/ModuleAdvancer.kt @@ -1121,6 +1121,7 @@ private fun checkForImportCycles( if (pendingByImporter.isEmpty()) { return null } for (importer in pendingByImporter.keys) { val loc = importer.loc + val visited = mutableSetOf() fun lookForCycle(possibleCycle: Cons.NotEmpty): Cons.NotEmpty? { val import = possibleCycle.head val exporter = import.exporter @@ -1128,6 +1129,9 @@ private fun checkForImportCycles( if (exporterLoc == loc) { return possibleCycle } + if (!visited.add(exporterLoc)) { + return null + } // For Modules, importers are also exporters val importsForExporter = pendingByImporter[ exporter as? Importer, diff --git a/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/InlineToRepairUnrealizedGoals.kt b/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/InlineToRepairUnrealizedGoals.kt index 7b189cc1..bddb2fda 100644 --- a/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/InlineToRepairUnrealizedGoals.kt +++ b/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/InlineToRepairUnrealizedGoals.kt @@ -310,7 +310,6 @@ private class InlineToRepairUnrealizedGoals( val calleeTree = callToInline.definition val formalNameToArg = callToInline.formalNameToFunArg val thisBindings = callToInline.thisTypeBindings - callToInline.thisTypeBindings val combinedCallArgs = callToInline.combinedCallArgs val callEdge = call.incoming!! diff --git a/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/Typer.kt b/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/Typer.kt index d5300b09..4bfe600c 100644 --- a/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/Typer.kt +++ b/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/Typer.kt @@ -3234,7 +3234,7 @@ private fun mergeTypeArgs(from: StaticType?, into: StaticType): StaticType { type.definition === WellKnownTypes.nullTypeDefinition -> null else -> type } - is OrType -> type.members.firstNotNullOf { findNominal(it) } + is OrType -> type.members.firstNotNullOfOrNull { findNominal(it) } else -> null } } diff --git a/frontend/src/commonMain/resources/implicits/Implicits.temper b/frontend/src/commonMain/resources/implicits/Implicits.temper index 9f09ce75..063075fd 100644 --- a/frontend/src/commonMain/resources/implicits/Implicits.temper +++ b/frontend/src/commonMain/resources/implicits/Implicits.temper @@ -1581,7 +1581,7 @@ export class Float64 extends Equatable { absTol: builtins.Float64 = 0.0, ): builtins.Boolean { let margin = (content.abs().max(other.abs()) * relTol).max(absTol); - (content - other).abs() < margin + (content - other).abs() <= margin } @connected("Float64::round") diff --git a/functional-test-suite/src/commonMain/resources/types/float/ops/ops.temper.md b/functional-test-suite/src/commonMain/resources/types/float/ops/ops.temper.md index 39e34700..93735206 100644 --- a/functional-test-suite/src/commonMain/resources/types/float/ops/ops.temper.md +++ b/functional-test-suite/src/commonMain/resources/types/float/ops/ops.temper.md @@ -77,8 +77,11 @@ But also a bit of testing of `near` params. We have both `relTol` and `absTol` tolerance options, in that order. Either or both can be specified, although we try only one or the other here. - console.log("one.near(one + 0.1, absTol = 0.11): ${ - one.near(one + 0.1, null, 0.11).toString() + console.log("one.near(one + 0.25, absTol = 0.25): ${ + one.near(one + 0.25, null, 0.25).toString() + }"); + console.log("one.near(one + 0.1 + 1e-9, absTol = 0.1): ${ + one.near(one + 0.1 + 1e-9, null, 0.1).toString() }"); console.log("one.near(one - 0.1, absTol = 0.11): ${ one.near(one - 0.1, null, 0.11).toString() @@ -127,7 +130,8 @@ one.sinh(): ✅ (three * pi / four).tan(): ✅ one.tanh(): ✅ one.near(one + 0.1): false -one.near(one + 0.1, absTol = 0.11): true +one.near(one + 0.25, absTol = 0.25): true +one.near(one + 0.1 + 1e-9, absTol = 0.1): false one.near(one - 0.1, absTol = 0.11): true ten.near(ten + 0.1, absTol = 0.011): false ten.near(ten + 0.1, relTol = 0.011): true diff --git a/lexer/src/commonMain/kotlin/lang/temper/lexer/Lexer.kt b/lexer/src/commonMain/kotlin/lang/temper/lexer/Lexer.kt index cc683e5e..fddc012c 100644 --- a/lexer/src/commonMain/kotlin/lang/temper/lexer/Lexer.kt +++ b/lexer/src/commonMain/kotlin/lang/temper/lexer/Lexer.kt @@ -1107,7 +1107,7 @@ class Lexer( // If a '<' is adjacent to a '<' or '=', treat it as part of a // larger shift or comparison operator, or close tag start marker. val before = text[end - 1] - val after = if (end + 1 < limit) { text[end - 1] } else { '\u0000' } + val after = if (end + 1 < limit) { text[end + 1] } else { '\u0000' } if ( !(before == '=' || before == '<' || after == '=' || after == '<') ) { diff --git a/tooling/src/commonMain/kotlin/lang/temper/tooling/buildrun/Runner.kt b/tooling/src/commonMain/kotlin/lang/temper/tooling/buildrun/Runner.kt index e8f3ff6d..73bdc08b 100644 --- a/tooling/src/commonMain/kotlin/lang/temper/tooling/buildrun/Runner.kt +++ b/tooling/src/commonMain/kotlin/lang/temper/tooling/buildrun/Runner.kt @@ -157,5 +157,6 @@ internal class SynchronizedPrintBuffer { @Synchronized fun append(str: String) { buffer.append(str) } + @Synchronized override fun toString(): String = "$buffer" }