"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Parser = exports.BlockCommand = void 0;
const TSU = __importStar(require("@panyam/tsutils"));
const G = __importStar(require("galore"));
const TLEX = __importStar(require("tlex"));
const yaml_1 = __importDefault(require("yaml"));
const core_1 = require("./core");
const notation_1 = require("./notation");
const commands_1 = require("./commands");
const carnatic = __importStar(require("./carnatic"));
const ONE = TSU.Num.Fraction.ONE;
const TWO = ONE.timesNum(2);
const [parser] = G.newParser(String.raw `
    %define IdentChar     /[^%!@$#&\^|\[\]={}()<>+\-,;~: \t\f\r\n\v\\\.\'\"]/

    %token  BSLASH        "\\"
    %token  OPEN_SQ       "["
    %token  CLOSE_SQ      "]"
    %token  EQUALS        "="
    %token  OPEN_PAREN    "("
    %token  CLOSE_PAREN   ")"
    %token  OPEN_BRACE    "{"
    %token  CLOSE_BRACE   "}"
    %token  SLASH         "/"
    %token  STAR          "*"
    %token  CARET         "^"
    // %skip "-"
    %token  COMMA         ","
    %token  SEMI_COLON    ";"
    %token  COLON         ":"

    %token  FRONT_MATTER                 /^---$/m    { toFrontMatter }
    %token  SINGLE_LINE_RAW_STRING       />(.*)$/m    { toSingleLineRawString }
    %token  MULTI_LINE_RAW_STRING        /r(#{0,})"/  { toMultiLineRawString }

    %token  EMBELISHMENT  /~[^\s]*/                 { toEmbelishment }
    %token  NUMBER        /-?\d+/                   { toNumber }
    %token  BOOLEAN       /true|false/              { toBoolean }
    %token  POST_MARKER   /<<"(([^"\\\n]|\\.|\\\n)*)"/  { toMarker }
    %token  POST_MARKER   /<<'(([^'\\\n]|\\.|\\\n)*)'/  { toMarker }
    %token  PRE_MARKER   /"(([^"\\\n]|\\.|\\\n)*)">>/  { toMarker }
    %token  PRE_MARKER   /'(([^'\\\n]|\\.|\\\n)*)'>>/  { toMarker }
    %token  STRING        /"([^"\\\n]|\\.|\\\n)*"/  { toString }
    %token  STRING        /'([^'\\\n]|\\.|\\\n)*'/  { toString }
    %token  DOTS_IDENT    /(\.+)({IdentChar}+)/     { toOctavedNote   }
    %token  IDENT_DOTS    /({IdentChar}+)(\.+)/     { toOctavedNote   }
    %token  IDENT_COLON   /{IdentChar}+:/           { toRoleSelector  }
    %token  IDENT         /{IdentChar}+/
    %token  BSLASH_IDENT  /\\{IDENT}/               { toCommandName   }
    %token  BSLASH_NUMBER /\\{NUMBER}/
    %token  HYPHEN        /-/
    %skip                 /[ \t\n\f\r]+/
    %skip_flex            "//.*$"
    %skip                 /\/\*.*?\*\//

    Document -> Elements
              | FRONT_MATTER Elements { prependFrontMatter }
              ;

    Elements -> Elements Command Atoms { appendCommand } 
              | Elements RoleSelector Atoms { appendRoleSelector } 
              | Elements Embedding  Atoms { insertEmbedding }
              | Atoms { appendAtoms }
              ;

    Embedding -> SINGLE_LINE_RAW_STRING | MULTI_LINE_RAW_STRING ;

    Command -> BSLASH_IDENT CommandParams ? OptBlock { newCommandWithBlock } ;
    CommandParams -> OPEN_PAREN ParamList ? CLOSE_PAREN { $2 } ;
    OptBlock -> Block
             | { nullBlock }
             ;
    Block -> BlockStart Elements CLOSE_BRACE { endBlock } ;
    BlockStart -> OPEN_BRACE { beginBlock } ;

    ParamList -> ParamList COMMA Param { concatParamList } ;
    ParamList -> Param { newParamList };
    Param -> ParamValue { newParam } ;
    Param -> ParamKey EQUALS ParamValue { newParam } ;
    ParamKey  -> IDENT ;
    ParamValue -> ( STRING | Fraction | NUMBER | BOOLEAN ) ;

    RoleSelector -> IDENT_COLON ;

    Atoms -> Atoms Atom { concatAtoms } ;
    Atoms -> { newArray } ;

    Atom -> Atom POST_MARKER  { applyPostMarker }
          | PreMarkedAtom
          ;
    PreMarkedAtom -> Leaf
                  | PRE_MARKER PreMarkedAtom { applyPreMarker }
                  ;

    Leaf -> Space | Lit | Group | Rest  ;
    Leaf -> Duration Leaf { applyDuration } ;
    Rest -> HYPHEN { newRest };
    Space -> COMMA { newSpace } 
          | SEMI_COLON { newDoubleSpace } 
          | UNDER_SCORE { newSilentSpace } 
          ;

    /* - An alternative representation to support both pre and
     *   post embelishment operators.   Here PRE_EMBs have a higher
     *   associativity over POST_EMBs
     */
     Lit -> Lit POST_EMB  { litWithPostEmb }
         | PreEmbLit
         ;

     PreEmbLit -> LitToken 
                | LitToken CARET NUMBER { litWithCaret }
                | LitToken CARET STAR   { litWithCaret }
                | PRE_EMB PreEmbLit     { litWithPreEmb }
                ;
      
     LitToken -> DOTS_IDENT { litToAtom }
              | IDENT { litToAtom }
              | IDENT_DOTS { litToAtom }
              | STRING  { litToAtom }
              ;

    Group -> OPEN_SQ Atoms CLOSE_SQ { newGroup };

    Duration -> Fraction | NUMBER;
    Fraction -> NUMBER SLASH NUMBER { newFraction } ;
    `, {
    debug: "",
    type: "lalr",
    leftRecursive: true,
    tokenHandlers: {
        toEmbelishment: (token, tape, owner) => {
            const [emb, pre] = owner.parseEmbelishment(token.value);
            if (emb == null) {
                console.log("Skipping Embelishment: ", token.value);
                owner.errors.push(new TLEX.TokenizerError(`Invalid embelishment: ${token.value}`, token.start, 1 + token.end - token.start, "InvalidEmbelishment", token.value));
                return null;
            }
            token.value = emb;
            token.tag = pre ? "PRE_EMB" : "POST_EMB";
            return token;
        },
        toCommandName: (token, _tape, _owner) => {
            token.value = token.value.substring(1);
            return token;
        },
        toBoolean: (token, _tape, _owner) => {
            token.value = token.value == "true";
            return token;
        },
        toNumber: (token, _tape, _owner) => {
            token.value = parseInt(token.value);
            return token;
        },
        toString: (token, _tape, _owner) => {
            token.value = token.value.substring(1, token.value.length - 1);
            return token;
        },
        toMarker: (token, _tape, _owner) => {
            if (token.tag != "PRE_MARKER" && token.tag != "POST_MARKER") {
                throw new Error("Invalid token for converting to note: " + token.tag);
            }
            const isBefore = token.tag == "PRE_MARKER";
            const markerText = isBefore
                ? token.value.substring(1, token.value.length - 3)
                : token.value.substring(3, token.value.length - 1);
            token.value = new core_1.Marker(markerText, isBefore);
            return token;
        },
        toOctavedNote: (token, _tape, _owner) => {
            if (token.tag == "DOTS_IDENT") {
                const octave = token.positions[1][1] - token.positions[1][0];
                const note = token.value.substring(octave);
                token.value = new core_1.Note(note, ONE, -octave);
            }
            else if (token.tag == "IDENT_DOTS") {
                const octave = token.positions[2][1] - token.positions[2][0];
                const note = token.value.substring(0, token.value.length - octave);
                token.value = new core_1.Note(note, ONE, octave);
            }
            else {
                throw new Error("Invalid token for converting to note: " + token.tag);
            }
            return token;
        },
        toRoleSelector: (token, _tape, _owner) => {
            token.value = token.value.substring(0, token.value.length - 1);
            return token;
        },
        toLineAnnotation: (token, _tape, _owner) => {
            token.value = token.value.substring(1);
            return token;
        },
        toSingleLineRawString: (token, _tape, _owner) => {
            token.value = token.value.substring(1);
            return token;
        },
        toMultiLineRawString: (token, tape, _owner) => {
            const hashes = tape.substring(token.positions[1][0], token.positions[1][1]);
            const endPat = '"' + hashes;
            const startPos = tape.index;
            const endPos = TLEX.TapeHelper.advanceAfter(tape, endPat) - endPat.length;
            if (endPos < 0) {
                throw new Error("EOF expected while finding end of Raw String Literal: '" + endPat + "'");
            }
            token.value = tape.substring(startPos, endPos);
            return token;
        },
        toFrontMatter: (token, tape, _owner) => {
            const endPat = "\n---";
            const startPos = tape.index;
            const endPos = TLEX.TapeHelper.advanceAfter(tape, endPat) - endPat.length;
            if (endPos < 0) {
                throw new Error("EOF expected while finding end of front matter");
            }
            token.value = tape.substring(startPos, endPos);
            return token;
        },
    },
});
const block_1 = require("./block");
const utils_1 = require("./utils");
class BlockCommand extends notation_1.Command {
    constructor(innerCommand, blockCommands) {
        super(innerCommand.params);
        this.innerCommand = innerCommand;
        this.blockCommands = blockCommands;
    }
    get name() {
        return `Block(${this.innerCommand.name})`;
    }
    createBlock(parent) {
        const cmd = this.innerCommand;
        if (cmd instanceof commands_1.Section) {
            return new block_1.SectionBlock(cmd.sectionName, parent);
        }
        else if (cmd instanceof commands_1.Repeat) {
            return new block_1.RepeatBlock(cmd.count, parent);
        }
        else if (cmd instanceof commands_1.SetCycle) {
            const cycle = (0, utils_1.parseCycle)(cmd.getParamAt(0));
            return new block_1.CycleBlock(cycle, parent);
        }
        else if (cmd instanceof commands_1.SetBeatDuration) {
            return new block_1.BeatDurationBlock(cmd.beatDuration, parent);
        }
        else if (cmd instanceof commands_1.SetBreaks) {
            return new block_1.BreaksBlock(cmd.pattern, parent);
        }
        else if (cmd instanceof commands_1.CreateRole) {
            const roleName = cmd.getParamAt(0);
            return new block_1.RoleBlock(roleName, cmd.notesOnly, parent);
        }
        else if (cmd instanceof commands_1.ScopedGroup) {
            return new block_1.GroupBlock(cmd.groupName || null, parent);
        }
        else {
            return new block_1.Block(cmd.name.toLowerCase(), parent);
        }
    }
    debugValue() {
        return {
            name: this.name,
            index: this.index,
            innerCommand: this.innerCommand.debugValue(),
            blockCommands: this.blockCommands.map((cmd) => cmd.debugValue()),
        };
    }
    applyToNotation(notation) {
        const block = this.createBlock(notation);
        for (const cmd of this.blockCommands) {
            cmd.applyToBlock(block);
        }
        notation.addBlockItem(block);
    }
    applyToBlock(container) {
        const block = this.createBlock(container);
        for (const cmd of this.blockCommands) {
            cmd.applyToBlock(block);
        }
        container.addBlockItem(block);
    }
}
exports.BlockCommand = BlockCommand;
class Parser {
    constructor(_config) {
        this.errors = [];
        this.metadata = {};
        this.commands = [];
        this.blockStartStack = [];
        this.ruleHandlers = {
            newFraction: (rule, parent, ...children) => {
                if (children.length == 1) {
                    return new TSU.Num.Fraction(children[0].value);
                }
                else {
                    return new TSU.Num.Fraction(children[0].value, children[2].value);
                }
            },
            newGroup: (rule, parent, ...children) => {
                const out = new core_1.Group(...children[1].value).setDuration(TWO, true);
                return out;
            },
            litWithCaret: (rule, parent, ...children) => {
                const lit = core_1.Note.fromLit(children[0].value);
                const opnode = children[2];
                const shiftValue = opnode.sym.label == "STAR" ? true : opnode.value;
                lit.shift = shiftValue;
                return lit;
            },
            litWithPreEmb: (rule, parent, ...children) => {
                const emb = children[0];
                const lit = children[1].value;
                lit.embelishments.splice(0, 0, emb.value);
                return lit;
            },
            litWithPostEmb: (rule, parent, ...children) => {
                const lit = children[0].value;
                const emb = children[1];
                lit.embelishments.push(emb);
                return lit;
            },
            litToAtom: (rule, parent, ...children) => {
                const lit = children[0];
                if (lit.sym.label == "DOTS_IDENT" || lit.sym.label == "IDENT_DOTS") {
                    return lit.value;
                }
                else if (lit.sym.label == "IDENT") {
                    return new core_1.Literal(lit.value);
                }
                else if (lit.sym.label == "STRING") {
                    return new core_1.Syllable(lit.value);
                }
                else {
                    throw new Error("Invalid lit: " + lit);
                }
            },
            newSpace: (_rule, _parent, ..._children) => {
                return new core_1.Space();
            },
            newRest: (_rule, _parent, ..._children) => {
                return new core_1.Rest();
            },
            newDoubleSpace: (_rule, _parent, ..._children) => {
                return new core_1.Space(ONE.timesNum(2));
            },
            newSilentSpace: (_rule, _parent, ..._children) => {
                return new core_1.Space(ONE, true);
            },
            applyPreMarker: (rule, parent, ...children) => {
                const marker = children[0].value;
                const leaf = children[1].value;
                if (!leaf.markersBefore) {
                    leaf.markersBefore = [];
                }
                leaf.markersBefore.splice(0, 0, marker);
                return leaf;
            },
            applyPostMarker: (rule, parent, ...children) => {
                const leaf = children[0].value;
                const marker = children[1].value;
                if (!leaf.markersAfter) {
                    leaf.markersAfter = [];
                }
                leaf.markersAfter.push(marker);
                return leaf;
            },
            applyDuration: (rule, parent, ...children) => {
                let dur = children[0].value;
                const leaf = children[1].value;
                if (typeof dur === "number") {
                    dur = ONE.timesNum(dur);
                }
                if (leaf.TYPE == core_1.AtomType.GROUP) {
                    leaf.setDuration(dur, true);
                }
                else {
                    leaf.duration = dur;
                }
                return leaf;
            },
            newArray: (rule, parent, ...children) => {
                return children.map((c) => c.value);
            },
            concatAtoms: (rule, parent, ...children) => {
                const atoms = children[0].value;
                const atom = children[1].value;
                atoms.push(atom);
                return atoms;
            },
            newParam: (rule, parent, ...children) => {
                if (children.length == 1) {
                    return { key: null, value: children[0].value };
                }
                else {
                    return { key: children[0].value, value: children[2].value };
                }
            },
            newParamList: (rule, parent, ...children) => {
                return [children[0].value];
            },
            concatParamList: (rule, parent, ...children) => {
                const params = children[0].value;
                const newParam = children[2].value;
                params.push(newParam);
                return params;
            },
            beginBlock: (_rule, _parent, ..._children) => {
                this.blockStartStack.push(this.commands.length);
                return null;
            },
            endBlock: (_rule, _parent, ..._children) => {
                const startIndex = this.blockStartStack.pop();
                const blockCommands = this.commands.splice(startIndex);
                return blockCommands;
            },
            nullBlock: (_rule, _parent, ..._children) => {
                return null;
            },
            newCommandWithBlock: (rule, parent, ...children) => {
                const name = children[0].value;
                const params = children[1].value;
                const blockCommands = children[2].value;
                const innerCommand = this.createCommand(name, params);
                if (blockCommands !== null) {
                    return new BlockCommand(innerCommand, blockCommands);
                }
                else {
                    return innerCommand;
                }
            },
            appendAtoms: (rule, parent, ...children) => {
                const atoms = children[0].value;
                if (atoms.length > 0) {
                    this.addCommand(new commands_1.AddAtoms(...atoms));
                }
                return null;
            },
            prependFrontMatter: (rule, parent, ...children) => {
                const fm = children[0].value;
                const data = yaml_1.default.parse(fm);
                console.log("Parsed FM: ", data);
                this.metadata = data;
                return children[1].value;
            },
            appendCommand: (rule, parent, ...children) => {
                const command = children[1].value;
                this.addCommand(command);
                const atoms = children[2].value;
                if (atoms.length > 0) {
                    this.addCommand(new commands_1.AddAtoms(...atoms));
                }
                return null;
            },
            appendRoleSelector: (rule, parent, ...children) => {
                const roleName = children[1].value;
                const lName = roleName.toLowerCase().trim();
                this.addCommand(new commands_1.ActivateRole([{ key: null, value: lName }]));
                const atoms = children[2].value;
                if (atoms.length > 0) {
                    this.addCommand(new commands_1.AddAtoms(...atoms));
                }
                return null;
            },
            insertEmbedding: (rule, parent, ...children) => {
                const rawVal = children[1].value;
                this.addCommand(new commands_1.RawEmbedding([{ key: null, value: rawVal }]));
                const atoms = children[2].value;
                if (atoms.length > 0) {
                    this.addCommand(new commands_1.AddAtoms(...atoms));
                }
                return null;
            },
        };
    }
    createCommand(name, params) {
        const lName = name.trim().toLowerCase();
        params = params || [];
        if (lName == "line") {
            return new commands_1.CreateLine(params);
        }
        else if (lName == "role") {
            return new commands_1.CreateRole(params);
        }
        else if (lName == "layout") {
            return new commands_1.ApplyLayout(params);
        }
        else if (lName == "beatduration") {
            return new commands_1.SetBeatDuration(params);
        }
        else if (lName == "breaks") {
            return new commands_1.SetBreaks(params);
        }
        else if (lName == "cycle") {
            return new commands_1.SetCycle(params);
        }
        else if (lName == "section") {
            return new commands_1.Section(params);
        }
        else if (lName == "group") {
            return new commands_1.ScopedGroup(params);
        }
        else if (lName == "repeat") {
            return new commands_1.Repeat(params);
        }
        else {
            throw new Error("Invalid command: " + lName);
        }
    }
    addCommand(cmd) {
        cmd.index = this.commands.length;
        this.commands.push(cmd);
    }
    parse(input) {
        this.errors = [];
        try {
            const ptree = parser.parse(input, {
                tokenizerContext: this,
                ruleHandlers: this.ruleHandlers,
                onTokenError: (err) => {
                    this.errors.push(err);
                    return true;
                },
            });
            return ptree;
        }
        catch (err) {
            if (err instanceof G.ParseError) {
                this.errors.push(err);
            }
            else {
                throw err;
            }
        }
        return null;
    }
    parseAndBuild(input) {
        this.parse(input);
        const notation = new notation_1.Notation();
        const errors = [];
        errors.push(...this.errors);
        for (const key in this.metadata)
            notation.addMetaData(new notation_1.MetaData(key, this.metadata[key]));
        for (const cmd of this.commands)
            cmd.applyToNotation(notation);
        return [notation, errors];
    }
    parseEmbelishment(value) {
        return carnatic.parseEmbelishment(value);
    }
}
exports.Parser = Parser;
//# sourceMappingURL=parser.js.map