UNPKG

jambda-calc

Version:

Convert JavaScript functions to the Lambda Calculus, and visualize them with Tromp diagrams.

166 lines 5.47 kB
var TokenType; (function (TokenType) { TokenType["LAMBDA"] = "lambda"; TokenType["DOT"] = "dot"; TokenType["VARIABLE"] = "variable"; TokenType["LPAREN"] = "lparen"; TokenType["RPAREN"] = "rparen"; TokenType["EOF"] = "eof"; })(TokenType || (TokenType = {})); /** * Parser for lambda calculus expressions. */ export class Parser { constructor(source) { this.source = source; this.tokens = []; this.position = 0; this.current = { type: TokenType.EOF, value: '', pos: 0 }; this.tokenize(); this.advance(); } tokenize() { if (!this.source || this.source.length === 0) { this.tokens.push({ type: TokenType.EOF, value: '', pos: 0 }); return; } const maxSourceLength = 100000; // 100K characters max if (this.source.length > maxSourceLength) { throw new Error(`Source code exceeds maximum length of ${maxSourceLength} characters`); } let pos = 0; while (pos < this.source.length) { const char = this.source[pos]; // handle char types if (/\s/.test(char)) { // skip whitespace pos++; continue; } // lambda symbol if (char === 'λ') { this.tokens.push({ type: TokenType.LAMBDA, value: 'λ', pos }); pos++; continue; } // dot if (char === '.') { this.tokens.push({ type: TokenType.DOT, value: '.', pos }); pos++; continue; } // parens if (char === '(') { this.tokens.push({ type: TokenType.LPAREN, value: '(', pos }); pos++; continue; } if (char === ')') { this.tokens.push({ type: TokenType.RPAREN, value: ')', pos }); pos++; continue; } // vars (alphanumeric and some special) if (/[a-zA-Z0-9_'+*\-/]/.test(char)) { let value = ''; const startPos = pos; while (pos < this.source.length && /[a-zA-Z0-9_'+*\-/]/.test(this.source[pos])) { value += this.source[pos]; pos++; } this.tokens.push({ type: TokenType.VARIABLE, value, pos: startPos }); continue; } // skip any other character pos++; } this.tokens.push({ type: TokenType.EOF, value: '', pos }); } advance() { if (this.position < this.tokens.length) { this.current = this.tokens[this.position++]; } } match(type) { return this.current.type === type; } consume(type, errorMessage) { if (this.match(type)) { const token = this.current; this.advance(); return token; } throw new Error(`${errorMessage} at position ${this.current.pos}`); } /** * Parse a lambda expression. Converts formal lambda calc into AST for rendering. */ parse() { const term = this.parseExpression(); if (!this.match(TokenType.EOF)) { throw new Error(`Unexpected token ${this.current.type} at position ${this.current.pos}`); } return term; } /** * Parse an expression (abstraction, application, or atomic) */ parseExpression() { if (this.match(TokenType.LAMBDA)) { return this.parseAbstraction(); } return this.parseApplication(); } /** * Parse a lambda abstraction (λx.body) */ parseAbstraction() { this.consume(TokenType.LAMBDA, 'Expected lambda'); const variable = this.consume(TokenType.VARIABLE, 'Expected variable after lambda').value; this.consume(TokenType.DOT, 'Expected dot after variable in lambda abstraction'); const body = this.parseExpression(); return { type: 'abstraction', variable, body, }; } /** * Parse an application (func arg) */ parseApplication() { // start with atomic term let term = this.parseAtomic(); // keep applying terms as long as we have more atomic terms // applications are left-associative while (!this.match(TokenType.EOF) && !this.match(TokenType.RPAREN) && !this.match(TokenType.DOT)) { const argument = this.parseAtomic(); term = { type: 'application', left: term, right: argument, }; } return term; } /** * Parse an atomic expression (variable or parenthesized expression) */ parseAtomic() { if (this.match(TokenType.VARIABLE)) { const name = this.current.value; this.advance(); return { type: 'variable', name }; } if (this.match(TokenType.LPAREN)) { this.advance(); const expr = this.parseExpression(); this.consume(TokenType.RPAREN, 'Expected closing parenthesis'); return expr; } throw new Error(`Unexpected token ${this.current.type} at position ${this.current.pos}`); } } //# sourceMappingURL=parser.js.map