diff --git a/src/end-to-end.test.ts b/src/end-to-end.test.ts index 0463b0c..a99e336 100644 --- a/src/end-to-end.test.ts +++ b/src/end-to-end.test.ts @@ -221,9 +221,18 @@ testCases(endToEnd, code => code)('end-to-end tests', [ ], [':match({ a: A })({ tag: a, value: {} })', either.makeRight('A')], [':atom.prepend(a)(b)', either.makeRight('ab')], - [`:natural_number.add(1)(1)`, either.makeRight('2')], [ - `:natural_number.add(one)(juan)`, + `{ + :atom.equal(hello)(hello) + :atom.equal("")("") + :atom.equal(hello)(Hello) + :atom.equal("1.0")("1.00") + }`, + either.makeRight({ 0: 'true', 1: 'true', 2: 'false', 3: 'false' }), + ], + [`:integer.add(1)(1)`, either.makeRight('2')], + [ + `:integer.add(one)(juan)`, output => { assert(either.isLeft(output)) }, @@ -236,6 +245,11 @@ testCases(endToEnd, code => code)('end-to-end tests', [ [`1 - 2 - 3`, either.makeRight('-4')], [`1 - (2 - 3)`, either.makeRight('2')], [`(1 - 2) - 3`, either.makeRight('-4')], + [`:integer.multiply(2)(2)`, either.makeRight('4')], + [`2 * 2`, either.makeRight('4')], + [`2 * -2`, either.makeRight('-4')], + [`-2 * -2`, either.makeRight('4')], + [`2 * 0`, either.makeRight('0')], [':flow(:atom.append(b))(:atom.append(a))(z)', either.makeRight('zab')], [ `@runtime { :object.lookup("key which does not exist in runtime context") }`, diff --git a/src/language/semantics/prelude.ts b/src/language/semantics/prelude.ts index 29f7326..d2d0f5b 100644 --- a/src/language/semantics/prelude.ts +++ b/src/language/semantics/prelude.ts @@ -18,6 +18,7 @@ export const prelude = makeObjectNode({ '>>': globalFunctions.flow, '|>': globalFunctions.identity, '+': integer.add, + '*': integer.multiply, '-': integer.subtract, '<': integer.less_than, '>': integer.greater_than, diff --git a/src/language/semantics/stdlib/atom.ts b/src/language/semantics/stdlib/atom.ts index 9d7e464..98ec27a 100644 --- a/src/language/semantics/stdlib/atom.ts +++ b/src/language/semantics/stdlib/atom.ts @@ -43,6 +43,41 @@ export const atom = { ), ), ), + // Note that this is simple string equality; e.g. `:atom.equal(1)(01)` + // is `false`. For this reason it should not be aliased as a global `==` + // operator or similar as its behavior may not be what users expect for all + // types of values. + equal: preludeFunction( + ['atom', 'equal'], + { + parameter: types.atom, + return: makeFunctionType('', { + parameter: types.atom, + return: types.boolean, + }), + }, + atom2 => + either.makeRight( + makeFunctionNode( + { + parameter: types.atom, + return: types.atom, + }, + serializeOnceAppliedFunction(['atom', 'equal'], atom2), + option.none, + atom1 => { + if (typeof atom2 !== 'string' || typeof atom1 !== 'string') { + return either.makeLeft({ + kind: 'panic', + message: 'equal received a non-atom argument', + }) + } else { + return either.makeRight(String(atom1 === atom2)) + } + }, + ), + ), + ), prepend: preludeFunction( ['atom', 'prepend'], { diff --git a/src/language/semantics/stdlib/integer.ts b/src/language/semantics/stdlib/integer.ts index 41d4d6d..287cef7 100644 --- a/src/language/semantics/stdlib/integer.ts +++ b/src/language/semantics/stdlib/integer.ts @@ -40,7 +40,12 @@ export const integer = { }) } else { return either.makeRight( - // TODO: See comment in `natural_number.add`. + // FIXME: It's wasteful to always convert here. + // + // Consider `add(add(1)(1))(1)`—the `2` returned from the inner `add` is + // stringified only to be converted back to a bigint. This is acceptable for the + // prototype, but a real implementation could use a fancier `SemanticGraph` which + // can model atoms as different native data types. String(BigInt(number1) + BigInt(number2)), ) } @@ -97,7 +102,7 @@ export const integer = { }) } else { return either.makeRight( - // TODO: See comment in `natural_number.add`. + // TODO: See comment in `integer.add`. String(BigInt(number1) > BigInt(number2)), ) } @@ -136,7 +141,7 @@ export const integer = { }) } else { return either.makeRight( - // TODO: See comment in `natural_number.add`. + // TODO: See comment in `integer.add`. String(BigInt(number1) < BigInt(number2)), ) } @@ -144,6 +149,45 @@ export const integer = { ), ), ), + multiply: preludeFunction( + ['integer', 'multiply'], + { + parameter: types.integer, + return: makeFunctionType('', { + parameter: types.integer, + return: types.integer, + }), + }, + number2 => + either.makeRight( + makeFunctionNode( + { + parameter: types.integer, + return: types.integer, + }, + serializeOnceAppliedFunction(['integer', 'multiply'], number2), + option.none, + number1 => { + if ( + typeof number1 !== 'string' || + !types.integer.isAssignableFrom(makeUnionType('', [number1])) || + typeof number2 !== 'string' || + !types.integer.isAssignableFrom(makeUnionType('', [number2])) + ) { + return either.makeLeft({ + kind: 'panic', + message: 'numbers must be atoms', + }) + } else { + return either.makeRight( + // TODO: See comment in `integer.add`. + String(BigInt(number1) * BigInt(number2)), + ) + } + }, + ), + ), + ), subtract: preludeFunction( ['integer', 'subtract'], { @@ -175,7 +219,7 @@ export const integer = { }) } else { return either.makeRight( - // TODO: See comment in `natural_number.add`. + // TODO: See comment in `integer.add`. String(BigInt(number1) - BigInt(number2)), ) } diff --git a/src/language/semantics/stdlib/natural-number.ts b/src/language/semantics/stdlib/natural-number.ts index 52a264b..58e08ce 100644 --- a/src/language/semantics/stdlib/natural-number.ts +++ b/src/language/semantics/stdlib/natural-number.ts @@ -9,54 +9,6 @@ import { } from './stdlib-utilities.js' export const natural_number = { - add: preludeFunction( - ['natural_number', 'add'], - { - parameter: types.naturalNumber, - return: makeFunctionType('', { - parameter: types.naturalNumber, - return: types.naturalNumber, - }), - }, - number2 => - either.makeRight( - makeFunctionNode( - { - parameter: types.naturalNumber, - return: types.naturalNumber, - }, - serializeOnceAppliedFunction(['natural_number', 'add'], number2), - option.none, - number1 => { - if ( - typeof number1 !== 'string' || - !types.naturalNumber.isAssignableFrom( - makeUnionType('', [number1]), - ) || - typeof number2 !== 'string' || - !types.naturalNumber.isAssignableFrom( - makeUnionType('', [number2]), - ) - ) { - return either.makeLeft({ - kind: 'panic', - message: 'numbers must be atoms', - }) - } else { - return either.makeRight( - // FIXME: It's wasteful to always convert here. - // - // Consider `add(add(1)(1))(1)`—the `2` returned from the inner `add` is - // stringified only to be converted back to a bigint. This is acceptable for the - // prototype, but a real implementation could use a fancier `SemanticGraph` which - // can model atoms as different native data types. - String(BigInt(number1) + BigInt(number2)), - ) - } - }, - ), - ), - ), is: preludeFunction( ['natural_number', 'is'], {