From 9a24fb5092a32b75989e245e076ee0d4ef234155 Mon Sep 17 00:00:00 2001 From: Julian Hyde Date: Tue, 24 Sep 2024 16:42:56 -0700 Subject: [PATCH] [MOREL-228] Int structure Implement the `Int` structure from the standard basis library. (The `Int` structure is an instance of the `INTEGER` signature in the Standard Basis Library but Morel does not currently have signatures.) Fixes #228 --- docs/reference.md | 38 ++- .../net/hydromatic/morel/compile/BuiltIn.java | 113 +++++++++ .../java/net/hydromatic/morel/eval/Codes.java | 222 ++++++++++++++++-- src/test/resources/script/builtIn.smli | 142 ++++++++++- 4 files changed, 482 insertions(+), 33 deletions(-) diff --git a/docs/reference.md b/docs/reference.md index 2ebe040d..4e2ab13a 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -306,6 +306,32 @@ Exception: | General.op o | (β → γ) (α → β) → α → γ | "f o g" is the function composition of `f` and `g`. Thus, `(f o g) a` is equivalent to `f (g a)`. | | Interact.use | string → unit | "use f" loads source text from the file named `f`. | | Interact.useSilently | string → unit | "useSilently f" loads source text from the file named `f`, without printing to stdout. | +| Int op * | int * int → int | "i * j" is the product of `i` and `j`. It raises Overflow when the result is not representable. | +| Int op + | int * int → int | "i + j" is the sum of `i` and `j`. It raises Overflow when the result is not representable. | +| Int op - | int * int → int | "i - j" is the difference of `i` and `j`. It raises Overflow when the result is not representable. | +| Int op div | int * int → int | "i div j" returns the greatest integer less than or equal to the quotient of i by j, i.e., `floor(i / j)`. It raises Overflow when the result is not representable, or Div when `j = 0`. Note that rounding is towards negative infinity, not zero. | +| Int op mod | int * int → int | "i mod j" returns the remainder of the division of i by j. It raises Div when `j = 0`. When defined, `(i mod j)` has the same sign as `j`, and `(i div j) * j + (i mod j) = i`. | +| Int op < | int * int → bool | "x < y" returns true if x is less than y. Return false on unordered arguments, i.e., if either argument is NaN, so that the usual reversal of comparison under negation does not hold, e.g., `a < b` is not the same as `not (a >= b)`. | +| Int op <= | int * int → bool | As "<" | +| Int op > | int * int → bool | As "<" | +| Int op >= | int * int → bool | As "<" | +| Int op ~ | int → int | "~ i" returns the negation of `i`. | +| Int.abs | int → int | "abs i" returns the absolute value of `i`. | +| Int.compare | int * int → order | "compare (i, j)" returns `LESS`, `EQUAL`, or `GREATER` according to whether its first argument is less than, equal to, or greater than the second. | +| Int.fromInt, int | int → int | "fromInt i" converts a value from type `int` to the default integer type. Raises Overflow if the value does not fit. | +| Int.fromString | string → int option | "fromString s" scans a `int` value from a string. Returns `SOME(r)` if a `int` value can be scanned from a prefix of `s`, ignoring any initial whitespace; otherwise, it returns `NONE`. Equivalent to `StringCvt.scanString (scan StringCvt.DEC)`. | +| Int.max | int * int → int | "max (i, j)" returns the larger of the arguments. | +| Int.maxInt | int | "maxInt" is the maximal (most positive) integer representable by `int`. If a value is `NONE`, `int` can represent all positive integers, within the limits of the heap size. If `precision` is `SOME(n)`, then we have `maxInt` = 2(n-1) - 1. | +| Int.min | int * int → int | "min (i, j)" returns the smaller of the arguments. | +| Int.minVal | int | "minPos" is the minimal (most negative) integer representable by `int`. If a value is `NONE`, `int` can represent all negative integers, within the limits of the heap size. If `precision` is `SOME(n)`, then we have `minInt` = -2(n-1). | +| Int.mod | int * int → int | "mod (i, j)" returns the remainder of the division of `i` by `j`. It raises `Div` when `j = 0`. When defined, `(i mod j)` has the same sign as `j, and `(i div j) * j + (i mod j) = i`. | +| Int.precision | int | "precision" is the precision. If `SOME(n)`, this denotes the number `n` of significant bits in type `int`, including the sign bit. If it is `NONE`, int has arbitrary precision. The precision need not necessarily be a power of two. | +| Int.quot | int * int → int | "quot (i, j)" returns the truncated quotient of the division of `i` by `j`, i.e., it computes `(i / j)` and then drops any fractional part of the quotient. It raises `Overflow` when the result is not representable, or `Div` when `j = 0`. Note that unlike `div`, `quot` rounds towards zero. In addition, unlike `div` and `mod`, neither `quot` nor `rem` are infix by default; an appropriate infix declaration would be `infix 7 quot rem`. This is the semantics of most hardware divide instructions, so `quot` may be faster than `div`. | +| Int.rem | int * int → int | "rem (i, j)" returns the remainder of the division of `i` by `j`. It raises `Div` when `j = 0`. `(i rem j)` has the same sign as i, and it holds that `(i quot j) * j + (i rem j) = i`. This is the semantics of most hardware divide instructions, so `rem` may be faster than `mod`. | +| Int.sameSign | int * int → bool | "sameSign (i, j)" returns true if `i` and `j` have the same sign. It is equivalent to `(sign i = sign j)`. | +| Int.sign | int → int | "sign i" returns ~1, 0, or 1 when `i` is less than, equal to, or greater than 0, respectively. | +| Int.toInt | int → int | "toInt i" converts a value from the default integer type to type `int`. Raises Overflow if the value does not fit. | +| Int.toString | int → string | "toString i" converts a `int` into a `string`; equivalent to `(fmt StringCvt.DEC r)`. | | List.nil | α list | "nil" is the empty list. | | List.null | α list → bool | "null l" returns `true` if the list `l` is empty. | | List.length | α list → int | "length l" returns the number of elements in the list `l`. | @@ -355,7 +381,7 @@ Exception: | Option.composePartial | (α → β option) * (γ → α option) → γ → β option | "composePartial (f, g) a" returns `NONE` if `g(a)` is `NONE`; otherwise, if `g(a)` is `SOME v`, returns `f(v)`. | | Option.map | α → β) → α option → β option | "map f opt" maps `NONE` to `NONE` and `SOME v` to `SOME (f v)`. | | Option.mapPartial | α → β option) → α option → β option | "mapPartial f opt" maps `NONE` to `NONE` and `SOME v` to `f(v)`. | -| Option.getOpt | α option * α → α | "getOpt (opt, a)" returns `v` if `opt` is `SOME(v)`; otherwise returns `a`. | +| Option.getOpt | α option * α → α | "getOpt (opt, a)" returns `v` if `opt` is `SOME (v)`; otherwise returns `a`. | | Option.isSome | α option → bool | "isSome opt" returns `true` if `opt` is `SOME v`; otherwise returns `false`. | | Option.filter | (α → bool) → α → α option | "filter f a" returns `SOME a` if `f(a)` is `true`, `NONE` otherwise. | | Option.join | α option option → α option | "join opt" maps `NONE` to `NONE` and `SOME v` to `v`. | @@ -377,7 +403,7 @@ Exception: | Real.floor | real → int | "floor r" produces `floor(r)`, the largest int not larger than `r`. | | Real.fromInt, real | int → real | "fromInt i" converts the integer `i` to a `real` value. If the absolute value of `i` is larger than `maxFinite`, then the appropriate infinity is returned. If `i` cannot be exactly represented as a `real` value, uses current rounding mode to determine the resulting value. | | Real.fromManExp | {exp:int, man:real} → real | "fromManExp r" returns `{man, exp}`, where `man` and `exp` are the mantissa and exponent of r, respectively. | -| Real.fromString | string → real option | "fromString s" scans a `real` value from a string. Returns `SOME(r)` if a `real` value can be scanned from a prefix of `s`, ignoring any initial whitespace; otherwise, it returns `NONE`. This function is equivalent to `StringCvt.scanString scan`. | +| Real.fromString | string → real option | "fromString s" scans a `real` value from a string. Returns `SOME (r)` if a `real` value can be scanned from a prefix of `s`, ignoring any initial whitespace; otherwise, it returns `NONE`. This function is equivalent to `StringCvt.scanString scan`. | | Real.isFinite | real → bool | "isFinite x" returns true if x is neither NaN nor an infinity. | | Real.isNan | real → bool | "isNan x" returns true if x NaN. | | Real.isNormal | real → bool | "isNormal x" returns true if x is normal, i.e., neither zero, subnormal, infinite nor NaN. | @@ -438,8 +464,8 @@ Exception: | Vector.collate | | Vector.concat | α vector list → α vector | "concat l" returns the vector that is the concatenation of the vectors in the list `l`. Raises `Size` if the total length of these vectors exceeds `maxLen` | | Vector.exists | -| Vector.find | (α → bool) → α vector → α option | "find f vec" applies `f` to each element `x` of the vector `vec`, from left to right, until `f(x)` evaluates to `true`. It returns `SOME(x)` if such an `x` exists; otherwise it returns `NONE`. | -| Vector.findi | (int * α → bool) → α vector → (int * α) option | "findi f vec" applies `f` to each element `x` and element index `i` of the vector `vec`, from left to right, until `f(i, x)` evaluates to `true`. It returns `SOME(i, x)` if such an `x` exists; otherwise it returns `NONE`. | +| Vector.find | (α → bool) → α vector → α option | "find f vec" applies `f` to each element `x` of the vector `vec`, from left to right, until `f(x)` evaluates to `true`. It returns `SOME (x)` if such an `x` exists; otherwise it returns `NONE`. | +| Vector.findi | (int * α → bool) → α vector → (int * α) option | "findi f vec" applies `f` to each element `x` and element index `i` of the vector `vec`, from left to right, until `f(i, x)` evaluates to `true`. It returns `SOME (i, x)` if such an `x` exists; otherwise it returns `NONE`. | | Vector.foldl | (α * β → β) → β → α vector → β | "foldl f init vec" folds the function `f` over all the elements of vector `vec`, left to right, using the initial value `init` | | Vector.foldli | (int * α * β → β) → β → α vector → β | "foldli f init vec" folds the function `f` over all the (index, element) pairs of vector `vec`, left to right, using the initial value `init` | | Vector.foldr | (α * β → β) → β → α vector → β | "foldr f init vec" folds the function `f` over all the elements of vector `vec`, right to left, using the initial value `init` | @@ -457,6 +483,8 @@ Not yet implemented | Name | Type | Description | | ---- | ---- | ----------- | +| Int.fmt | StringCvt.radix → int → string | "fmt radix i" returns a string containing a representation of i with #"~" used as the sign for negative numbers. Formats the string according to `radix`; the hexadecimal digits 10 through 15 are represented as #"A" through #"F", respectively. No prefix "0x" is generated for the hexadecimal representation. | +| Int.scan | scan radix getc strm | Returns `SOME (i,rest)` if an integer in the format denoted by `radix` can be parsed from a prefix of the character stream `strm` after skipping initial whitespace, where `i` is the value of the integer parsed and `rest` is the rest of the character stream. `NONE` is returned otherwise. This function raises `Overflow` when an integer can be parsed, but is too large to be represented by type `int`. | | Real op != | real * real → bool | "x != y" is equivalent to `not o op ==` and the IEEE `?<>` operator. | | Real op *+ | real * real * real → real | "*+ (a, b, c)" returns `a * b + c`. Its behavior on infinities follows from the behaviors derived from addition and multiplication. | | Real op *- | real * real * real → real | "*- (a, b, c)" returns `a * b - c`. Its behavior on infinities follows from the behaviors derived from subtraction and multiplication. | @@ -469,7 +497,7 @@ Not yet implemented | Real.fromLarge | IEEEReal.rounding_mode → real → real | "toLarge r" converts a value of type `real` to type `LargeReal.real`. If `r` is too small or too large to be represented as a real, converts it to a zero or an infinity. | | Real.fromLargeInt | IntInf.int → real | See "fromInt" | | Real.nextAfter | real * real → real | "nextAfter (r, t)" returns the next representable real after `r` in the direction of `t`. Thus, if `t` is less than `r`, `nextAfter` returns the largest representable floating-point number less than `r`. | -| Real.scan | (char,'a) StringCvt.reader → (real,'a) StringCvt.reader | "scan getc strm" scans a `real` value from character source. Reads from ARG/strm/ using reader `getc`, ignoring initial whitespace. It returns `SOME(r, rest)` if successful, where `r` is the scanned `real` value and `rest` is the unused portion of the character stream `strm`. Values of too large a magnitude are represented as infinities; values of too small a magnitude are represented as zeros. | +| Real.scan | (char,'a) StringCvt.reader → (real,'a) StringCvt.reader | "scan getc strm" scans a `real` value from character source. Reads from ARG/strm/ using reader `getc`, ignoring initial whitespace. It returns `SOME (r, rest)` if successful, where `r` is the scanned `real` value and `rest` is the unused portion of the character stream `strm`. Values of too large a magnitude are represented as infinities; values of too small a magnitude are represented as zeros. | | Real.toDecimal | real → IEEEReal.decimal_approx | "toDecimal r" converts a `real` to a decimal approximation | | Real.toInt | real → IEEEReal.rounding_mode → int | "toInt mode x" converts the argument `x` to an integral type using the specified rounding mode. It raises `Overflow` if the result is not representable, in particular, if `x` is an infinity. It raises `Domain` if the input real is NaN. | | Real.toLarge | real → real | "toLarge r" convert a value of type `real` to type `LargeReal.real`. | diff --git a/src/main/java/net/hydromatic/morel/compile/BuiltIn.java b/src/main/java/net/hydromatic/morel/compile/BuiltIn.java index db11eeb2..d0a9ab4d 100644 --- a/src/main/java/net/hydromatic/morel/compile/BuiltIn.java +++ b/src/main/java/net/hydromatic/morel/compile/BuiltIn.java @@ -195,6 +195,119 @@ public enum BuiltIn { ts.fnType(h.get(0), h.get(1))), ts.fnType(h.get(0), h.get(2))))), + /* TODO: + val ~ : int -> int + val * : int * int -> int + val div : int * int -> int + val mod : int * int -> int + val quot : int * int -> int + val rem : int * int -> int + val + : int * int -> int + val - : int * int -> int + val > : int * int -> bool + val >= : int * int -> bool + val < : int * int -> bool + val <= : int * int -> bool + */ + + /** Function "Int.abs" of type "int → int". */ + INT_ABS("Int", "abs", ts -> ts.fnType(INT, INT)), + + /** Function "Int.compare", of type "int * int → order". */ + INT_COMPARE("Int", "compare", ts -> + ts.fnType(ts.tupleType(INT, INT), ts.lookup("order"))), + + /** Function "Int.div", of type "int * int → int". */ + INT_DIV("Int", "div", ts -> ts.fnType(ts.tupleType(INT, INT), INT)), + + /** Function "Int.toInt", of type "int → int". */ + INT_TO_INT("Int", "toInt", ts -> ts.fnType(INT, INT)), + + /** Function "Int.fromInt", of type "int → int". */ + INT_FROM_INT("Int", "fromInt", ts -> ts.fnType(INT, INT)), + + /** Function "Int.toLarge", of type "int → int". */ + INT_TO_LARGE("Int", "toLarge", ts -> ts.fnType(INT, INT)), + + /** Function "Int.fromLarge", of type "int → int". */ + INT_FROM_LARGE("Int", "fromLarge", ts -> ts.fnType(INT, INT)), + + /** Function "Int.fromString s", of type "string → int option", + * scans a {@code int} value from a {@code string}. Returns {@code SOME(r)} + * if a {@code int} value can be scanned from a prefix of {@code s}, ignoring + * any initial whitespace; otherwise, it returns {@code NONE}. This function + * is equivalent to {@code StringCvt.scanString scan}. */ + INT_FROM_STRING("Int", "fromString", ts -> + ts.fnType(STRING, ts.option(INT))), + + /** Constant "Int.minInt", of type "int option". */ + INT_MIN_INT("Int", "minInt", ts -> ts.option(INT)), + + /** Constant "Int.maxInt", of type "int option". */ + INT_MAX_INT("Int", "maxInt", ts -> ts.option(INT)), + + /** Constant "Int.precision", of type "int option". */ + INT_PRECISION("Int", "precision", ts -> ts.option(INT)), + + /** Function "Int.max", of type "int * int → int". + * + *

