Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/end-to-end.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,11 @@ testCases(endToEnd, code => code)('end-to-end tests', [
[`b atom.append c atom.prepend a`, either.makeRight('abc')],
[`(b atom.append c) atom.prepend a`, either.makeRight('abc')],
[`a atom.append (c atom.prepend b)`, either.makeRight('abc')],
[
`{ a: "it works!" } object.lookup a`,
either.makeRight({ tag: 'some', value: 'it works!' }),
],
[`{ a: :identity }.a(1) + 1`, either.makeRight('2')],
[
`1
+ 2
Expand Down
257 changes: 79 additions & 178 deletions src/language/parsing/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,16 +253,13 @@ const infixOperator = sequence([
])

const compactExpression: Parser<Molecule | Atom> = oneOf([
// (a => :b).c(d)
// (a)
// (1 + 1)
// (a => :b)(c)
// ({ a: 1 } |> :identity).a
map(
sequence([
surroundedByParentheses(
oneOf([
lazy(() => precededByAtomThenTrivia),
lazy(() => precededByColonThenAtom),
]),
),
surroundedByParentheses(lazy(() => expression)),
compactTrailingIndexesAndArguments,
]),
([expression, trailingIndexesAndArguments]) =>
Expand All @@ -275,18 +272,7 @@ const compactExpression: Parser<Molecule | Atom> = oneOf([
// :a.b(1).c
// :f(x)
// :a.b(1)(2)
map(
sequence([
colon,
atomRequiringDotQuotation,
compactTrailingIndexesAndArguments,
]),
([_colon, key, trailingIndexesAndArguments]) =>
trailingIndexesAndArgumentsToExpression(
{ 0: '@lookup', key },
trailingIndexesAndArguments,
),
),
lazy(() => precededByColonThenAtom),
// {}
lazy(() => precededByOpeningBrace),
// 1
Expand Down Expand Up @@ -326,95 +312,50 @@ const trailingInfixTokens = oneOrMore(
),
)

type TrailingInfixToken = readonly [
operator: readonly [Atom, readonly TrailingIndexOrArgument[]],
operand: Molecule | Atom,
]
type TrailingFunctionBodyOrInfixTokens =
| {
readonly kind: 'functionBody'
readonly additionalParameters: readonly Atom[]
readonly body: Molecule | Atom
}
| {
readonly kind: 'infixTokens'
readonly tokens: readonly [
TrailingInfixToken,
...(readonly TrailingInfixToken[]),
]
}

const precededByAtomThenTrivia = map(
const precededByAtomThenArrow = map(
sequence([
atom,
oneOf([
// a => :b
// a => {}
// a => (b => c => :d)
// a => b => c => d
// a => 1 + 1
map(
sequence([
trivia,
arrow,
trivia,
zeroOrMore(
map(
sequence([atom, trivia, arrow, trivia]),
([parameter, _trivia1, _arrow, _trivia2]) => parameter,
),
),
lazy(() => expression),
]),
([
_trivia1,
_arrow,
_trivia2,
additionalParameters,
body,
]): TrailingFunctionBodyOrInfixTokens => ({
kind: 'functionBody',
additionalParameters,
body,
}),
),
// 1 + 2 + 3 + 4
// 1 + (2 + 3 + 4)
// a => :b
// a => {}
// a => (b => c => :d)
// a => b => c => d
// a => 1 + 1
trivia,
arrow,
trivia,
zeroOrMore(
map(
trailingInfixTokens,
(tokens): TrailingFunctionBodyOrInfixTokens => ({
kind: 'infixTokens',
tokens,
}),
sequence([atom, trivia, arrow, trivia]),
([parameter, _trivia1, _arrow, _trivia2]) => parameter,
),
]),
),
lazy(() => expression),
]),
([initialAtom, trailingFunctionBodyOrInfixTokens]) => {
switch (trailingFunctionBodyOrInfixTokens.kind) {
case 'functionBody':
const [lastParameter, ...additionalParameters] = [
...trailingFunctionBodyOrInfixTokens.additionalParameters.toReversed(),
initialAtom,
]
const initialFunction = {
0: '@function',
parameter: lastParameter,
body: trailingFunctionBodyOrInfixTokens.body,
}
return additionalParameters.reduce(
(expression, additionalParameter) => ({
0: '@function',
parameter: additionalParameter,
body: expression,
}),
initialFunction,
)
case 'infixTokens':
return infixTokensToExpression([
initialAtom,
...trailingFunctionBodyOrInfixTokens.tokens.flat(),
])
([
initialParameter,
_trivia1,
_arrow,
_trivia2,
trailingParameters,
body,
]) => {
const [lastParameter, ...additionalParameters] = [
...trailingParameters.toReversed(),
initialParameter,
]
const initialFunction = {
0: '@function',
parameter: lastParameter,
body: body,
}
return additionalParameters.reduce(
(expression, additionalParameter) => ({
0: '@function',
parameter: additionalParameter,
body: expression,
}),
initialFunction,
)
},
)

Expand All @@ -423,53 +364,13 @@ const precededByAtomThenTrivia = map(
// :a.b(1).c
// :f(x)
// :a.b(1)(2)
// :a b.c :z
// :a b.c z
// :f(g) + b
// :a + :b + :c + :d
const precededByColonThenAtom = map(
sequence([
colon,
atomRequiringDotQuotation,
trailingIndexesAndArguments,
zeroOrMore(
map(
// See note in `trailingInfixTokens` about newlines.
oneOf([
sequence([
trivia,
infixOperator,
triviaExceptNewlines,
compactExpression,
]),
sequence([
triviaExceptNewlines,
infixOperator,
trivia,
compactExpression,
]),
]),
([_trivia1, operator, _trivia2, operand]) =>
[operator, operand] as const,
),
),
]),
([_colon, key, trailingIndexesAndArguments, infixOperationTokens]) => {
const initialExpression = trailingIndexesAndArgumentsToExpression(
sequence([colon, atomRequiringDotQuotation, trailingIndexesAndArguments]),
([_colon, key, trailingIndexesAndArguments]) =>
trailingIndexesAndArgumentsToExpression(
{ 0: '@lookup', key },
trailingIndexesAndArguments,
)
const [firstToken, ...additionalTokens] = infixOperationTokens
if (firstToken === undefined) {
return initialExpression
} else {
return infixTokensToExpression([
initialExpression,
...firstToken,
...additionalTokens.flat(),
])
}
},
),
)

// (1 + 1)
Expand All @@ -479,36 +380,21 @@ const precededByColonThenAtom = map(
// (1 + 1).b
// (:x => x)(1)
// (:f >> :g)(1)
// (1 + 1) - (1 + 1)
const precededByOpeningParenthesis = oneOf([
map(
sequence([
surroundedByParentheses(lazy(() => expression)),
trailingInfixTokens,
]),
([initialExpression, trailingInfixTokens]) =>
infixTokensToExpression([
initialExpression,
...trailingInfixTokens.flat(),
]),
),
map(
sequence([
surroundedByParentheses(lazy(() => expression)),
const precededByOpeningParenthesis = map(
sequence([
surroundedByParentheses(lazy(() => expression)),
trailingIndexesAndArguments,
]),
([expression, trailingIndexesAndArguments]) =>
trailingIndexesAndArgumentsToExpression(
expression,
trailingIndexesAndArguments,
]),
([expression, trailingIndexesAndArguments]) =>
trailingIndexesAndArgumentsToExpression(
expression,
trailingIndexesAndArguments,
),
),
])
),
)

// {}
// { a: b }
// { 1, 2, 3 }
// {a::f}.a(1) + 1
const precededByOpeningBrace = map(
sequence([sugarFreeMolecule, trailingIndexesAndArguments]),
([expression, trailingIndexesAndArguments]) =>
Expand All @@ -518,10 +404,25 @@ const precededByOpeningBrace = map(
),
)

export const expression: Parser<Atom | Molecule> = oneOf([
precededByOpeningParenthesis,
precededByOpeningBrace,
precededByColonThenAtom,
precededByAtomThenTrivia,
atom,
])
export const expression: Parser<Atom | Molecule> = map(
sequence([
oneOf([
precededByOpeningParenthesis,
precededByOpeningBrace,
precededByColonThenAtom,
precededByAtomThenArrow,
atom,
]),
optional(trailingInfixTokens),
]),
([initialExpression, trailingInfixTokens]) => {
if (trailingInfixTokens === undefined) {
return initialExpression
} else {
return infixTokensToExpression([
initialExpression,
...trailingInfixTokens.flat(),
])
}
},
)