tom-weatherhead/thaw-grammar

View on GitHub
src/languages/prolog/domain-object-model/prolog-functor-expression.ts

Summary

Maintainability
A
0 mins
Test Coverage
// tom-weatherhead/thaw-grammar/src/languages/prolog/domain-object-model/prolog-functor-expression.ts

import { LanguageSelector } from 'thaw-interpreter-types';

import { IPrologExpression } from './interfaces/iprolog-expression';
import { IPrologNumber } from './interfaces/iprolog-number';
import { PrologFloatLiteral } from './prolog-float-literal';
import { isPrologIntegerLiteral, PrologIntegerLiteral } from './prolog-integer-literal';
import { PrologNameExpression } from './prolog-name-expression';
import { createSubstitution } from './prolog-substitution';

import { ISubstitution } from './interfaces/isubstitution';
import { isIPrologVariable } from './interfaces/ivariable';

const typenamePrologFunctorExpression = 'PrologFunctorExpression';

export function isPrologFunctorExpression(obj: unknown): obj is PrologFunctorExpression {
    const fe = obj as PrologFunctorExpression;

    return typeof fe !== 'undefined' && fe.typename === typenamePrologFunctorExpression;
}

export class PrologFunctorExpression extends PrologNameExpression implements IPrologExpression {
    public readonly typename: string = typenamePrologFunctorExpression;

    constructor(gsParam: LanguageSelector, functor: string, expressionList: IPrologExpression[]) {
        super(gsParam, functor, expressionList);
    }

    private ListToString(expr: IPrologExpression): string {
        const functorExpression = expr as PrologFunctorExpression;

        if (
            expr instanceof PrologFunctorExpression &&
            typeof functorExpression !== 'undefined' &&
            functorExpression.Name === '[]' &&
            functorExpression.ExpressionList.length === 0
        ) {
            return '';
        } else if (
            expr instanceof PrologFunctorExpression &&
            typeof functorExpression !== 'undefined' &&
            functorExpression.Name === '.' &&
            functorExpression.ExpressionList.length === 2
        ) {
            return `, ${functorExpression.ExpressionList[0]}${this.ListToString(
                functorExpression.ExpressionList[1]
            )}`;
        } else {
            return ` | ${expr}`;
        }
    }

    private SequenceToString(expr: IPrologExpression): string {
        const functorExpression = expr as PrologFunctorExpression;

        if (
            expr instanceof PrologFunctorExpression &&
            typeof functorExpression !== 'undefined' &&
            functorExpression.Name === 'consSeq' &&
            functorExpression.ExpressionList.length === 2
        ) {
            return `${functorExpression.ExpressionList[0]}, ${this.SequenceToString(
                functorExpression.ExpressionList[1]
            )}`;
        } else {
            return `${expr}`;
        }
    }

    public override toString(): string {
        if (this.gs === LanguageSelector.Prolog2 && this.ExpressionList.length === 2) {
            if (this.Name === '.') {
                return `[${this.ExpressionList[0]}${this.ListToString(this.ExpressionList[1])}]`;
            } else if (this.Name === 'consSeq') {
                // ThAW 2014/03/28 : I added the brackets here because without them, ?- X = [(1, 2), (3, 4)], print(X). yielded [1, 2, 3, 4],
                // which was misleading.
                return `(${this.ExpressionList[0]}, ${this.SequenceToString(
                    this.ExpressionList[1]
                )})`;
            }
        }

        return super.toString();
    }

    public equals(other: unknown): boolean {
        return (
            isPrologFunctorExpression(other) &&
            other.gs === this.gs &&
            other.Name === this.Name &&
            other.ExpressionList.length === this.ExpressionList.length &&
            this.ExpressionList.every((expr, i) => other.ExpressionList[i].equals(expr))
        );
    }

    public ApplySubstitution(substitution: ISubstitution): IPrologExpression {
        return new PrologFunctorExpression(
            this.gs,
            this.Name,
            this.ExpressionList.map((expr: IPrologExpression) =>
                expr.ApplySubstitution(substitution)
            )
        );
    }

