diff --git a/src/basis/Basis.ts b/src/basis/Basis.ts index b49712ee8..dbd372cc3 100644 --- a/src/basis/Basis.ts +++ b/src/basis/Basis.ts @@ -34,6 +34,10 @@ import bootstrapStructure from './StructureBasis'; import { toTokens } from '../parser/toTokens'; import parseType from '../parser/paresType'; import type Locales from '../locale/Locales'; +import AnyType from '../nodes/AnyType'; +import BooleanType from '../nodes/BooleanType'; +import ValueException from '../values/ValueException'; +import BoolValue from '../values/BoolValue'; export class Basis { readonly locales: Locales; @@ -177,6 +181,30 @@ export function createBasisFunction( ); } +export function createEqualsFunction( + locales: Locales, + text: (locale: Locale) => FunctionText, + equal: boolean +) { + return createBasisFunction( + locales, + text, + undefined, + [new AnyType()], + BooleanType.make(), + (requestor, evaluation) => { + const left: Value | Evaluation | undefined = + evaluation.getClosure(); + const right = evaluation.getInput(0); + if (!(left instanceof Value)) + return new ValueException(evaluation.getEvaluator(), requestor); + if (!(right instanceof Value)) + return new ValueException(evaluation.getEvaluator(), requestor); + return new BoolValue(requestor, left.isEqualTo(right) === equal); + } + ); +} + export function createBasisConversion( docs: Docs, input: Type | string, diff --git a/src/basis/BoolBasis.ts b/src/basis/BoolBasis.ts index b37f9caf6..4cef25e49 100644 --- a/src/basis/BoolBasis.ts +++ b/src/basis/BoolBasis.ts @@ -3,7 +3,11 @@ import BooleanType from '@nodes/BooleanType'; import StructureDefinition from '@nodes/StructureDefinition'; import BoolValue from '@values/BoolValue'; import TextValue from '@values/TextValue'; -import { createBasisConversion, createBasisFunction } from './Basis'; +import { + createBasisConversion, + createBasisFunction, + createEqualsFunction, +} from './Basis'; import type Value from '@values/Value'; import { getDocLocales } from '@locale/getDocLocales'; import { getNameLocales } from '@locale/getNameLocales'; @@ -12,7 +16,6 @@ import type Expression from '../nodes/Expression'; import type Locale from '../locale/Locale'; import type { FunctionText, NameAndDoc } from '../locale/Locale'; import type Type from '../nodes/Type'; -import AnyType from '../nodes/AnyType'; import type Locales from '../locale/Locales'; export default function bootstrapBool(locales: Locales) { @@ -88,17 +91,15 @@ export default function bootstrapBool(locales: Locales) { return left.not(requestor); } ), - createBooleanFunction( + createEqualsFunction( + locales, (locale) => locale.basis.Boolean.function.equals, - [new AnyType()], - (requestor, left, right) => - new BoolValue(requestor, left.isEqualTo(right)) + true ), - createBooleanFunction( + createEqualsFunction( + locales, (locale) => locale.basis.Boolean.function.notequal, - [new AnyType()], - (requestor, left, right) => - new BoolValue(requestor, !left.isEqualTo(right)) + false ), createBasisConversion( getDocLocales( diff --git a/src/basis/MapBasis.test.ts b/src/basis/MapBasis.test.ts index 93a8e9604..95ca1de75 100644 --- a/src/basis/MapBasis.test.ts +++ b/src/basis/MapBasis.test.ts @@ -11,6 +11,8 @@ test.each([ "{'cat':1 'dog':2 'mouse':3}.translate(ƒ(k v) -v)", '{"cat":-1 "dog":-2 "mouse":-3}', ], + ['{1:1 2:2} = ø', '⊥'], + ['{1:1 2:2} ≠ ø', '⊤'], ])('Expect %s to be %s map functions', (code, value) => { expect(evaluateCode(code)?.toString()).toBe(value); }); diff --git a/src/basis/MapBasis.ts b/src/basis/MapBasis.ts index 7a346248f..6085a7c13 100644 --- a/src/basis/MapBasis.ts +++ b/src/basis/MapBasis.ts @@ -7,7 +7,11 @@ import ListValue from '@values/ListValue'; import TextValue from '@values/TextValue'; import MapValue from '@values/MapValue'; import SetValue from '@values/SetValue'; -import { createBasisConversion, createBasisFunction } from './Basis'; +import { + createBasisConversion, + createBasisFunction, + createEqualsFunction, +} from './Basis'; import BoolValue from '@values/BoolValue'; import TypeVariables from '@nodes/TypeVariables'; import { getDocLocales } from '@locale/getDocLocales'; @@ -23,7 +27,6 @@ import NumberValue from '@values/NumberValue'; import TextType from '../nodes/TextType'; import SetType from '../nodes/SetType'; import ListType from '../nodes/ListType'; -import AnyType from '../nodes/AnyType'; import type Locales from '../locale/Locales'; export default function bootstrapMap(locales: Locales) { @@ -74,45 +77,15 @@ export default function bootstrapMap(locales: Locales) { ); } ), - createBasisFunction( + createEqualsFunction( locales, (locale) => locale.basis.Map.function.equals, - undefined, - [new AnyType()], - BooleanType.make(), - (requestor, evaluation) => { - const map = evaluation?.getClosure(); - const other = evaluation.getInput(0); - return !( - map instanceof MapValue && other instanceof MapValue - ) - ? evaluation.getValueOrTypeException( - requestor, - MapType.make(), - other - ) - : new BoolValue(requestor, map.isEqualTo(other)); - } + true ), - createBasisFunction( + createEqualsFunction( locales, (locale) => locale.basis.Map.function.notequals, - undefined, - [new AnyType()], - BooleanType.make(), - (requestor, evaluation) => { - const map = evaluation?.getClosure(); - const other = evaluation.getInput(0); - return !( - map instanceof MapValue && other instanceof MapValue - ) - ? evaluation.getValueOrTypeException( - requestor, - MapType.make(), - other - ) - : new BoolValue(requestor, !map.isEqualTo(other)); - } + false ), createBasisFunction( locales, diff --git a/src/basis/NumberBasis.ts b/src/basis/NumberBasis.ts index 5df1d32fd..cbbfb47ea 100644 --- a/src/basis/NumberBasis.ts +++ b/src/basis/NumberBasis.ts @@ -14,7 +14,11 @@ import NumberValue from '@values/NumberValue'; import TextValue from '@values/TextValue'; import TypeException from '@values/TypeException'; import type Value from '@values/Value'; -import { createBasisConversion, createBasisFunction } from './Basis'; +import { + createBasisConversion, + createBasisFunction, + createEqualsFunction, +} from './Basis'; import InternalExpression from './InternalExpression'; import type Evaluation from '@runtime/Evaluation'; import ListValue from '@values/ListValue'; @@ -368,21 +372,16 @@ export default function bootstrapNumber(locales: Locales) { left.isEqualTo(right) ) ), - createBinaryOp( + createEqualsFunction( + locales, (locale) => locale.basis.Number.function.equal, - NumberType.make((unit) => unit), - BooleanType.make(), - (requestor, left, right) => - new BoolValue(requestor, left.isEqualTo(right)) + true ), - createBinaryOp( + createEqualsFunction( + locales, (locale) => locale.basis.Number.function.notequal, - NumberType.make((unit) => unit), - BooleanType.make(), - (requestor, left, right) => - new BoolValue(requestor, !left.isEqualTo(right)) + false ), - // Trigonometry createUnaryOp( (locale) => locale.basis.Number.function.cos, diff --git a/src/basis/SetBasis.test.ts b/src/basis/SetBasis.test.ts index 8eafec4d9..aaa992f26 100644 --- a/src/basis/SetBasis.test.ts +++ b/src/basis/SetBasis.test.ts @@ -10,6 +10,8 @@ test.each([ ['{1 2 3}.difference({3 4 5})', '{1 2}'], ['{1 2 3}.filter(ƒ(v) v % 2 = 1)', '{1 3}'], ['{1 2 3}.translate(ƒ(v) v + 2)', '{3 4 5}'], + ['{1 2 3} = ø', '⊥'], + ['{1 2 3} ≠ ø', '⊤'], ])('Expect %s to be %s', (code, value) => { expect(evaluateCode(code)?.toString()).toBe(value); }); diff --git a/src/basis/SetBasis.ts b/src/basis/SetBasis.ts index fb22bb246..2a9ec3c7e 100644 --- a/src/basis/SetBasis.ts +++ b/src/basis/SetBasis.ts @@ -6,7 +6,11 @@ import StructureDefinition from '@nodes/StructureDefinition'; import ListValue from '@values/ListValue'; import TextValue from '@values/TextValue'; import SetValue from '@values/SetValue'; -import { createBasisConversion, createBasisFunction } from './Basis'; +import { + createBasisConversion, + createBasisFunction, + createEqualsFunction, +} from './Basis'; import BoolValue from '@values/BoolValue'; import type Value from '@values/Value'; import type Evaluation from '@runtime/Evaluation'; @@ -21,7 +25,6 @@ import NumberType from '../nodes/NumberType'; import NumberValue from '@values/NumberValue'; import ListType from '../nodes/ListType'; import TextType from '../nodes/TextType'; -import AnyType from '../nodes/AnyType'; import type Locales from '../locale/Locales'; export default function bootstrapSet(locales: Locales) { @@ -67,45 +70,15 @@ export default function bootstrapSet(locales: Locales) { ); } ), - createBasisFunction( + createEqualsFunction( locales, (locale) => locale.basis.Set.function.equals, - undefined, - [new AnyType()], - BooleanType.make(), - (requestor, evaluation) => { - const set = evaluation?.getClosure(); - const other = evaluation.getInput(0); - return !( - set instanceof SetValue && other instanceof SetValue - ) - ? evaluation.getValueOrTypeException( - requestor, - SetType.make(), - other - ) - : new BoolValue(requestor, set.isEqualTo(other)); - } + true ), - createBasisFunction( + createEqualsFunction( locales, (locale) => locale.basis.Set.function.notequals, - undefined, - [new AnyType()], - BooleanType.make(), - (requestor, evaluation) => { - const set = evaluation?.getClosure(); - const other = evaluation.getInput(0); - return !( - set instanceof SetValue && other instanceof SetValue - ) - ? evaluation.getValueOrTypeException( - requestor, - SetType.make(), - other - ) - : new BoolValue(requestor, !set.isEqualTo(other)); - } + false ), createBasisFunction( locales, diff --git a/src/basis/TableBasis.ts b/src/basis/TableBasis.ts index 406c00561..f9c1a63df 100644 --- a/src/basis/TableBasis.ts +++ b/src/basis/TableBasis.ts @@ -1,29 +1,17 @@ -import Bind from '@nodes/Bind'; import Block, { BlockKind } from '@nodes/Block'; -import BooleanType from '@nodes/BooleanType'; -import FunctionDefinition from '@nodes/FunctionDefinition'; import StructureDefinition from '@nodes/StructureDefinition'; -import BoolValue from '@values/BoolValue'; -import InternalExpression from './InternalExpression'; -import type Docs from '@nodes/Docs'; -import type Names from '@nodes/Names'; -import { getInputLocales as getInputLocales } from '@locale/getInputLocales'; import { getDocLocales } from '@locale/getDocLocales'; import { getNameLocales } from '@locale/getNameLocales'; -import type Type from '../nodes/Type'; import TableType from '../nodes/TableType'; -import TableValue from '../values/TableValue'; -import Evaluation from '@runtime/Evaluation'; +import type TableValue from '../values/TableValue'; import type Expression from '../nodes/Expression'; -import type Value from '../values/Value'; -import { createBasisConversion } from './Basis'; +import { createBasisConversion, createEqualsFunction } from './Basis'; import ListValue from '@values/ListValue'; import ListType from '../nodes/ListType'; import TextType from '../nodes/TextType'; import TextValue from '../values/TextValue'; import TypeVariables from '../nodes/TypeVariables'; import TypeVariable from '../nodes/TypeVariable'; -import AnyType from '../nodes/AnyType'; import type Locales from '../locale/Locales'; export default function bootstrapTable(locales: Locales) { @@ -32,25 +20,6 @@ export default function bootstrapTable(locales: Locales) { getNameLocales(locales, (locale) => locale.basis.Table.row) ); - function createTableFunction( - docs: Docs, - names: Names, - inputs: { docs: Docs; names: Names }[], - types: Type[], - expression: InternalExpression - ) { - return FunctionDefinition.make( - docs, - names, - undefined, - inputs.map(({ docs, names }, index) => - Bind.make(docs, names, types[index]) - ), - expression, - expression.type - ); - } - return StructureDefinition.make( getDocLocales(locales, (locale) => locale.basis.Table.doc), getNameLocales(locales, (locale) => locale.basis.Table.name), @@ -59,63 +28,15 @@ export default function bootstrapTable(locales: Locales) { [], new Block( [ - createTableFunction( - getDocLocales( - locales, - (locale) => locale.basis.Table.function.equals.doc - ), - getNameLocales( - locales, - (locale) => locale.basis.Table.function.equals.names - ), - getInputLocales( - locales, - (locale) => locale.basis.Table.function.equals.inputs - ), - [new AnyType()], - new InternalExpression( - BooleanType.make(), - [], - (requestor, evaluation) => - binaryOp( - requestor, - evaluation, - (requestor, left, right) => - new BoolValue( - requestor, - left.isEqualTo(right) - ) - ) - ) + createEqualsFunction( + locales, + (locale) => locale.basis.Table.function.equals, + true ), - createTableFunction( - getDocLocales( - locales, - (locale) => locale.basis.Table.function.notequal.doc - ), - getNameLocales( - locales, - (locale) => locale.basis.Table.function.notequal.names - ), - getInputLocales( - locales, - (locale) => locale.basis.Table.function.notequal.inputs - ), - [new AnyType()], - new InternalExpression( - BooleanType.make(), - [], - (requestor, evaluation) => - binaryOp( - requestor, - evaluation, - (requestor, left, right) => - new BoolValue( - requestor, - !left.isEqualTo(right) - ) - ) - ) + createEqualsFunction( + locales, + (locale) => locale.basis.Table.function.notequal, + false ), createBasisConversion( getDocLocales( @@ -142,32 +63,3 @@ export default function bootstrapTable(locales: Locales) { ) ); } - -function binaryOp( - requestor: Expression, - evaluation: Evaluation, - evaluate: ( - requestor: Expression, - left: TableValue, - right: TableValue - ) => Value -): Value { - const left = evaluation.getClosure(); - const right = evaluation.resolve( - (evaluation.getDefinition() as FunctionDefinition).inputs[0].names - ); - // This should be impossible, but the type system doesn't know it. - if (!(left instanceof TableValue)) - return evaluation.getValueOrTypeException( - requestor, - TableType.make(), - left instanceof Evaluation ? undefined : left - ); - if (!(right instanceof TableValue)) - return evaluation.getValueOrTypeException( - requestor, - TableType.make(), - undefined - ); - else return evaluate(requestor, left, right); -} diff --git a/src/basis/TextBasis.test.ts b/src/basis/TextBasis.test.ts index 9d074a8a9..a7941fa59 100644 --- a/src/basis/TextBasis.test.ts +++ b/src/basis/TextBasis.test.ts @@ -3,4 +3,6 @@ import evaluateCode from '../runtime/evaluate'; test('Test text functions', () => { expect(evaluateCode('"hello".length()')?.toString()).toBe('5'); + expect(evaluateCode('"hello" = ø')?.toString()).toBe('⊥'); + expect(evaluateCode('"hello" ≠ ø')?.toString()).toBe('⊤'); }); diff --git a/src/basis/TextBasis.ts b/src/basis/TextBasis.ts index 0f6cc0bfa..2e78b32e1 100644 --- a/src/basis/TextBasis.ts +++ b/src/basis/TextBasis.ts @@ -4,7 +4,11 @@ import TextType from '@nodes/TextType'; import type Type from '@nodes/Type'; import BoolValue from '@values/BoolValue'; import type Value from '@values/Value'; -import { createBasisConversion, createBasisFunction } from './Basis'; +import { + createBasisConversion, + createBasisFunction, + createEqualsFunction, +} from './Basis'; import TextValue from '@values/TextValue'; import StructureDefinition from '@nodes/StructureDefinition'; import NumberValue from '@values/NumberValue'; @@ -16,7 +20,6 @@ import type Expression from '@nodes/Expression'; import type Locale from '../locale/Locale'; import type { FunctionText, NameAndDoc } from '../locale/Locale'; import ListType from '../nodes/ListType'; -import AnyType from '../nodes/AnyType'; import type Locales from '../locale/Locales'; export default function bootstrapText(locales: Locales) { @@ -96,41 +99,15 @@ export default function bootstrapText(locales: Locales) { ); } ), - createBasisFunction( + createEqualsFunction( locales, (locale) => locale.basis.Text.function.equals, - undefined, - [new AnyType()], - BooleanType.make(), - (requestor, evaluation) => { - const text = evaluation.getClosure() as TextValue; - const input = evaluation.getInput(0); - if (input === undefined) - return evaluation.getValueOrTypeException( - requestor, - TextType.make(), - input - ); - return new BoolValue(requestor, text.isEqualTo(input)); - } + true ), - createBasisFunction( + createEqualsFunction( locales, (locale) => locale.basis.Text.function.notequals, - undefined, - [new AnyType()], - BooleanType.make(), - (requestor, evaluation) => { - const text = evaluation.getClosure() as TextValue; - const input = evaluation.getInput(0); - if (input === undefined) - return evaluation.getValueOrTypeException( - requestor, - TextType.make(), - input - ); - return new BoolValue(requestor, !text.isEqualTo(input)); - } + false ), createBinaryTextFunction( (locale) => locale.basis.Text.function.notequals, diff --git a/src/nodes/BooleanLiteral.test.ts b/src/nodes/BooleanLiteral.test.ts index b12352d4a..8c4d2e734 100644 --- a/src/nodes/BooleanLiteral.test.ts +++ b/src/nodes/BooleanLiteral.test.ts @@ -1,5 +1,4 @@ -import { FALSE_SYMBOL, TRUE_SYMBOL } from '@parser/Symbols'; -import ExceptionValue from '@values/ExceptionValue'; +import { FALSE_SYMBOL, NONE_SYMBOL, TRUE_SYMBOL } from '@parser/Symbols'; import { test, expect } from 'vitest'; import evaluateCode from '../runtime/evaluate'; @@ -13,5 +12,10 @@ test('Test equality', () => { expect(evaluateCode(`${FALSE_SYMBOL} = ${FALSE_SYMBOL}`)?.toString()).toBe( TRUE_SYMBOL ); - expect(evaluateCode(`${TRUE_SYMBOL} = 1`)).toBeInstanceOf(ExceptionValue); + expect(evaluateCode(`${FALSE_SYMBOL} = ${NONE_SYMBOL}`)?.toString()).toBe( + FALSE_SYMBOL + ); + expect(evaluateCode(`${FALSE_SYMBOL} ≠ ${NONE_SYMBOL}`)?.toString()).toBe( + TRUE_SYMBOL + ); }); diff --git a/src/nodes/Table.test.ts b/src/nodes/Table.test.ts index c9f1a51d5..2a6e324d2 100644 --- a/src/nodes/Table.test.ts +++ b/src/nodes/Table.test.ts @@ -6,6 +6,8 @@ test.each([ ['⎡a•# b•#⎦⎡1 2⎦ = ⎡a•# b•#⎦⎡1 2⎦', TRUE_SYMBOL], ['⎡a•# b•#⎦⎡1 2⎦ = ⎡a•# b•#⎦⎡1 3⎦', FALSE_SYMBOL], ['⎡a•# b•#⎦⎡1 2⎦ ≠ ⎡a•# b•#⎦⎡1 3⎦', TRUE_SYMBOL], + ['⎡a•# b•#⎦⎡1 2⎦ = ø', FALSE_SYMBOL], + ['⎡a•# b•#⎦⎡1 2⎦ ≠ ø', TRUE_SYMBOL], ])('%s = %s', (code: string, value: string) => { expect(evaluateCode(code)?.toString()).toBe(value); }); diff --git a/src/values/NumberValue.test.ts b/src/values/NumberValue.test.ts index 7a145846a..535aee39e 100644 --- a/src/values/NumberValue.test.ts +++ b/src/values/NumberValue.test.ts @@ -46,8 +46,8 @@ test.each([ [`1 = 1`, TRUE_SYMBOL], [`1 = 2`, FALSE_SYMBOL], [`1m = 1m`, TRUE_SYMBOL], - [`1m = 1`, '!TypeException'], - [`1 = ø`, '!TypeException'], + [`1m = 1`, FALSE_SYMBOL], + [`1 = ø`, FALSE_SYMBOL], ['5 < 3', FALSE_SYMBOL], ['5 < 10', TRUE_SYMBOL], ['100.1 < 100.2', TRUE_SYMBOL], @@ -93,6 +93,8 @@ test.each([ ['1.max(2 3)', '3'], ['1.max(0 -1)', '1'], ['1m.max()', '!ValueException'], + ['1 = ø', '⊥'], + ['1 ≠ ø', '⊤'], ])('Expect %s to be %s', (code, value) => { expect(evaluateCode(code)?.toString()).toBe(value); });