diff --git a/README.md b/README.md index fbf3701..da65701 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ data representation implied by the fact that a value is an atom (e.g. the atom `2` may be an integer in memory). Bare words not containing any -[reserved character sequences](./src/language/parsing/atom.ts#L33-L57) are +[reserved character sequences](./src/language/parsing/atom.ts#L33-L55) are atoms: ``` diff --git a/examples/lookup-environment-variable.plz b/examples/lookup-environment-variable.plz index 4176f5d..3670d55 100644 --- a/examples/lookup-environment-variable.plz +++ b/examples/lookup-environment-variable.plz @@ -1,15 +1,17 @@ {@runtime, context => - :flow({ - :context.arguments.lookup + :flow( :match({ none: {} - some: :flow({ - :context.environment.lookup + some: :flow( :match({ none: {} some: :identity }) - }) + )( + :context.environment.lookup + ) }) - })(variable) + )( + :context.arguments.lookup + )(variable) } diff --git a/src/end-to-end.test.ts b/src/end-to-end.test.ts index 1a9a8e5..cf6b886 100644 --- a/src/end-to-end.test.ts +++ b/src/end-to-end.test.ts @@ -133,7 +133,7 @@ testCases(endToEnd, code => code)('end-to-end tests', [ ], [':match({ a: A })({ tag: a, value: {} })', either.makeRight('A')], [':atom.prepend(a)(b)', either.makeRight('ab')], - [':flow({ :atom.append(a), :atom.append(b) })(z)', either.makeRight('zab')], + [':flow(:atom.append(b))(:atom.append(a))(z)', either.makeRight('zab')], [ `{ // foo: bar @@ -159,42 +159,21 @@ testCases(endToEnd, code => code)('end-to-end tests', [ either.makeRight({ tag: 'none', value: {} }), ], [ - `{@runtime, {@apply, :flow, { - {@apply, :object.lookup, environment} - {@apply, :match, { - none: "environment does not exist" - some: {@apply, :flow, { - {@apply, :object.lookup, lookup} - {@apply, :match, { - none: "environment.lookup does not exist" - some: {@apply, :apply, PATH} - }} - }} - }} - }}}`, - output => { - if (either.isLeft(output)) { - assert.fail(output.value.message) - } - assert(typeof output.value === 'object') - assert.deepEqual(output.value['tag'], 'some') - assert.deepEqual(typeof output.value['value'], 'string') - }, - ], - [ - `{@runtime, :flow({ - :object.lookup(environment) - :match({ - none: "environment does not exist" - some: :flow({ - :object.lookup(lookup) - :match({ - none: "environment.lookup does not exist" - some: :apply(PATH) - }) + `{@runtime, :flow( + :match({ + none: "environment does not exist" + some: :flow( + :match({ + none: "environment.lookup does not exist" + some: :apply(PATH) + }) + )( + :object.lookup(lookup) + ) }) - }) - })}`, + )( + :object.lookup(environment) + )}`, output => { if (either.isLeft(output)) { assert.fail(output.value.message) diff --git a/src/language/compiling/compiler.test.ts b/src/language/compiling/compiler.test.ts index 51fee90..db271d3 100644 --- a/src/language/compiling/compiler.test.ts +++ b/src/language/compiling/compiler.test.ts @@ -21,51 +21,18 @@ testCases(compile, input => `compiling \`${JSON.stringify(input)}\``)( { true1: ['@check', true, ['@lookup', 'identity']], true2: ['@apply', ['@index', ['@lookup', 'boolean'], ['not']], false], - true3: [ - '@apply', - [ - '@apply', - ['@lookup', 'flow'], - [ - ['@index', ['@lookup', 'boolean'], ['not']], - ['@index', ['@lookup', 'boolean'], ['not']], - ], - ], - true, - ], false1: ['@check', false, ['@index', ['@lookup', 'boolean'], ['is']]], false2: [ '@apply', ['@index', ['@lookup', 'boolean'], ['is']], 'not a boolean', ], - false3: [ - '@apply', - [ - '@apply', - ['@lookup', 'flow'], - [ - [ - '@apply', - ['@lookup', 'flow'], - [ - ['@index', ['@lookup', 'boolean'], ['not']], - ['@index', ['@lookup', 'boolean'], ['not']], - ], - ], - ['@index', ['@lookup', 'boolean'], ['not']], - ], - ], - true, - ], }, success({ true1: 'true', true2: 'true', - true3: 'true', false1: 'false', false2: 'false', - false3: 'false', }), ], [ @@ -116,37 +83,25 @@ testCases(compile, input => `compiling \`${JSON.stringify(input)}\``)( '@runtime', [ '@apply', - ['@lookup', 'flow'], - [ - ['@lookup', 'identity'], - ['@lookup', 'identity'], - ], + ['@apply', ['@lookup', 'flow'], ['@lookup', 'identity']], + ['@lookup', 'identity'], ], ], success({ 0: '@runtime', function: { 0: '@apply', - function: { 0: '@lookup', key: 'flow' }, - argument: { - 0: { 0: '@lookup', key: 'identity' }, - 1: { 0: '@lookup', key: 'identity' }, + function: { + 0: '@apply', + function: { 0: '@lookup', key: 'flow' }, + argument: { 0: '@lookup', key: 'identity' }, }, + argument: { 0: '@lookup', key: 'identity' }, }, }), ], [ - [ - '@runtime', - [ - '@apply', - ['@lookup', 'flow'], - [ - ['@index', ['@lookup', 'boolean'], ['not']], - ['@index', ['@lookup', 'boolean'], ['not']], - ], - ], - ], + ['@runtime', ['@index', ['@lookup', 'boolean'], ['not']]], output => { assert(either.isLeft(output)) assert(output.value.kind === 'typeMismatch') diff --git a/src/language/parsing/atom.ts b/src/language/parsing/atom.ts index acbe31f..eb0e72e 100644 --- a/src/language/parsing/atom.ts +++ b/src/language/parsing/atom.ts @@ -44,15 +44,13 @@ const atomComponentsRequiringQuotation = [ singleLineCommentDelimiter, whitespace, + // Reserved to allow symbols like `=>` to not be conflated with atoms: + literal('='), + // Reserved for future use: literal('['), literal(']'), - literal('<'), - literal('>'), literal('#'), - literal('&'), - literal('|'), - literal('='), literal(';'), ] as const diff --git a/src/language/runtime/evaluator.test.ts b/src/language/runtime/evaluator.test.ts index ab29770..9e134c4 100644 --- a/src/language/runtime/evaluator.test.ts +++ b/src/language/runtime/evaluator.test.ts @@ -17,56 +17,6 @@ testCases(evaluate, input => `evaluating \`${JSON.stringify(input)}\``)( [ ['Hello, world!', success('Hello, world!')], [['@check', true, ['@lookup', 'identity']], success('true')], - [ - [ - '@runtime', - [ - '@apply', - ['@lookup', 'flow'], - [ - [ - '@apply', - ['@index', ['@lookup', 'object'], ['lookup']], - 'environment', - ], - [ - '@apply', - ['@lookup', 'match'], - { - none: 'environment does not exist!', - some: [ - '@apply', - ['@lookup', 'flow'], - [ - [ - '@apply', - ['@index', ['@lookup', 'object'], ['lookup']], - 'lookup', - ], - [ - '@apply', - ['@lookup', 'match'], - { - none: 'environment.lookup does not exist!', - some: ['@apply', ['@lookup', 'apply'], 'PATH'], - }, - ], - ], - ], - }, - ], - ], - ], - ], - output => { - if (either.isLeft(output)) { - assert.fail(output.value.message) - } - assert(typeof output.value === 'object') - assert(output.value['tag'] === 'some') - assert(typeof output.value['value'] === 'string') - }, - ], [ ['@check', 'not a boolean', ['@index', ['@lookup', 'boolean'], 'is']], output => assert(either.isLeft(output)), diff --git a/src/language/semantics/prelude.ts b/src/language/semantics/prelude.ts index e2d9b97..d592d8a 100644 --- a/src/language/semantics/prelude.ts +++ b/src/language/semantics/prelude.ts @@ -13,4 +13,12 @@ export const prelude = makeObjectNode({ integer: makeObjectNode(integer), atom: makeObjectNode(atom), object: makeObjectNode(object), + + // Aliases: + '>>': globalFunctions.flow, + '|>': globalFunctions.identity, + '+': integer.add, + '-': integer.subtract, + '<': integer.less_than, + '>': integer.greater_than, }) diff --git a/src/language/semantics/stdlib/global-functions.ts b/src/language/semantics/stdlib/global-functions.ts index 062a1c9..2f4adcb 100644 --- a/src/language/semantics/stdlib/global-functions.ts +++ b/src/language/semantics/stdlib/global-functions.ts @@ -5,19 +5,18 @@ import { isFunctionNode, makeFunctionNode } from '../function-node.js' import { isObjectNode, lookupPropertyOfObjectNode, - makeObjectNode, type ObjectNode, } from '../object-node.js' import { isSemanticGraph, type SemanticGraph } from '../semantic-graph.js' import { types } from '../type-system.js' import { makeFunctionType, - makeObjectType, makeTypeParameter, } from '../type-system/type-formats.js' import { preludeFunction, serializeOnceAppliedFunction, + serializeTwiceAppliedFunction, } from './stdlib-utilities.js' const A = makeTypeParameter('a', { assignableTo: types.something }) @@ -75,66 +74,58 @@ export const globalFunctions = { ), ), - // { 0: a => b, 1: b => c } => (a => c) + // (b => c) => (a => b) => (a => c) flow: preludeFunction( ['flow'], { - parameter: makeObjectType('', { - 0: makeFunctionType('', { - parameter: A, - return: B, - }), - 1: makeFunctionType('', { - parameter: B, - return: C, - }), - }), - return: makeFunctionType('', { - parameter: A, - return: C, - }), + // TODO + parameter: types.something, + return: types.something, }, - argument => { - if (!isObjectNode(argument)) { + secondFunction => { + if (!isFunctionNode(secondFunction)) { return either.makeLeft({ kind: 'panic', - message: '`flow` must be given an object', + message: 'argument must be a function', }) } else { - const argument0 = lookupPropertyOfObjectNode('0', argument) - const argument1 = lookupPropertyOfObjectNode('1', argument) - if (option.isNone(argument0) || option.isNone(argument1)) { - return either.makeLeft({ - kind: 'panic', - message: - "`flow`'s argument must contain properties named '0' and '1'", - }) - } else if ( - !isFunctionNode(argument0.value) || - !isFunctionNode(argument1.value) - ) { - return either.makeLeft({ - kind: 'panic', - message: "`flow`'s argument must contain functions", - }) - } else { - const function0 = argument0.value - const function1 = argument1.value - return either.makeRight( - makeFunctionNode( - { - parameter: function0.signature.parameter, - return: function1.signature.parameter, - }, - serializeOnceAppliedFunction( - ['flow'], - makeObjectNode({ 0: function0, 1: function1 }), - ), - option.none, - argument => either.flatMap(function0(argument), function1), - ), - ) - } + return either.makeRight( + makeFunctionNode( + { + // TODO + parameter: types.something, + return: types.something, + }, + serializeOnceAppliedFunction(['flow'], secondFunction), + option.none, + firstFunction => { + if (!isFunctionNode(firstFunction)) { + return either.makeLeft({ + kind: 'panic', + message: 'argument must be a function', + }) + } else { + return either.makeRight( + makeFunctionNode( + { + // TODO + parameter: types.something, + return: types.something, + }, + serializeTwiceAppliedFunction( + ['flow'], + secondFunction, + firstFunction, + ), + option.none, + argument => + either.flatMap(firstFunction(argument), secondFunction), + ), + ) + } + }, + ), + ) } }, ),