From fb83821d14428552d8acc8c0e04146122375eff2 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Tue, 13 Jul 2021 18:29:11 -0600 Subject: [PATCH] WIP overhaul operand and variadic functions --- src/kiss/Helpers.hx | 6 -- src/kiss/Kiss.hx | 20 +++++- src/kiss/KissInterp.hx | 8 +-- src/kiss/Macros.hx | 83 ++------------------- src/kiss/Operand.hx | 92 ------------------------ src/kiss/Prelude.hx | 159 +++++++++++++++++++---------------------- 6 files changed, 97 insertions(+), 271 deletions(-) delete mode 100644 src/kiss/Operand.hx diff --git a/src/kiss/Helpers.hx b/src/kiss/Helpers.hx index 4ef3bdf..7f698de 100644 --- a/src/kiss/Helpers.hx +++ b/src/kiss/Helpers.hx @@ -257,10 +257,6 @@ class Helpers { interp.variables.set("kiss", { ReaderExp: { ReaderExpDef: ReaderExpDef - }, - Operand: { - toDynamic: Operand.toDynamic, - fromDynamic: Operand.fromDynamic } }); interp.variables.set("k", k.forHScript()); @@ -407,8 +403,6 @@ class Helpers { meta: (m:String, exp:ReaderExp) -> MetaExp(m, exp).withPosOf(posRef), field: (f:String, exp:ReaderExp) -> FieldExp(f, exp).withPosOf(posRef), keyValue: (key:ReaderExp, value:ReaderExp) -> KeyValueExp(key, value).withPosOf(posRef), - opToDynamic: (operandExp:ReaderExp) -> CallExp(Symbol("kiss.Operand.toDynamic").withPosOf(posRef), [operandExp]).withPosOf(posRef), - opFromDynamic: (operandExp:ReaderExp) -> CallExp(Symbol("kiss.Operand.fromDynamic").withPosOf(posRef), [operandExp]).withPosOf(posRef), begin: (exps:Array) -> CallExp(Symbol("begin").withPosOf(posRef), exps).withPosOf(posRef) }; } diff --git a/src/kiss/Kiss.hx b/src/kiss/Kiss.hx index 5ec83fb..3f493a1 100644 --- a/src/kiss/Kiss.hx +++ b/src/kiss/Kiss.hx @@ -51,6 +51,7 @@ class Kiss { wrapListExps: true, loadedFiles: new Map(), // Helpful built-in aliases + // These ones might conflict with a programmer's variable names, so they only apply in call expressions: callAliases: [ "print" => Symbol("Prelude.print"), "sort" => Symbol("Prelude.sort"), @@ -66,9 +67,24 @@ class Kiss { "filter" => Symbol("Lambda.filter"), // TODO use truthy as the default filter function "flatten" => Symbol("Lambda.flatten"), "has" => Symbol("Lambda.has"), - "count" => Symbol("Lambda.count") + "count" => Symbol("Lambda.count"), + "min" => Symbol("Prelude.min"), + "max" => Symbol("Prelude.max") + ], + // These ones won't conflict with variables and might commonly be used with (apply) + identAliases: [ + "+" => Symbol("Prelude.add"), + "-" => Symbol("Prelude.subtract"), + "*" => Symbol("Prelude.multiply"), + "/" => Symbol("Prelude.divide"), + "%" => Symbol("Prelude.mod"), + "^" => Symbol("Prelude.pow"), + ">" => Symbol("Prelude.greaterThan"), + ">=" => Symbol("Prelude.greaterEqual"), + "<" => Symbol("Prelude.lessThan"), + "<=" => Symbol("Prelude.lesserEqual"), + "=" => Symbol("Prelude.areEqual") ], - identAliases: new Map(), fields: [], loadingDirectory: "", hscript: false diff --git a/src/kiss/KissInterp.hx b/src/kiss/KissInterp.hx index b251774..17fa01b 100644 --- a/src/kiss/KissInterp.hx +++ b/src/kiss/KissInterp.hx @@ -20,17 +20,13 @@ class KissInterp extends Interp { this.nullForUnknownVar = nullForUnknownVar; variables.set("Prelude", Prelude); - variables.set("kiss", { - Operand: { - fromDynamic: Operand.fromDynamic, - toDynamic: Operand.toDynamic - } - }); variables.set("Lambda", Lambda); variables.set("Std", Std); variables.set("Keep", ExtraElementHandling.Keep); variables.set("Drop", ExtraElementHandling.Drop); variables.set("Throw", ExtraElementHandling.Throw); + // Might eventually need to simulate types in the namespace: + variables.set("kiss", {}); } override function resolve(id:String):Dynamic { diff --git a/src/kiss/Macros.hx b/src/kiss/Macros.hx index 52db6c7..a4b8046 100644 --- a/src/kiss/Macros.hx +++ b/src/kiss/Macros.hx @@ -65,67 +65,17 @@ class Macros { }; } - macros["%"] = (wholeExp:ReaderExp, exps:Array, k) -> { - wholeExp.checkNumArgs(2, 2, '(% [divisor] [dividend])'); - var b = wholeExp.expBuilder(); - b.opToDynamic( - b.call( - b.symbol("Prelude.mod"), [ - b.opFromDynamic(exps[1]), - b.opFromDynamic(exps[0]) - ])); - }; - destructiveVersion("%", "%="); - - macros["^"] = (wholeExp:ReaderExp, exps:Array, k) -> { - wholeExp.checkNumArgs(2, 2, '(^ [base] [exponent])'); - var b = wholeExp.expBuilder(); - b.opToDynamic( - b.call(b.symbol("Prelude.pow"), [ - b.opFromDynamic(exps[1]), - b.opFromDynamic(exps[0]) - ])); - }; destructiveVersion("^", "^="); - - macros["+"] = variadicMacro("Prelude.add"); destructiveVersion("+", "+="); - - macros["-"] = variadicMacro("Prelude.subtract"); destructiveVersion("-", "-="); - - macros["*"] = variadicMacro("Prelude.multiply"); destructiveVersion("*", "*="); - - macros["/"] = variadicMacro("Prelude.divide"); destructiveVersion("/", "/="); - macros["min"] = variadicMacro("Prelude.min"); - macros["max"] = variadicMacro("Prelude.max"); - - macros[">"] = variadicMacro("Prelude.greaterThan"); - macros[">="] = variadicMacro("Prelude.greaterEqual"); - macros["<"] = variadicMacro("Prelude.lessThan"); - macros["<="] = variadicMacro("Prelude.lesserEqual"); - - macros["="] = variadicMacro("Prelude.areEqual"); - - // the (apply [func] [args]) macro keeps its own list of aliases for the math operators - // that can't just be function aliases because they emulate &rest behavior + // These shouldn't be ident aliases because they are common variable names var opAliases = [ - "+" => "Prelude.add", - "-" => "Prelude.subtract", - "*" => "Prelude.multiply", - "/" => "Prelude.divide", - ">" => "Prelude.greaterThan", - ">=" => "Prelude.greaterEqual", - "<" => "Prelude.lessThan", - "<=" => "Prelude.lesserEqual", - "=" => "Prelude.areEqual", - "max" => "Prelude.max", "min" => "Prelude.min", - "zip" => "Prelude.zip" + "max" => "Prelude.max" ]; macros["apply"] = (wholeExp:ReaderExp, exps:Array, k) -> { @@ -139,22 +89,12 @@ class Macros { b.symbol("null"); }; var func = switch (exps[0].def) { - case Symbol(sym) if (opAliases.exists(sym)): - b.symbol(opAliases[sym]); + case Symbol(func) if (opAliases.exists(func)): + b.symbol(opAliases[func]); default: exps[0]; }; - var args = switch (exps[0].def) { - case Symbol(sym) if (opAliases.exists(sym)): - b.list([ - b.call( - b.field("map", exps[1]), [ - b.symbol("kiss.Operand.fromDynamic") - ]) - ]); - default: - exps[1]; - }; + var args = exps[1]; b.call( b.symbol("Reflect.callMethod"), [ callOn, func, args @@ -833,17 +773,4 @@ class Macros { throw CompileError.fromExp(exps[0], 'top-level expression of (cond... ) must be a call list starting with a condition expression'); }; } - - static function variadicMacro(func:String):MacroFunction { - return (wholeExp:ReaderExp, exps:Array, k) -> { - var b = wholeExp.expBuilder(); - b.callSymbol(func, [ - b.list([ - for (exp in exps) { - b.callSymbol("kiss.Operand.fromDynamic", [exp]); - } - ]) - ]); - }; - } } diff --git a/src/kiss/Operand.hx b/src/kiss/Operand.hx deleted file mode 100644 index cb55f0b..0000000 --- a/src/kiss/Operand.hx +++ /dev/null @@ -1,92 +0,0 @@ -package kiss; - -import haxe.ds.Either; - -/** - Arithmetic operands -**/ -abstract Operand(Either) from Either to Either { - @:from inline static function fromString(s:String):Operand { - return Left(s); - } - - @:from inline static function fromInt(i:Int):Operand { - return Right(0.0 + i); - } - - @:from inline static function fromFloat(f:Float):Operand { - return Right(0.0 + f); - } - - @:from inline static function fromBool(b:Bool):Operand { - return if (b == true) Right(Math.POSITIVE_INFINITY) else Right(Math.NEGATIVE_INFINITY); - } - - // Doing this one implicitly just wasn't working in conjunction with Lambda.fold - - /* @:from */ - public static function fromDynamic(d:Dynamic):Operand { - return switch (Type.typeof(d)) { - case TInt | TFloat: Right(0.0 + d); - // Treating true and false as operands can be useful for equality. In practice, no one should use them - // as operands for any other reason - case TBool: - fromBool(d); - default: - if (Std.isOfType(d, String)) { - Left(d); - } - // Taking a gamble here that no one will ever try to pass a different kind of Either as an operand, - // because at runtime this is as specific as the check can get. - else if (Std.isOfType(d, Either)) { - d; - } else { - throw '$d cannot be converted to Operand'; - }; - }; - } - - @:to public inline function toString():Null { - return switch (this) { - case Left(s): s; - default: null; - }; - } - - @:to public inline function toFloat():Null { - return switch (this) { - case Right(f): f; - default: null; - }; - } - - @:to public inline function toInt():Null { - return switch (this) { - case Right(f): Math.floor(f); - default: null; - }; - } - - @:to public inline function toBool():Null { - return switch (this) { - case Right(f) if (f == Math.POSITIVE_INFINITY): true; - case Right(f) if (f == Math.NEGATIVE_INFINITY): false; - default: null; - } - } - - // This wasn't working as an implicit conversion in conjunction with Lambda.fold() - - /* @:to */ - public static function toDynamic(o:Dynamic):Null { - return if (Std.isOfType(o, Either)) { - var o:Operand = cast o; - switch (o) { - case Right(f): f; - case Left(str): str; - }; - } else { - o; - }; - } -} diff --git a/src/kiss/Prelude.hx b/src/kiss/Prelude.hx index 14f5c60..9ade846 100644 --- a/src/kiss/Prelude.hx +++ b/src/kiss/Prelude.hx @@ -2,7 +2,6 @@ package kiss; using Std; -import kiss.Operand; import kiss.ReaderExp; import haxe.ds.Either; import haxe.Constraints; @@ -25,46 +24,39 @@ enum ExtraElementHandling { } class Prelude { - static function variadic(op:(Operand, Operand) -> Null, comparison = false):(Array) -> Dynamic { - return (l:kiss.List) -> switch (Lambda.fold(l.slice(1), op, l[0])) { - case null: - false; - case somethingElse if (comparison): - true; - case somethingElse: - Operand.toDynamic(somethingElse); + static function stringOrFloat(d:Dynamic):Either { + return switch (Type.typeof(d)) { + case TInt | TFloat: Right(0.0 + d); + default: + if (Std.isOfType(d, String)) { + Left(d); + } else { + throw 'cannot use $d in multiplication'; + }; }; } // Kiss arithmetic will incur overhead because of these switch statements, but the results will not be platform-dependent - static function _add(a:Operand, b:Operand):Operand { - return switch ([a, b]) { - case [Left(str), Left(str2)]: - Left(str2 + str); - case [Right(f), Right(f2)]: - Right(f + f2); - default: - throw 'cannot add mismatched types ${Operand.toDynamic(a)} and ${Operand.toDynamic(b)}'; - }; + static function _add(values:Array):Dynamic { + var sum:Dynamic = values[0]; + for (value in values.slice(1)) sum += value; + return sum; } - public static var add = variadic(_add); + public static var add:Function = Reflect.makeVarArgs(_add); - static function _subtract(val:Operand, from:Operand):Operand { - return switch ([from, val]) { - case [Right(from), Right(val)]: - Right(from - val); - default: - throw 'cannot subtract ${Operand.toDynamic(val)} from ${Operand.toDynamic(from)}'; - } + static function _subtract(values:Array):Dynamic { + var difference:Float = values[0]; + for (value in values.slice(1)) difference -= value; + return difference; } - public static var subtract = variadic(_subtract); + public static var subtract:Function = Reflect.makeVarArgs(_subtract); - static function _multiply(a:Operand, b:Operand):Operand { - return switch ([a, b]) { + static function _multiply2(a:Dynamic, b:Dynamic):Dynamic { + return switch ([stringOrFloat(a), stringOrFloat(b)]) { case [Right(f), Right(f2)]: - Right(f * f2); + f * f2; case [Left(a), Left(b)]: throw 'cannot multiply strings "$a" and "$b"'; case [Right(i), Left(s)] | [Left(s), Right(i)] if (i % 1 == 0): @@ -72,81 +64,75 @@ class Prelude { for (_ in 0...Math.floor(i)) { result += s; } - Left(result); + result; default: - throw 'cannot multiply ${Operand.toDynamic(a)} and ${Operand.toDynamic(b)}'; + throw 'cannot multiply $a and $b'; }; } - public static var multiply = variadic(_multiply); - - static function _divide(bottom:Operand, top:Operand):Operand { - return switch ([top, bottom]) { - case [Right(f), Right(f2)]: - Right(f / f2); - default: - throw 'cannot divide $top by $bottom'; - }; + static function _multiply(values:Array):Dynamic { + var product = values[0]; + for (value in values.slice(1)) product = _multiply2(product, value); + return product; } - public static var divide = variadic(_divide); + public static var multiply:Function = Reflect.makeVarArgs(_multiply); - public static function mod(bottom:Operand, top:Operand):Operand { - return Right(top.toFloat() % bottom.toFloat()); + static function _divide(values:Array):Dynamic { + var quotient:Float = values[0]; + for (value in values.slice(1)) quotient /= value; + return quotient; } - public static function pow(exponent:Operand, base:Operand):Operand { - return Right(Math.pow(base.toFloat(), exponent.toFloat())); + public static var divide:Function = Reflect.makeVarArgs(_divide); + + public static function mod(top:Dynamic, bottom:Dynamic):Dynamic { + return top % bottom; } - static function _min(a:Operand, b:Operand):Operand { - return Right(Math.min(a.toFloat(), b.toFloat())); + public static function pow(base:Dynamic, exponent:Dynamic):Dynamic { + return Math.pow(base, exponent); } - public static var min = variadic(_min); - - static function _max(a:Operand, b:Operand):Operand { - return Right(Math.max(a.toFloat(), b.toFloat())); + static function _min(values:Array):Dynamic { + var min = values[0]; + for (value in values.slice(1)) min = Math.min(min, value); + return min; } - public static var max = variadic(_max); + public static var min:Function = Reflect.makeVarArgs(_min); - static function _greaterThan(b:Operand, a:Operand):Null { - return if (a == null || b == null) null else if (a.toFloat() > b.toFloat()) b else null; + static function _max(values:Array):Dynamic { + var max = values[0]; + for (value in values.slice(1)) max = Math.max(max, value); + return max; } - public static var greaterThan = variadic(_greaterThan, true); + public static var max:Function = Reflect.makeVarArgs(_max); - static function _greaterEqual(b:Operand, a:Operand):Null { - return if (a == null || b == null) null else if (a.toFloat() >= b.toFloat()) b else null; + static function _comparison(op:String, values:Array):Bool { + for (idx in 1...values.length) { + var a = values[idx-1]; + var b = values[idx]; + var check = switch (op) { + case ">": a > b; + case ">=": a >= b; + case "<": a < b; + case "<=": a <= b; + case "==": a == b; + default: throw 'Unreachable case'; + } + if (!check) + return false; + } + return true; } - public static var greaterEqual = variadic(_greaterEqual, true); - - static function _lessThan(b:Operand, a:Operand):Null { - return if (a == null || b == null) null else if (a.toFloat() < b.toFloat()) b else null; - } - - public static var lessThan = variadic(_lessThan, true); - - static function _lesserEqual(b:Operand, a:Operand):Null { - return if (a == null || b == null) null else if (a.toFloat() <= b.toFloat()) b else null; - } - - public static var lesserEqual = variadic(_lesserEqual, true); - - static function _areEqual(a:Operand, b:Operand):Null { - return if (a == null || b == null) null else switch ([a, b]) { - case [Left(aStr), Left(bStr)] if (aStr == bStr): - a; - case [Right(aFloat), Right(bFloat)] if (aFloat == bFloat): - a; - default: - null; - }; - } - - public static var areEqual = variadic(_areEqual, true); + public static var greaterThan:Function = Reflect.makeVarArgs(_comparison.bind(">")); + public static var greaterEqual:Function = Reflect.makeVarArgs(_comparison.bind(">=")); + public static var lessThan:Function = Reflect.makeVarArgs(_comparison.bind("<")); + public static var lesserEqual:Function = Reflect.makeVarArgs(_comparison.bind("<=")); + public static var areEqual:Function = Reflect.makeVarArgs(_comparison.bind("==")); public static function sort(a:Array, ?comp:(T, T) -> Int):kiss.List { if (comp == null) @@ -181,7 +167,6 @@ class Prelude { public static function zip(arrays:Array>, extraHandling = Drop):kiss.List> { var lengthsAreEqual = true; var lengths = [for (arr in arrays) arr.length]; - var lengthOperands = [for (length in lengths) Operand.fromDynamic(length)]; for (idx in 1...lengths.length) { if (lengths[idx] != lengths[idx - 1]) { lengthsAreEqual = false; @@ -193,9 +178,9 @@ class Prelude { case Throw: throw 'zip was given lists of mis-matched size: $arrays'; case Keep: - Operand.toDynamic(Prelude.max(lengthOperands)); + Prelude.max(lengths); case Drop: - Operand.toDynamic(Prelude.min(lengthOperands)); + Prelude.min(lengths); } } else { lengths[0];