tom-weatherhead/thaw-grammar

View on GitHub
src/languages/lambda-calculus-integer-extension/lambda-calculus-integer-extension-grammar.ts

Summary

Maintainability
A
0 mins
Test Coverage
// tom-weatherhead/thaw-grammar/src/languages/lambda-calculus-integer-extension/lambda-calculus-integer-extension-grammar.ts

import { GrammarSymbol, IToken, LexicalState, SemanticStackType } from 'thaw-interpreter-types';

import { Name } from 'thaw-interpreter-core';

import { GrammarBase, GrammarException } from 'thaw-interpreter-core';

import { ILCExpression } from '../lambda-calculus/domain-object-model/interfaces/expression';

import { LCFunctionCall } from '../lambda-calculus/domain-object-model/call';
import { LCLambdaExpression } from '../lambda-calculus/domain-object-model/lambda-expression';
import { LCVariable } from '../lambda-calculus/domain-object-model/variable';

import { LCIntegerLiteral } from './domain-object-model/integer-literal';
import { LCPrimitiveOperator } from './domain-object-model/primitive-operator';

// Inherited productions:

// Deleted: λexp -> var
// λexp -> λ var . λexp
// λexp -> ( λexp λexp )

// New productions:

// λexp -> intexpr
// intexpr -> intliteral
// intexpr -> var
// intexpr -> ( binaryintop intexpr intexpr )
// binaryintop -> +
// binaryintop -> -
// binaryintop -> *
// binaryintop -> / (i.e. integer division: Math.floor(a / b))
// binaryintop -> %

export class LambdaCalculusIntegerExtensionGrammar extends GrammarBase {
    constructor() {
        super(GrammarSymbol.nonterminalStart);

        // This initial production needed to be added: Start -> Expression EOF
        this.addProduction(GrammarSymbol.nonterminalStart, [
            GrammarSymbol.nonterminalExpression,
            GrammarSymbol.terminalEOF
        ]);

        // Expression -> Variable
        this.addProduction(GrammarSymbol.nonterminalExpression, [
            GrammarSymbol.nonterminalVariable
        ]);

        // Expression -> Lambda Expression
        this.addProduction(GrammarSymbol.nonterminalExpression, [
            GrammarSymbol.nonterminalLambdaExpression
        ]);

        this.addProduction(GrammarSymbol.nonterminalExpression, [
            GrammarSymbol.terminalLeftBracket,
            GrammarSymbol.nonterminalBracketedExpression,
            GrammarSymbol.terminalRightBracket
        ]);

        // Variable -> Name
        this.addProduction(GrammarSymbol.nonterminalVariable, [
            GrammarSymbol.terminalID,
            '#variable'
        ]);

        // Lambda Expression -> λ Variable . Expression
        this.addProduction(GrammarSymbol.nonterminalLambdaExpression, [
            GrammarSymbol.terminalFn,
            GrammarSymbol.nonterminalVariable,
            GrammarSymbol.terminalDot,
            GrammarSymbol.nonterminalExpression,
            '#lambdaExpression'
        ]);

        this.addProduction(GrammarSymbol.nonterminalBracketedExpression, [
            GrammarSymbol.nonterminalFunctionCall
        ]);

        // Function Call -> Expression Expression
        this.addProduction(GrammarSymbol.nonterminalFunctionCall, [
            GrammarSymbol.nonterminalExpression,
            GrammarSymbol.nonterminalExpression,
            '#functionCall'
        ]);

        // intexpr -> intlit
        this.addProduction(GrammarSymbol.nonterminalExpression, [
            GrammarSymbol.terminalIntegerLiteral
        ]);

        // intexpr -> binaryintop intexpr intexpr
        this.addProduction(GrammarSymbol.nonterminalBracketedExpression, [
            GrammarSymbol.nonterminalOptr,
            GrammarSymbol.nonterminalExpression,
            GrammarSymbol.nonterminalExpression,
            '#integerOperator'
        ]);

        // binaryintop -> +
        this.addProduction(GrammarSymbol.nonterminalOptr, [GrammarSymbol.terminalPlus]);

        // binaryintop -> -
        this.addProduction(GrammarSymbol.nonterminalOptr, [GrammarSymbol.terminalMinus]);

        // binaryintop -> *
        this.addProduction(GrammarSymbol.nonterminalOptr, [GrammarSymbol.terminalMultiply]);

        // binaryintop -> / (i.e. integer division: Math.floor(a / b))
        // this.addProduction(GrammarSymbol.nonterminalOptr, [
        //     GrammarSymbol.terminalDivide
        // ]);

        // binaryintop -> %
        // this.addProduction(GrammarSymbol.nonterminalOptr, [
        //     GrammarSymbol.terminalMod
        // ]);

        // binaryintop -> =
        this.addProduction(GrammarSymbol.nonterminalOptr, [GrammarSymbol.terminalEquals]);

        // Handling 'if':
        // 'if a then b else c' translates to '((a b) c)', where a is a fn that takes 2 args.
        // In fact, a is either TRUE or FALSE, where:
        // TRUE := λx.λy.x
        // FALSE := λx.λy.y
        this.addProduction(GrammarSymbol.nonterminalExpression, [
            GrammarSymbol.terminalIf,
            GrammarSymbol.nonterminalExpression,
            GrammarSymbol.nonterminalExpression,
            GrammarSymbol.nonterminalExpression,
            '#if'
        ]);
    }

