Skip to content

Commit 96c00d0

Browse files
authored
improve the ski parser (#30)
1 parent 9fc5170 commit 96c00d0

File tree

2 files changed

+36
-52
lines changed

2 files changed

+36
-52
lines changed

lib/parser/ski.ts

Lines changed: 34 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -11,53 +11,39 @@ import { parseWithEOF } from "./eof.ts";
1111
import { cons } from "../cons.ts";
1212
import { type SKITerminalSymbol, term } from "../ski/terminal.ts";
1313

14-
/**
15-
* Parses a chain of SKI atomic terms (term { term }).
16-
* For example, the input "SII" will be parsed as:
17-
* mkApp(mkApp(S, I), I)
18-
*
19-
* Returns a tuple of the literal parsed, the SKI expression, and the updated state.
20-
*/
21-
function parseSKIChain(rdb: ParserState): [string, SKIExpression, ParserState] {
22-
let [lit, expr, state] = parseAtomicSKI(rdb);
23-
for (;;) {
24-
const [next, newState] = peek(state);
25-
if (
26-
next === null ||
27-
(next !== "(" && !["S", "K", "I"].includes(next.toUpperCase()))
28-
) {
29-
return [lit, expr, newState];
30-
}
31-
const [nextLit, nextExpr, updatedState] = parseAtomicSKI(newState);
14+
const TERMINALS = new Set(["S", "K", "I"]);
15+
16+
function isSymbol(tok: string | null): tok is SKITerminalSymbol {
17+
return tok !== null && TERMINALS.has(tok.toUpperCase());
18+
}
19+
20+
function isAtomStart(tok: string | null): boolean {
21+
return tok === "(" || isSymbol(tok);
22+
}
23+
24+
function parseSeq(rdb: ParserState): [string, SKIExpression, ParserState] {
25+
let [lit, expr, state] = parseAtomicOrParens(rdb);
26+
let [next, newState] = peek(state);
27+
28+
while (isAtomStart(next)) {
29+
const [nextLit, nextExpr, updatedState] = parseAtomicOrParens(newState);
3230
lit = `${lit} ${nextLit}`;
3331
expr = cons(expr, nextExpr);
3432
state = updatedState;
33+
[next, newState] = peek(state);
3534
}
35+
36+
return [lit, expr, newState];
3637
}
3738

38-
/**
39-
* Parses an atomic SKI term.
40-
* This is either one of the terminals S, K, I or a parenthesized SKI expression.
41-
*
42-
* Returns a tuple of the literal parsed, the SKI expression, and the updated state.
43-
*/
44-
export function parseAtomicSKI(
39+
function parseAtomicOrParens(
4540
rdb: ParserState,
4641
): [string, SKIExpression, ParserState] {
4742
const [peeked, state] = peek(rdb);
43+
4844
if (peeked === "(") {
49-
// Parse a parenthesized expression.
50-
const stateAfterLP = matchLP(state);
51-
// Inside parentheses we parse a whole chain.
52-
const [innerLit, innerExpr, stateAfterChain] = parseSKIChain(stateAfterLP);
53-
const stateAfterRP = matchRP(stateAfterChain);
54-
return [`(${innerLit})`, innerExpr, stateAfterRP];
55-
} else if (
56-
peeked &&
57-
(peeked.toUpperCase() === "S" ||
58-
peeked.toUpperCase() === "K" ||
59-
peeked.toUpperCase() === "I")
60-
) {
45+
return parseParens(state);
46+
} else if (isSymbol(peeked)) {
6147
const token = peeked.toUpperCase();
6248
const stateAfterConsume = consume(state);
6349
return [peeked, term(token as SKITerminalSymbol), stateAfterConsume];
@@ -69,22 +55,20 @@ export function parseAtomicSKI(
6955
}
7056
}
7157

72-
/**
73-
* Parses a full SKI expression.
74-
* (This is just a wrapper around parseSKIChain, which implements the left‐associative application.)
75-
*
76-
* Returns a tuple of the literal parsed, the SKI expression, and the updated state.
77-
*/
78-
export function parseSKIInternal(
79-
rdb: ParserState,
58+
function parseParens(
59+
state: ParserState,
8060
): [string, SKIExpression, ParserState] {
81-
return parseSKIChain(rdb);
61+
const stateAfterLP = matchLP(state);
62+
const [innerLit, innerExpr, stateAfterChain] = parseSeq(stateAfterLP);
63+
const stateAfterRP = matchRP(stateAfterChain);
64+
return [`(${innerLit})`, innerExpr, stateAfterRP];
8265
}
8366

84-
/**
85-
* Parses an input string into an SKI expression.
86-
*/
67+
export const parseSKIDelimited = (
68+
rdb: ParserState,
69+
): [string, SKIExpression, ParserState] => parseSeq(rdb);
70+
8771
export function parseSKI(input: string): SKIExpression {
88-
const [, expr] = parseWithEOF(input, parseSKIInternal);
72+
const [, expr] = parseWithEOF(input, parseSKIDelimited);
8973
return expr;
9074
}

lib/parser/tripLang.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { ParseError } from "./parseError.ts";
1313
import { parseSystemFTerm } from "./systemFTerm.ts";
1414
import { parseArrowType, parseTypedLambdaInternal } from "./typedLambda.ts";
1515
import { parseUntypedLambdaInternal } from "./untyped.ts";
16-
import { parseSKIInternal } from "./ski.ts";
16+
import { parseSKIDelimited } from "./ski.ts";
1717
import type { TripLangProgram, TripLangTerm } from "../meta/trip.ts";
1818
import { parseSystemFType } from "./systemFType.ts";
1919
import type { BaseType } from "../types/types.ts";
@@ -113,7 +113,7 @@ export function parseTripLangDefinition(
113113
return [{ kind: UNTYPED, name, term }, skipWhitespace(finalState)];
114114

115115
case COMBINATOR:
116-
[, term, finalState] = parseSKIInternal(currentState);
116+
[, term, finalState] = parseSKIDelimited(currentState);
117117
return [{ kind: COMBINATOR, name, term }, skipWhitespace(finalState)];
118118

119119
case TYPE:

0 commit comments

Comments
 (0)