Returns the returns the larger of the arguments. If exactly one argument + * is NaN, returns the other argument. If both arguments are NaN, returns + * NaN. */ + INT_MAX("Int", "max", ts -> ts.fnType(ts.tupleType(INT, INT), INT)), + + /** Function "Int.min", of type "int * int → int". + * + *

Returns the returns the larger of the arguments. If exactly one argument + * is NaN, returns the other argument. If both arguments are NaN, returns + * NaN. */ + INT_MIN("Int", "min", ts -> ts.fnType(ts.tupleType(INT, INT), INT)), + + /** Function "Int.quot", of type "int * int → int". */ + INT_QUOT("Int", "quot", ts -> ts.fnType(ts.tupleType(INT, INT), INT)), + + /** Function "Int.mod", of type "int * int → int". + * + *

Returns the fractional part of r. "intMod" is equivalent to + * "#frac o split". */ + INT_MOD("Int", "mod", ts -> ts.fnType(ts.tupleType(INT, INT), INT)), + + /** Function "Int.rem", of type "int * int → int". + * + *

Returns the remainder {@code x - n * y}, where + * {@code n = trunc (x / y)}. The result has the same sign as {@code x} and + * has absolute value less than the absolute value of {@code y}. If {@code x} + * is an infinity or {@code y} is 0, returns NaN. If {@code y} is an infinity, + * returns {@code x}. */ + INT_REM("Int", "rem", ts -> ts.fnType(ts.tupleType(INT, INT), INT)), + + /** Function "Int.sameSign", of type "int * int → bool". + * + *

Returns true if and only if {@code signBit r1} equals + * {@code signBit r2}. */ + INT_SAME_SIGN("Int", "sameSign", ts -> + ts.fnType(ts.tupleType(INT, INT), BOOL)), + + /** Function "Int.sign", of type "int → int". + * + *

Returns ~1 if r is negative, 0 if r is zero, or 1 if r is positive. + * An infinity returns its sign; a zero returns 0 regardless of its sign. + * It raises + * {@link net.hydromatic.morel.eval.Codes.BuiltInExn#DOMAIN Domain} + * on NaN. */ + INT_SIGN("Int", "sign", ts -> ts.fnType(INT, INT)), + + /** Function "Int.toString", of type "int → string". + * + *

"toString r" converts ints into strings. The value returned by + * {@code toString t} is equivalent to: + * + *

{@code
+   * (fmt (StringCvt.GEN NONE) r)
+   * }
+ */ + INT_TO_STRING("Int", "toString", ts -> ts.fnType(INT, STRING)), + /** Function "Interact.use" of type "string → unit" * *

"use f" loads source text from the file named `f`. */ diff --git a/src/main/java/net/hydromatic/morel/eval/Codes.java b/src/main/java/net/hydromatic/morel/eval/Codes.java index 90261164..cc4b47ec 100644 --- a/src/main/java/net/hydromatic/morel/eval/Codes.java +++ b/src/main/java/net/hydromatic/morel/eval/Codes.java @@ -109,6 +109,15 @@ public static Code constant(Object value) { return new ConstantCode(value); } + /** Returns an Applicable that returns its argument. */ + private static ApplicableImpl identity(BuiltIn builtIn) { + return new ApplicableImpl(builtIn) { + @Override public Object apply(EvalEnv env, Object arg) { + return arg; + } + }; + } + /** @see BuiltIn#OP_EQ */ private static final Applicable OP_EQ = new Applicable2(BuiltIn.OP_EQ) { @@ -318,12 +327,7 @@ public static Code orElse(Code code0, Code code1) { }; /** @see BuiltIn#OP_DIV */ - private static final Applicable OP_DIV = - new Applicable2(BuiltIn.OP_DIV) { - @Override public Integer apply(Integer a0, Integer a1) { - return Math.floorDiv(a0, a1); - } - }; + private static final Applicable OP_DIV = new IntDiv(BuiltIn.OP_DIV); /** @see BuiltIn#GENERAL_OP_O */ private static final Applicable GENERAL_OP_O = @@ -340,6 +344,172 @@ public static Code orElse(Code code0, Code code1) { } }; + /** @see BuiltIn#INT_ABS */ + private static final Applicable INT_ABS = + new ApplicableImpl(BuiltIn.INT_ABS) { + @Override public Object apply(EvalEnv env, Object arg) { + return Math.abs((int) arg); + } + }; + + /** @see BuiltIn#INT_COMPARE */ + private static final Applicable INT_COMPARE = + new Applicable2(BuiltIn.INT_COMPARE) { + @Override public List apply(Integer a0, Integer a1) { + if (a0 < a1) { + return ORDER_LESS; + } + if (a0 > a1) { + return ORDER_GREATER; + } + return ORDER_EQUAL; + } + }; + + /** @see BuiltIn#INT_FROM_INT */ + private static final Applicable INT_FROM_INT = + identity(BuiltIn.INT_FROM_INT); + + /** @see BuiltIn#INT_FROM_LARGE */ + private static final Applicable INT_FROM_LARGE = + identity(BuiltIn.INT_FROM_LARGE); + + /** Pattern for integers (after '~' has been converted to '-'). + * ".", ".e", ".e-", ".e5", "e7", "2.", ".5", "2.e5" are invalid; + * "-2", "5" are valid. */ + static final Pattern INT_PATTERN = + Pattern.compile("^ *-?[0-9]+"); + + /** @see BuiltIn#INT_FROM_STRING */ + private static final Applicable INT_FROM_STRING = + new ApplicableImpl(BuiltIn.INT_FROM_STRING) { + @Override public Object apply(EvalEnv env, Object arg) { + final String s = (String) arg; + final String s2 = s.replace('~', '-'); + final Matcher matcher = INT_PATTERN.matcher(s2); + if (!matcher.find(0)) { + return OPTION_NONE; + } + final String s3 = s2.substring(0, matcher.end()); + try { + final int f = Integer.parseInt(s3); + return optionSome(f); + } catch (NumberFormatException e) { + // We should not have reached this point. The pattern + // should not have matched the input. + throw new AssertionError(e); + } + } + }; + + /** @see BuiltIn#INT_MAX */ + private static final Applicable INT_MAX = + new Applicable2(BuiltIn.INT_MAX) { + @Override public Integer apply(Integer a0, Integer a1) { + return Math.max(a0, a1); + } + }; + + /** @see BuiltIn#INT_MAX_INT */ + private static final List INT_MAX_INT = optionSome(Integer.MAX_VALUE); + + /** @see BuiltIn#INT_MIN */ + private static final Applicable INT_MIN = + new Applicable2(BuiltIn.INT_MIN) { + @Override public Integer apply(Integer a0, Integer a1) { + return Math.min(a0, a1); + } + }; + + /** @see BuiltIn#INT_MIN_INT */ + private static final List INT_MIN_INT = optionSome(Integer.MAX_VALUE); + + /** @see BuiltIn#INT_DIV */ + private static final Applicable INT_DIV = new IntDiv(BuiltIn.INT_DIV); + + /** @see BuiltIn#INT_MOD */ + private static final Applicable INT_MOD = new IntMod(BuiltIn.INT_MOD); + + /** Implements {@link #INT_MOD} and {@link #OP_MOD}. */ + private static class IntMod + extends Applicable2 { + IntMod(BuiltIn builtIn) { + super(builtIn); + } + + @Override public Integer apply(Integer a0, Integer a1) { + return Math.floorMod(a0, a1); + } + } + + /** Implements {@link #INT_DIV} and {@link #OP_DIV}. */ + private static class IntDiv + extends Applicable2 { + IntDiv(BuiltIn builtIn) { + super(builtIn); + } + + @Override public Integer apply(Integer a0, Integer a1) { + return Math.floorDiv(a0, a1); + } + } + + /** @see BuiltIn#INT_PRECISION */ + private static final List INT_PRECISION = optionSome(32); // Java int 32 bits + + /** @see BuiltIn#INT_QUOT */ + private static final Applicable INT_QUOT = + new Applicable2(BuiltIn.INT_QUOT) { + @Override public Integer apply(Integer a0, Integer a1) { + return a0 / a1; + } + }; + + /** @see BuiltIn#INT_REM */ + private static final Applicable INT_REM = + new Applicable2(BuiltIn.INT_REM) { + @Override public Integer apply(Integer a0, Integer a1) { + return a0 % a1; + } + }; + + /** @see BuiltIn#INT_SAME_SIGN */ + private static final Applicable INT_SAME_SIGN = + new Applicable2(BuiltIn.INT_SAME_SIGN) { + @Override public Boolean apply(Integer a0, Integer a1) { + return a0 < 0 && a1 < 0 + || a0 == 0 && a1 == 0 + || a0 > 0 && a1 > 0; + } + }; + + /** @see BuiltIn#INT_SIGN */ + private static final Applicable INT_SIGN = + new ApplicableImpl(BuiltIn.INT_SIGN) { + @Override public Object apply(EvalEnv env, Object arg) { + return Integer.compare((Integer) arg, 0); + } + }; + + /** @see BuiltIn#INT_TO_INT */ + private static final Applicable INT_TO_INT = + identity(BuiltIn.INT_TO_INT); + + /** @see BuiltIn#INT_TO_LARGE */ + private static final Applicable INT_TO_LARGE = identity(BuiltIn.INT_TO_LARGE); + + /** @see BuiltIn#INT_TO_STRING */ + private static final Applicable INT_TO_STRING = + new ApplicableImpl(BuiltIn.INT_TO_STRING) { + @Override public String apply(EvalEnv env, Object arg) { + // Java's formatting is reasonably close to ML's formatting, + // if we replace minus signs. + Integer f = (Integer) arg; + final String s = Integer.toString(f); + return s.replace('-', '~'); + } + }; + /** @see BuiltIn#INTERACT_USE */ private static final Applicable INTERACT_USE = new InteractUse(Pos.ZERO, false); @@ -624,12 +794,7 @@ public static Applicable nth(int slot) { }; /** @see BuiltIn#OP_MOD */ - private static final Applicable OP_MOD = - new Applicable2(BuiltIn.OP_MOD) { - @Override public Integer apply(Integer a0, Integer a1) { - return Math.floorMod(a0, a1); - } - }; + private static final Applicable OP_MOD = new IntMod(BuiltIn.OP_MOD); /** @see BuiltIn#OP_PLUS */ private static final Macro OP_PLUS = (typeSystem, env, argType) -> { @@ -2398,11 +2563,7 @@ private static Core.Exp sysEnv(TypeSystem typeSystem, Environment env, /** @see BuiltIn#VECTOR_FROM_LIST */ private static final Applicable VECTOR_FROM_LIST = - new ApplicableImpl(BuiltIn.VECTOR_FROM_LIST) { - @Override public Object apply(EvalEnv env, Object arg) { - return arg; // vector and list have the same implementation in Java - } - }; + identity(BuiltIn.VECTOR_FROM_LIST); /** @see BuiltIn#VECTOR_TABULATE */ private static final Applicable VECTOR_TABULATE = @@ -2679,13 +2840,7 @@ private static Applicable vectorFindi(Applicable f) { }; /** @see BuiltIn#Z_LIST */ - private static final Applicable Z_LIST = - new ApplicableImpl(BuiltIn.Z_LIST) { - @Override public Object apply(EvalEnv env, Object arg) { - assert arg instanceof List; - return arg; - } - }; + private static final Applicable Z_LIST = identity(BuiltIn.Z_LIST); private static void populateBuiltIns(Map valueMap) { if (SKIP) { @@ -2790,6 +2945,25 @@ public static Applicable aggregate(Environment env0, Code aggregateCode, .put(BuiltIn.ABS, ABS) .put(BuiltIn.IGNORE, IGNORE) .put(BuiltIn.GENERAL_OP_O, GENERAL_OP_O) + .put(BuiltIn.INT_ABS, INT_ABS) + .put(BuiltIn.INT_COMPARE, INT_COMPARE) + .put(BuiltIn.INT_DIV, INT_DIV) + .put(BuiltIn.INT_FROM_INT, INT_FROM_INT) + .put(BuiltIn.INT_FROM_LARGE, INT_FROM_LARGE) + .put(BuiltIn.INT_FROM_STRING, INT_FROM_STRING) + .put(BuiltIn.INT_MAX, INT_MAX) + .put(BuiltIn.INT_MAX_INT, INT_MAX_INT) + .put(BuiltIn.INT_MIN, INT_MIN) + .put(BuiltIn.INT_MIN_INT, INT_MIN_INT) + .put(BuiltIn.INT_MOD, INT_MOD) + .put(BuiltIn.INT_PRECISION, INT_PRECISION) + .put(BuiltIn.INT_QUOT, INT_QUOT) + .put(BuiltIn.INT_REM, INT_REM) + .put(BuiltIn.INT_SAME_SIGN, INT_SAME_SIGN) + .put(BuiltIn.INT_SIGN, INT_SIGN) + .put(BuiltIn.INT_TO_INT, INT_TO_INT) + .put(BuiltIn.INT_TO_LARGE, INT_TO_LARGE) + .put(BuiltIn.INT_TO_STRING, INT_TO_STRING) .put(BuiltIn.INTERACT_USE, INTERACT_USE) .put(BuiltIn.INTERACT_USE_SILENTLY, INTERACT_USE_SILENTLY) .put(BuiltIn.OP_CARET, OP_CARET) diff --git a/src/test/resources/script/builtIn.smli b/src/test/resources/script/builtIn.smli index 8a3fecb9..98817932 100644 --- a/src/test/resources/script/builtIn.smli +++ b/src/test/resources/script/builtIn.smli @@ -27,6 +27,19 @@ Sys.set ("stringDepth", ~1); General; > val it = {ignore=fn,`op o`=fn} > : {ignore:'a -> unit, `op o`:('b -> 'c) * ('d -> 'b) -> 'd -> 'c} +Int; +> val it = +> {abs=fn,compare=fn,div=fn,fromInt=fn,fromLarge=fn,fromString=fn,max=fn, +> maxInt=SOME 2147483647,min=fn,minInt=SOME 2147483647,mod=fn, +> precision=SOME 32,quot=fn,rem=fn,sameSign=fn,sign=fn,toInt=fn,toLarge=fn, +> toString=fn} +> : {abs:int -> int, compare:int * int -> order, div:int * int -> int, +> fromInt:int -> int, fromLarge:int -> int, fromString:string -> int option, +> max:int * int -> int, maxInt:int option, min:int * int -> int, +> minInt:int option, mod:int * int -> int, precision:int option, +> quot:int * int -> int, rem:int * int -> int, sameSign:int * int -> bool, +> sign:int -> int, toInt:int -> int, toLarge:int -> int, +> toString:int -> string} Interact; > val it = {use=fn,useSilently=fn} > : {use:string -> unit, useSilently:string -> unit} @@ -134,6 +147,15 @@ Sys.plan (); > val it = > "apply2(fnValue +, constant(2), apply2(fnValue *, constant(3), constant(4)))" > : string +14 div 3; +> val it = 4 : int +14 div ~3; +> val it = ~5 : int + +from (i, j) in [(14, 3), (~14, 3), (14, ~3), (~14, ~3)] + where (i div j) <> Int.`div` (i, j) + orelse (i mod j) <> Int.`mod` (i, j); +> val it = [] : {i:int, j:int} list fn x => x + 1; > val it = fn : int -> int @@ -1443,6 +1465,116 @@ Sys.plan (); > "apply(fnCode apply(fnValue Option.composePartial, argCode tuple(match(i, apply(fnCode match(true, constant([NONE]), _, apply(fnValue tyCon, argCode get(name i))), argCode apply2(fnValue =, get(name i), constant(0)))), match(s, apply(fnCode match(true, constant([NONE]), _, apply(fnValue tyCon, argCode apply(fnValue String.size, argCode get(name s)))), argCode apply2(fnValue =, get(name s), constant()))))), argCode constant())" > : string +(* Int --------------------------------------------------------- *) +Int.abs; +> val it = fn : int -> int +Int.abs 5; +> val it = 5 : int +Int.abs ~3; +> val it = 3 : int +Int.compare; +> val it = fn : int * int -> order +Int.compare (4, 3); +> val it = GREATER : order +Int.`div`; +> val it = fn : int * int -> int +Int.`div` (14, 3); +> val it = 4 : int +Int.`div` (~14, 3); +> val it = ~5 : int +Int.`div` (14, ~3); +> val it = ~5 : int +Int.`div` (~14, ~3); +> val it = 4 : int +Int.`div` (0, ~3); +> val it = 0 : int +Int.fromInt; +> val it = fn : int -> int +Int.fromInt 3; +> val it = 3 : int +Int.fromLarge; +> val it = fn : int -> int +Int.fromLarge 3; +> val it = 3 : int +Int.fromString; +> val it = fn : string -> int option +Int.fromString "~345"; +> val it = SOME ~345 : int option +Int.max; +> val it = fn : int * int -> int +Int.max (4, 3); +> val it = 4 : int +Int.maxInt; +> val it = SOME 2147483647 : int option +Int.min; +> val it = fn : int * int -> int +Int.min (4, 3); +> val it = 3 : int +Int.`mod`; +> val it = fn : int * int -> int +Int.`mod` (14, 3); +> val it = 2 : int +Int.`mod` (~14, 3); +> val it = 1 : int +Int.`mod` (14, ~3); +> val it = ~1 : int +Int.`mod` (~14, ~3); +> val it = ~2 : int +Int.`mod` (0, ~3); +> val it = 0 : int +Int.quot; +> val it = fn : int * int -> int +Int.quot (14, 3); +> val it = 4 : int +Int.quot (~14, 3); +> val it = ~4 : int +Int.quot (14, ~3); +> val it = ~4 : int +Int.quot (~14, ~3); +> val it = 4 : int +Int.quot (0, ~3); +> val it = 0 : int +Int.precision; +> val it = SOME 32 : int option +Int.rem; +> val it = fn : int * int -> int +Int.rem (14, 3); +> val it = 2 : int +Int.rem (~14, 3); +> val it = ~2 : int +Int.rem (14, ~3); +> val it = 2 : int +Int.rem (~14, ~3); +> val it = ~2 : int +Int.rem (0, ~3); +> val it = 0 : int +Int.sameSign; +> val it = fn : int * int -> bool +Int.sameSign (3, ~2); +> val it = false : bool +Int.sameSign (0, ~2); +> val it = false : bool +Int.sameSign (2, 345); +> val it = true : bool +Int.sameSign (0, 0); +> val it = true : bool +Int.sign; +> val it = fn : int -> int +Int.sign ~3; +> val it = ~1 : int +Int.toInt; +> val it = fn : int -> int +Int.toInt 3; +> val it = 3 : int +Int.toLarge; +> val it = fn : int -> int +Int.toLarge 3; +> val it = 3 : int +Int.toString; +> val it = fn : int -> string +Int.toString ~345; +> val it = "~345" : string + (* Real -------------------------------------------------------- *) (*) val radix : int @@ -2704,6 +2836,8 @@ Sys.env (); > [("EQUAL","order"),("GREATER","order"), > ("General", > "{ignore:forall 'a. 'a -> unit, `op o`:forall 'a 'b 'c. ('b -> 'c) * ('a -> 'b) -> 'a -> 'c}"), +> ("Int", +> "{abs:int -> int, compare:int * int -> order, div:int * int -> int, fromInt:int -> int, fromLarge:int -> int, fromString:string -> int option, max:int * int -> int, maxInt:int option, min:int * int -> int, minInt:int option, mod:int * int -> int, precision:int option, quot:int * int -> int, rem:int * int -> int, sameSign:int * int -> bool, sign:int -> int, toInt:int -> int, toLarge:int -> int, toString:int -> string}"), > ("Interact","{use:string -> unit, useSilently:string -> unit}"), > ("LESS","order"), > ("List", @@ -2757,8 +2891,7 @@ Sys.env (); > ("op @","forall 'a. 'a list * 'a list -> 'a list"), > ("op ^","string * string -> string"),("op div","int * int -> int"), > ("op elem","forall 'a. 'a * 'a list -> bool"), -> ("op except","forall 'a. 'a list * 'a list -> 'a list"), -> ("op intersect","forall 'a. 'a list * 'a list -> 'a list"),...] +> ("op except","forall 'a. 'a list * 'a list -> 'a list"),...] > : (string * string) list env; @@ -2768,6 +2901,8 @@ env (); > [("EQUAL","order"),("GREATER","order"), > ("General", > "{ignore:forall 'a. 'a -> unit, `op o`:forall 'a 'b 'c. ('b -> 'c) * ('a -> 'b) -> 'a -> 'c}"), +> ("Int", +> "{abs:int -> int, compare:int * int -> order, div:int * int -> int, fromInt:int -> int, fromLarge:int -> int, fromString:string -> int option, max:int * int -> int, maxInt:int option, min:int * int -> int, minInt:int option, mod:int * int -> int, precision:int option, quot:int * int -> int, rem:int * int -> int, sameSign:int * int -> bool, sign:int -> int, toInt:int -> int, toLarge:int -> int, toString:int -> string}"), > ("Interact","{use:string -> unit, useSilently:string -> unit}"), > ("LESS","order"), > ("List", @@ -2821,8 +2956,7 @@ env (); > ("op @","forall 'a. 'a list * 'a list -> 'a list"), > ("op ^","string * string -> string"),("op div","int * int -> int"), > ("op elem","forall 'a. 'a * 'a list -> bool"), -> ("op except","forall 'a. 'a list * 'a list -> 'a list"), -> ("op intersect","forall 'a. 'a list * 'a list -> 'a list"),...] +> ("op except","forall 'a. 'a list * 'a list -> 'a list"),...] > : (string * string) list (*) val plan : unit -> string