    public Unify(otherExpr: IPrologExpression): ISubstitution | undefined {
        if (isIPrologVariable(otherExpr)) {
            return otherExpr.Unify(this);
        }

        // A PrologFunctorExpression can unify with a PrologFunctorExpression;
        // a PrologGoal can unify with a PrologGoal,
        // but a PrologFunctorExpression cannot unify with a PrologGoal.

        const otherNameExpression = otherExpr as PrologFunctorExpression;

        if (
            !isPrologFunctorExpression(otherExpr) ||
            this.Name !== otherNameExpression.Name ||
            this.ExpressionList.length !== otherNameExpression.ExpressionList.length
        ) {
            return undefined;
        }

        let substitution = createSubstitution();

        for (let i = 0; i < this.ExpressionList.length; ++i) {
            const newExpr1 = this.ExpressionList[i].ApplySubstitution(substitution);
            const newExpr2 = otherNameExpression.ExpressionList[i].ApplySubstitution(substitution);
            const substitution2 = newExpr1.Unify(newExpr2);

            if (typeof substitution2 === 'undefined') {
                return undefined;
            }

            substitution = substitution.compose(substitution2);
        }

        return substitution;
    }

    private static EvaluateUnaryOperatorToNumber(
        thisFunctorExpression: PrologFunctorExpression
    ): IPrologNumber | undefined {
        const arg1Evaluated = thisFunctorExpression.ExpressionList[0].EvaluateToNumber();

        if (typeof arg1Evaluated === 'undefined') {
            return undefined;
        }

        if (isPrologIntegerLiteral(arg1Evaluated)) {
            const arg1Value = arg1Evaluated.ToInteger();
            let result: number;

            switch (thisFunctorExpression.Name) {
                case '+':
                    result = arg1Value;
                    break;

                case '-':
                    result = -arg1Value;
                    break;

                default:
                    return undefined;
            }

            return new PrologIntegerLiteral(result);
        } else {
            const arg1Value = arg1Evaluated.ToDouble();
            let result: number;

            switch (thisFunctorExpression.Name) {
                case '+':
                    result = arg1Value;
                    break;

                case '-':
                    result = -arg1Value;
                    break;

                default:
                    return undefined;
            }

            return new PrologFloatLiteral(result);
        }
    }

    public EvaluateToNumber(): IPrologNumber | undefined {
        if (this.ExpressionList.length === 1) {
            return PrologFunctorExpression.EvaluateUnaryOperatorToNumber(this);
        } else if (this.ExpressionList.length !== 2) {
            return undefined;
        }

        const arg1Evaluated = this.ExpressionList[0].EvaluateToNumber();
        const arg2Evaluated = this.ExpressionList[1].EvaluateToNumber();

        if (typeof arg1Evaluated === 'undefined' || typeof arg2Evaluated === 'undefined') {
            return undefined;
        }

        if (isPrologIntegerLiteral(arg1Evaluated) && isPrologIntegerLiteral(arg2Evaluated)) {
            const arg1Value = arg1Evaluated.ToInteger();
            const arg2Value = arg2Evaluated.ToInteger();
            let result: number;

            switch (this.Name) {
                case '+':
                    result = arg1Value + arg2Value;
                    break;

                case '-':
                    result = arg1Value - arg2Value;
                    break;

                case '*':
                    result = arg1Value * arg2Value;
                    break;

                case '/':
                    result = arg1Value / arg2Value;
                    break;

                case 'mod':
                    result = arg1Value % arg2Value;
                    break;

                default:
                    return undefined;
            }

            return new PrologIntegerLiteral(result);
        } else {
            const arg1Value = arg1Evaluated.ToDouble();
            const arg2Value = arg2Evaluated.ToDouble();
            let result: number;

            switch (this.Name) {
                case '+':
                    result = arg1Value + arg2Value;
                    break;

                case '-':
                    result = arg1Value - arg2Value;
                    break;

                case '*':
                    result = arg1Value * arg2Value;
                    break;

                case '/':
                    result = arg1Value / arg2Value;
                    break;

                case 'mod':
                    result = arg1Value % arg2Value;
                    break;

                default:
                    return undefined;
            }

            return new PrologFloatLiteral(result);
        }
    }
}