"use strict";
// -*- mode: typescript; indent-tabs-mode: nil; js-basic-offset: 4 -*-
//
// This file is part of Genie
//
// Copyright 2020 The Board of Trustees of the Leland Stanford Junior University
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: Giovanni Campagna <gcampagn@cs.stanford.edu>
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ChoiceRuleHead = exports.ComputedStringLiteralRuleHead = exports.StringLiteralRuleHead = exports.NonTerminalRuleHead = exports.RuleHeadPart = exports.ConstantNonTerminalConstraint = exports.EqualityNonTerminalConstraint = exports.NonTerminalConstraint = exports.Condition = exports.Expansion = exports.Constants = exports.Rule = exports.RuleAttributes = exports.Import = exports.IfStmt = exports.ForLoop = exports.FunctionDeclarationStmt = exports.ContextStmt = exports.NonTerminalStmt = exports.KeyFunctionDeclarationStmt = exports.JSImportStmt = exports.CodeBlock = exports.Statement = exports.Grammar = exports.NodeVisitor = void 0;
const assert_1 = __importDefault(require("assert"));
const escaping_1 = require("../../utils/escaping");
class NodeVisitor {
    visitImport(stmt) { }
    visitContextStmt(stmt) { }
    visitNonTerminalStmt(stmt) { }
    visitKeyFunctionDeclaration(stmt) { }
    visitExpansionRule(stmt) { }
    visitConstantsRule(stmt) { }
    visitConditionRule(stmt) { }
    visitNonTerminalRuleHead(node) { }
}
exports.NodeVisitor = NodeVisitor;
class Grammar {
    constructor(comment, statements) {
        this.comment = comment;
        this.statements = statements;
    }
    visit(visitor) {
        for (const stmt of this.statements)
            stmt.visit(visitor);
    }
    codegen() {
        let buffer = '';
        buffer += (this.comment);
        for (const stmt of this.statements) {
            if (stmt instanceof JSImportStmt)
                buffer += stmt.codegen();
        }
        buffer += `import type * as $Genie from "genie-toolkit";\n`;
        buffer += `export default async function($runtime : typeof $Genie.SentenceGeneratorRuntime, $ttUtils : typeof $Genie.ThingTalkUtils, $options : $Genie.SentenceGeneratorTypes.GrammarOptions, $locale : $Genie.I18n.LanguagePack, $grammar : $Genie.SentenceGenerator<any, any>, $loader : ThingpediaLoader) : Promise<void> {\n`;
        for (const stmt of this.statements) {
            if (stmt instanceof JSImportStmt)
                continue;
            buffer += stmt.codegen('   ');
        }
        buffer += '};\n';
        return buffer;
    }
}
exports.Grammar = Grammar;
class Statement {
}
exports.Statement = Statement;
class CodeBlock extends Statement {
    constructor(code) {
        super();
        this.code = code;
    }
    codegen() {
        return this.code;
    }
    visit(visitor) { }
}
exports.CodeBlock = CodeBlock;
Statement.CodeBlock = CodeBlock;
class JSImportStmt extends Statement {
    constructor(code) {
        super();
        this.code = code;
    }
    codegen() {
        return `import ${this.code};\n`;
    }
    visit(visitor) { }
}
exports.JSImportStmt = JSImportStmt;
Statement.JSImportStmt = JSImportStmt;
class KeyFunctionDeclarationStmt extends Statement {
    constructor(decls) {
        super();
        this.decls = decls;
    }
    codegen() {
        return ''; // this is a type declaration processed by the compiler,
        // it generates no code
    }
    visit(visitor) {
        visitor.visitKeyFunctionDeclaration(this);
    }
}
exports.KeyFunctionDeclarationStmt = KeyFunctionDeclarationStmt;
Statement.KeyFunctionDeclaration = KeyFunctionDeclarationStmt;
class NonTerminalStmt extends Statement {
    constructor(name, type, rules) {
        super();
        this.name = name;
        this.type = type;
        this.rules = rules;
        this.keyfn = 'undefined';
    }
    visit(visitor) {
        visitor.visitNonTerminalStmt(this);
        for (const rule of this.rules)
            rule.visit(visitor);
    }
    codegen(prefix = '') {
        let buffer = '';
        buffer += (`${prefix}$grammar.declareSymbol(${escaping_1.stringEscape(this.name)});\n`);
        for (const rule of this.rules)
            buffer += rule.codegen(this.name, prefix, this.type || 'any', this.keyfn);
        return buffer;
    }
}
exports.NonTerminalStmt = NonTerminalStmt;
Statement.NonTerminal = NonTerminalStmt;
class ContextStmt extends Statement {
    constructor(names, type) {
        super();
        this.names = names;
        this.type = type;
    }
    visit(visitor) {
        visitor.visitContextStmt(this);
    }
    codegen(prefix = '') {
        return this.names.map((name) => `${prefix}$grammar.declareContext(${escaping_1.stringEscape(name)});\n`).join('');
    }
}
exports.ContextStmt = ContextStmt;
Statement.Context = ContextStmt;
class FunctionDeclarationStmt extends Statement {
    constructor(name, args, code, type = 'any') {
        super();
        this.name = name;
        this.args = args;
        this.code = code;
        this.type = type;
    }
    visit(visitor) { }
    codegen(prefix = '') {
        return `${prefix}$grammar.declareFunction('${this.name}', (${this.args}) : ${this.type} => {${this.code}});\n`;
    }
}
exports.FunctionDeclarationStmt = FunctionDeclarationStmt;
Statement.FunctionDeclaration = FunctionDeclarationStmt;
class ForLoop extends Statement {
    constructor(head, statements) {
        super();
        this.head = head;
        this.statements = statements;
    }
    visit(visitor) {
        for (const stmt of this.statements)
            stmt.visit(visitor);
    }
    codegen(prefix = '') {
        let buffer = '';
        buffer += (`${prefix}for (${this.head}) {\n`);
        for (const stmt of this.statements)
            buffer += stmt.codegen(prefix + '    ');
        buffer += (`${prefix}}\n`);
        return buffer;
    }
}
exports.ForLoop = ForLoop;
Statement.ForLoop = ForLoop;
class IfStmt extends Statement {
    constructor(cond, iftrue, iffalse) {
        super();
        this.cond = cond;
        this.iftrue = iftrue;
        this.iffalse = iffalse;
    }
    visit(visitor) {
        for (const stmt of this.iftrue)
            stmt.visit(visitor);
        for (const stmt of this.iffalse)
            stmt.visit(visitor);
    }
    codegen(prefix = '') {
        let buffer = '';
        buffer += (`${prefix}if (${this.cond}) {\n`);
        for (const stmt of this.iftrue)
            buffer += stmt.codegen(prefix + '    ');
        if (this.iffalse.length > 0) {
            buffer += (`${prefix}} else {\n`);
            for (const stmt of this.iffalse)
                buffer += stmt.codegen(prefix + '    ');
        }
        buffer += (`${prefix}}\n`);
        return buffer;
    }
}
exports.IfStmt = IfStmt;
Statement.If = IfStmt;
class Import extends Statement {
    constructor(what) {
        super();
        this.what = what;
    }
    visit(visitor) {
        visitor.visitImport(this);
    }
    codegen(prefix = '') {
        return `${prefix}await (await $runtime.import(${escaping_1.stringEscape(this.what)}, __dirname))($runtime, $ttUtils, $options, $locale, $grammar, $loader);\n`;
    }
}
exports.Import = Import;
Statement.Import = Import;
class RuleAttributes {
    constructor(attributes = []) {
        this.attributes = attributes;
    }
    codegen() {
        let buf = '{ ';
        buf += this.attributes.map((attr) => `${attr.name}: (${attr.code})`).join(', ');
        buf += ' }';
        return buf;
    }
}
exports.RuleAttributes = RuleAttributes;
class Rule {
}
exports.Rule = Rule;
class Constants extends Rule {
    constructor(token, typeCode, attrs) {
        super();
        this.token = token;
        this.typeCode = typeCode;
        this.attrs = attrs;
    }
    visit(visitor) {
        visitor.visitConstantsRule(this);
    }
    codegen(nonTerminal, prefix = '', type, keyfn) {
        return `${prefix}$grammar.addConstants(${escaping_1.stringEscape(nonTerminal)}, ${escaping_1.stringEscape(this.token)}, ${this.typeCode}, ${keyfn}, ${this.attrs.codegen()});\n`;
    }
}
exports.Constants = Constants;
Rule.Constants = Constants;
function makeBodyLambda(head, body, type = 'any') {
    const bodyArgs = [];
    let i = 0;
    for (const headPart of head) {
        if (headPart instanceof NonTerminalRuleHead && headPart.name)
            bodyArgs.push(headPart.name + ' : ' + headPart.type);
        else
            bodyArgs.push(`$${i++}` + ' : ' + headPart.type);
    }
    return `(${bodyArgs.join(', ')}) : (${type})|null => ${body}`;
}
function getTranslationKey(expansion) {
    let str = '';
    let comment = '';
    let positionalIdx = 0;
    let needsComment = false;
    for (const part of expansion) {
        if (str)
            str += ' ';
        if (comment)
            comment += ' ';
        if (part instanceof StringLiteralRuleHead) {
            str += part.value;
            comment += part.value;
        }
        else if (part instanceof ComputedStringLiteralRuleHead) {
            str += '${' + String(positionalIdx++) + '}';
            comment += '${' + part.code + '}';
            needsComment = true;
        }
        else if (part instanceof NonTerminalRuleHead) {
            str += '${' + part.category + '}';
            comment += '${' + part.category + '}';
        }
        else if (part instanceof ChoiceRuleHead) {
            str += '{' + part.values.join('|') + '}';
            comment += '{' + part.values.join('|') + '}';
        }
        else {
            throw new TypeError();
        }
    }
    return [str, comment, needsComment];
}
class Expansion extends Rule {
    constructor(head, bodyCode, attrs) {
        super();
        this.head = head;
        this.bodyCode = bodyCode;
        this.attrs = attrs;
        assert_1.default(Array.isArray(head));
    }
    visit(visitor) {
        visitor.visitExpansionRule(this);
        for (const head of this.head)
            head.visit(visitor);
    }
    getTranslationKey() {
        return getTranslationKey(this.head);
    }
    codegen(nonTerminal, prefix = '', type, keyfn) {
        const expanderCode = makeBodyLambda(this.head, this.bodyCode, type);
        return `${prefix}$grammar.addRule(${escaping_1.stringEscape(nonTerminal)}, [${this.head.map((h, i) => h.codegen(this.head, i)).join(', ')}], (${expanderCode}), ${keyfn}, ${this.attrs.codegen()});\n`;
    }
}
exports.Expansion = Expansion;
Rule.Expansion = Expansion;
class Condition extends Rule {
    constructor(flag, rules) {
        super();
        this.flag = flag;
        this.rules = rules;
    }
    visit(visitor) {
        visitor.visitConditionRule(this);
        for (const rule of this.rules)
            rule.visit(visitor);
    }
    codegen(nonTerminal, prefix, type, keyfn) {
        const flag = this.flag.startsWith('?') ?
            `$options.flags.${this.flag.substring(1)}` :
            `!$options.flags.${this.flag.substring(1)}`;
        let buffer = '';
        buffer += (`${prefix}if (${flag}) {\n`);
        for (const rule of this.rules)
            buffer += rule.codegen(nonTerminal, prefix + '    ', type, keyfn);
        buffer += (`${prefix}}\n`);
        return buffer;
    }
}
exports.Condition = Condition;
Rule.Condition = Condition;
class NonTerminalConstraint {
}
exports.NonTerminalConstraint = NonTerminalConstraint;
class EqualityNonTerminalConstraint extends NonTerminalConstraint {
    constructor(ourIndexName, nonTermRef, theirIndexName) {
        super();
        this.ourIndexName = ourIndexName;
        this.nonTermRef = nonTermRef;
        this.theirIndexName = theirIndexName;
    }
    codegen(allParts, ourKeyFn, ourIndex) {
        if (ourKeyFn === 'undefined')
            console.error(`WARNING: key function is not set in constraint {${this.ourIndexName} = ${this.nonTermRef}.${this.theirIndexName}}, cannot check correctness statically`);
        const ourTypeConstraint = ourKeyFn === 'undefined' ? '' :
            ` as (${escaping_1.stringEscape(this.ourIndexName)} extends keyof ReturnType<typeof ${ourKeyFn}> ? string : void)`;
        let nonTermIndex, theirKeyFn;
        if (/^[0-9]+/.test(this.nonTermRef)) {
            nonTermIndex = parseInt(this.nonTermRef, 10);
            assert_1.default(allParts[nonTermIndex]);
            theirKeyFn = allParts[nonTermIndex];
        }
        else {
            for (let i = 0; i < allParts.length; i++) {
                const part = allParts[i];
                if (part instanceof NonTerminalRuleHead &&
                    part.name === this.nonTermRef) {
                    nonTermIndex = i;
                    theirKeyFn = part.keyfn;
                    break;
                }
            }
            if (nonTermIndex === undefined)
                throw new Error(`Invalid non-terminal backreference to ${this.nonTermRef} for equality constraint of ${allParts[ourIndex]} (alias not found)`);
            if (nonTermIndex >= ourIndex)
                throw new Error(`Invalid non-terminal backreference to ${this.nonTermRef} for equality constraint of ${allParts[ourIndex]} (alias must precede usage)`);
        }
        if (theirKeyFn === 'undefined')
            console.error(`WARNING: key function is not set in constraint {${this.ourIndexName} = ${this.nonTermRef}.${this.theirIndexName}}, cannot check correctness statically`);
        const theirTypeConstraint = theirKeyFn === 'undefined' ? '' :
            ` as (${escaping_1.stringEscape(this.theirIndexName)} extends keyof ReturnType<typeof ${theirKeyFn}> ? string : void)`;
        return `[${escaping_1.stringEscape(this.ourIndexName)}${ourTypeConstraint}, ${nonTermIndex}, ${escaping_1.stringEscape(this.theirIndexName)}${theirTypeConstraint}]`;
    }
}
exports.EqualityNonTerminalConstraint = EqualityNonTerminalConstraint;
NonTerminalConstraint.Equality = EqualityNonTerminalConstraint;
class ConstantNonTerminalConstraint extends NonTerminalConstraint {
    constructor(indexName, valueCode) {
        super();
        this.indexName = indexName;
        this.valueCode = valueCode;
    }
    codegen(allParts, ourKeyFn) {
        if (ourKeyFn === 'undefined')
            console.error(`WARNING: key function is not set in constraint {${this.indexName} = ${this.valueCode}}, cannot check correctness statically`);
        const ourTypeConstraint = ourKeyFn === 'undefined' ? '' :
            ` as (${escaping_1.stringEscape(this.indexName)} extends keyof ReturnType<typeof ${ourKeyFn}> ? string : void)`;
        const constantTypeConstraint = ourKeyFn === 'undefined' ? '' :
            ` as ReturnType<typeof ${ourKeyFn}>[${escaping_1.stringEscape(this.indexName)}]`;
        return `[${escaping_1.stringEscape(this.indexName)}${ourTypeConstraint}, (${this.valueCode})${constantTypeConstraint}]`;
    }
}
exports.ConstantNonTerminalConstraint = ConstantNonTerminalConstraint;
NonTerminalConstraint.Constant = ConstantNonTerminalConstraint;
class RuleHeadPart {
}
exports.RuleHeadPart = RuleHeadPart;
class NonTerminalRuleHead extends RuleHeadPart {
    constructor(name, category, constraint) {
        super();
        this.name = name;
        this.category = category;
        this.constraint = constraint;
        this.type = 'any';
        this.keyfn = 'undefined';
    }
    toString() {
        return `${this.name} : NT[${this.category}]`;
    }
    visit(visitor) {
        visitor.visitNonTerminalRuleHead(this);
    }
    codegen(allParts, index) {
        return `new $runtime.NonTerminal(${escaping_1.stringEscape(this.category)}, ${this.constraint ? this.constraint.codegen(allParts, this.keyfn, index) : 'undefined'})`;
    }
}
exports.NonTerminalRuleHead = NonTerminalRuleHead;
RuleHeadPart.NonTerminal = NonTerminalRuleHead;
class StringLiteralRuleHead extends RuleHeadPart {
    constructor(value) {
        super();
        this.value = value;
        this.type = 'undefined';
    }
    visit(visitor) { }
    codegen() {
        return escaping_1.stringEscape(this.value);
    }
}
exports.StringLiteralRuleHead = StringLiteralRuleHead;
RuleHeadPart.StringLiteral = StringLiteralRuleHead;
class ComputedStringLiteralRuleHead extends RuleHeadPart {
    constructor(code) {
        super();
        this.code = code;
        this.type = 'undefined';
    }
    visit(visitor) { }
    codegen() {
        return this.code;
    }
}
exports.ComputedStringLiteralRuleHead = ComputedStringLiteralRuleHead;
RuleHeadPart.ComputedStringLiteral = ComputedStringLiteralRuleHead;
class ChoiceRuleHead extends RuleHeadPart {
    constructor(values) {
        super();
        this.values = values;
        this.type = 'undefined';
    }
    visit(visitor) { }
    codegen() {
        return `new $runtime.Choice([${this.values.map(escaping_1.stringEscape).join(', ')}])`;
    }
}
exports.ChoiceRuleHead = ChoiceRuleHead;
RuleHeadPart.Choice = ChoiceRuleHead;
//# sourceMappingURL=meta_ast.js.map