    public get languageName(): string {
        return 'The Lambda Calculus + Ints';
    }

    // public get selectorsOfCompatibleParsers(): ParserSelector[] {
    //     return [ParserSelector.LL1];
    // }

    public executeSemanticAction(semanticStack: SemanticStackType, action: string): void {
        let name: Name;
        let variable: LCVariable;
        let expression: ILCExpression;
        let expression2: ILCExpression;
        let expression3: ILCExpression;

        switch (action) {
            case '#variable':
                name = semanticStack.pop() as Name;
                semanticStack.push(new LCVariable(name.value)); //, name.line, name.column
                break;

            case '#lambdaExpression':
                expression = semanticStack.pop() as ILCExpression; // The function's body
                variable = semanticStack.pop() as LCVariable; // The function's formal argument list
                semanticStack.push(new LCLambdaExpression(variable, expression)); // Add line and column?
                break;

            case '#functionCall':
                expression2 = semanticStack.pop() as ILCExpression;
                expression = semanticStack.pop() as ILCExpression;
                semanticStack.push(new LCFunctionCall(expression, expression2));
                break;

            case '#integerOperator':
                expression2 = semanticStack.pop() as ILCExpression;
                expression = semanticStack.pop() as ILCExpression;
                name = semanticStack.pop() as Name;
                semanticStack.push(new LCPrimitiveOperator(name.value, expression, expression2)); //, name.line, name.column
                break;

            case '#if':
                // throw new GrammarException(`Unimplemented semantic action: ${action}`);
                expression3 = semanticStack.pop() as ILCExpression;
                expression2 = semanticStack.pop() as ILCExpression;
                expression = semanticStack.pop() as ILCExpression;
                semanticStack.push(
                    new LCFunctionCall(new LCFunctionCall(expression, expression2), expression3)
                );
                break;

            default:
                throw new GrammarException(`Unrecognized semantic action: ${action}`);
        }
    }

    public override tokenToSymbol(token: IToken): GrammarSymbol {
        const tokenValueAsString: string = token.tokenValue as string;

        switch (token.tokenType) {
            case LexicalState.tokenEOF:
                return GrammarSymbol.terminalEOF;
            case LexicalState.tokenIntLit:
                return GrammarSymbol.terminalIntegerLiteral;
            case LexicalState.tokenLeftBracket:
                return GrammarSymbol.terminalLeftBracket;
            case LexicalState.tokenRightBracket:
                return GrammarSymbol.terminalRightBracket;
            // case LexicalState.tokenLeftSquareBracket:
            //     return GrammarSymbol.terminalLeftSquareBracket;
            // case LexicalState.tokenRightSquareBracket:
            //     return GrammarSymbol.terminalRightSquareBracket;
            case LexicalState.tokenLowercaseGreekLetterLambda:
                return GrammarSymbol.terminalFn;
            case LexicalState.tokenDot:
                return GrammarSymbol.terminalDot;
            case LexicalState.tokenPlus:
                return GrammarSymbol.terminalPlus;
            case LexicalState.tokenMinus:
                return GrammarSymbol.terminalMinus;
            case LexicalState.tokenMult:
                return GrammarSymbol.terminalMultiply;
            case LexicalState.tokenEqual:
                return GrammarSymbol.terminalEquals;

            case LexicalState.tokenIdent:
                switch (tokenValueAsString) {
                    case 'if':
                        return GrammarSymbol.terminalIf;
                    default:
                        return GrammarSymbol.terminalID;
                }

            default:
                throw new GrammarException(
                    `No grammar symbol matches token ${token.tokenType} ${
                        LexicalState[token.tokenType]
                    } (value '${token.tokenValue}')`,
                    token.line,
                    token.column
                );
        }
    }

    public override pushTokenOntoSemanticStack(
        semanticStack: SemanticStackType,
        tokenAsSymbol: GrammarSymbol,
        token: IToken
    ): void {
        const value = token.tokenValue;

        switch (tokenAsSymbol) {
            case GrammarSymbol.terminalID:
            case GrammarSymbol.terminalPlus:
            case GrammarSymbol.terminalMinus:
            case GrammarSymbol.terminalMultiply:
            case GrammarSymbol.terminalEquals:
                semanticStack.push(new Name(value as string, token.line, token.column));
                break;

            case GrammarSymbol.terminalIntegerLiteral:
                semanticStack.push(new LCIntegerLiteral(value));
                break;

            case GrammarSymbol.terminalLeftBracket:
            case GrammarSymbol.terminalRightBracket:
            case GrammarSymbol.terminalFn:
            case GrammarSymbol.terminalDot:
            case GrammarSymbol.terminalIf:
            case GrammarSymbol.terminalEOF:
                break;

            default:
                throw new GrammarException(
                    `pushTokenOntoSemanticStack() : Unexpected tokenAsSymbol ${GrammarSymbol[tokenAsSymbol]} (${tokenAsSymbol})`,
                    token.line,
                    token.column
                );
        }
    }
}