<commit_before>var compile = require('./compile').compile,
    lexer = require('./lexer'),
    loadModule = require('./modules').loadModule,
    parser = require('../lib/parser').parser,
    types = require('./types'),
    nodeToType = require('./typeinference').nodeToType,
    _ = require('underscore');

var getSandbox = function() {
    var sandbox = {require: require, exports: exports};

    var name;
    for(name in global) {
        sandbox[name] = global[name];
    }

    return sandbox;
};

var getFileContents = function(filename) {
    var fs = require('fs'),
        exts = ["", ".roy", ".lroy"],
        filenames = _.map(exts, function(ext){
            return filename + ext;
        }),
        foundFilename,
        source,
        err,
        i;

    // Check to see if an extension is specified, if so, don't bother
    // checking others
    if (/\..+$/.test(filename)) {
        source = fs.readFileSync(filename, 'utf8');
        filenames = [filename];
    } else {
        foundFilename = _.find(filenames, function(filename) {
            return fs.existsSync(filename);
        });
        if(foundFilename) {
            source = fs.readFileSync(foundFilename, 'utf8');
        }
    }

    if(!source) {
        throw new Error("File(s) not found: " + filenames.join(", "));
    }

    return source;
};

var colorLog = function(color) {
    var args = [].slice.call(arguments, 1);

    args[0] = '\u001b[' + color + 'm' + args[0];
    args[args.length - 1] = args[args.length - 1] + '\u001b[0m';

    console.log.apply(console, args);
};

var nodeRepl = function(opts) {
    var readline = require('readline'),
        path = require('path'),
        vm = require('vm');

    var stdout = process.stdout;
    var stdin = process.openStdin();
    var repl = readline.createInterface(stdin, stdout);

    var env = {};
    var sources = {};
    var aliases = {};
    var sandbox = getSandbox();

    var block = [];
    var inBlock = false;

    // Prologue
    console.log("Roy: " + opts.info.description);
    console.log(opts.info.author);
    console.log(":? for help");

    // Include the standard library
    var fs = require('fs');
    var prelude = fs.readFileSync(path.dirname(__dirname) + '/lib/prelude.roy', 'utf8');
    vm.runInNewContext(compile(prelude, env, {}, {nodejs: true}).output, sandbox, 'eval');
    repl.setPrompt('roy> ');
    repl.on('close', function() {
        stdin.destroy();
    });
    repl.on('line', function(line) {
        var compiled;
        var output;
        var joined;

        var tokens;
        var ast;

        // Check for a "metacommand"
        // e.g. ":q" or ":l test.roy"
        var metacommand = line.replace(/^\s+/, '').split(' ');
        try {
            if (!inBlock && /^:/.test(metacommand[0])) {
                compiled = processMeta(metacommand, env, aliases, sources);
            } else if (/(=|->|→|\(|\{|\[|\bthen|\b(do|match).+?)\s*$/.test(line)) {
                // A block is starting.
                // E.g.: let, lambda, object, match, etc.
                // Remember that, change the prompt to signify we're in a block,
                // and start keeping track of the lines in this block.
                inBlock = true;
                repl.setPrompt('...  ');
                block.push(line);
            } else if (inBlock && /\S/.test(line)) {
                // We're still in the block.
                block.push(line);
            } else {
                // End of a block.
                // Add the final line to the block, and reset our stuff.
                block.push(line);
                joined = block.join('\n');

                inBlock = false;
                repl.setPrompt('roy> ');
                block = [];

                // Remember the source if it's a binding
                tokens = lexer.tokenise(joined);
                ast = parser.parse(tokens);

                if (typeof ast.body[0] != 'undefined') {
                    ast.body[0].accept({
                        // Simple bindings.
                        // E.g.: let x = 37
                        visitLet: function(n) {
                            sources[n.name] = n.value;
                        },
                        // Bindings that are actually functions.
                        // E.g.: let f x = 37
                        visitFunction: function(n) {
                            sources[n.name] = n;
                        }
                    });
                }

                // Just eval it
                compiled = compile(joined, env, aliases, {nodejs: true, filename: ".", run: true});
            }

            if(compiled) {
                output = vm.runInNewContext(compiled.output, sandbox, 'eval');

                if(typeof output != 'undefined') {
                    colorLog(32, (typeof output == 'object' ? JSON.stringify(output) : output) + " : " + compiled.type);
                }
            }
        } catch(e) {
            colorLog(31, (e.stack || e.toString()));
            // Reset the block because something wasn't formatted properly.
            block = [];
        }
        repl.prompt();
    });
    repl.prompt();
};

var writeModule = function(env, exported, filename) {
    var fs = require('fs');

    var moduleOutput = _.map(exported, function(v, k) {
        if(v instanceof types.TagType) {
            return 'type ' + v.toString().replace(/#/g, '');
        }
        return k + ': ' + v.toString();
    }).join('\n') + '\n';
    fs.writeFile(filename, moduleOutput, 'utf8');
};

var importModule = function(name, env, opts) {
    var addTypesToEnv = function(moduleTypes) {
        _.each(moduleTypes, function(v, k) {
            var dataType = [new types.TagNameType(k)];
            _.each(function() {
                dataType.push(new types.Variable());
            });
            env[k] = new types.TagType(dataType);
        });
    };

    var moduleTypes;
    if(opts.nodejs) {
        // Need to convert to absolute paths for the CLI
        if(opts.run) {
            var path = require("path");
            name = path.resolve(path.dirname(opts.filename), name);
        }

        moduleTypes = loadModule(name, opts);
        addTypesToEnv(moduleTypes.types);
        var variable = name.substr(name.lastIndexOf("/") + 1);
        env[variable] = new types.Variable();
        var props = {};
        _.each(moduleTypes.env, function(v, k) {
            props[k] = nodeToType(v, env, {});
        });
        env[variable] = new types.ObjectType(props);

        console.log("Using sync CommonJS module:", name);

        return variable + " = require(" + JSON.stringify(name) + ")";
    } else {
        moduleTypes = loadModule(name, opts);
        addTypesToEnv(moduleTypes.types);
        _.each(moduleTypes.env, function(v, k) {
            env[k] = nodeToType(v, env, {});
        });

        if(console) console.log("Using browser module:", name);

        return "";
    }
};

var runRoy = function(argv, opts) {
    var fs = require('fs');
    var path = require('path');
    var vm = require('vm');

    var extensions = /\.l?roy$/;
    var literateExtension = /\.lroy$/;

    var exported;
    var env = {};
    var aliases = {};
    var sandbox = getSandbox();

    if(opts.run) {
        // Include the standard library
        if(opts.includePrelude) {
            argv.unshift(path.dirname(__dirname) + '/lib/prelude.roy');
        }
    } else {
        var modules = [];
        if(!argv.length || argv[0] != 'lib/prelude.roy') {
            modules.push(path.dirname(__dirname) + '/lib/prelude');
        }
        _.each(modules, function(module) {
            var moduleTypes = loadModule(module, {filename: '.'});
            _.each(moduleTypes.env, function(v, k) {
                env[k] = new types.Variable();
                env[k] = nodeToType(v, env, aliases);
            });
        });
    }

    _.each(argv, function(filename) {
        // Read the file content.
        var source = getFileContents(filename);

        if(filename.match(literateExtension)) {
            // Strip out the Markdown.
            source = source.match(/^ {4,}.+$/mg).join('\n').replace(/^ {4}/gm, '');
        } else {
            console.assert(filename.match(extensions), 'Filename must end with ".roy" or ".lroy"');
        }

        exported = {};
        var outputPath = filename.replace(extensions, '.js');
        var SourceMapGenerator = require('source-map').SourceMapGenerator;
        var sourceMap = new SourceMapGenerator({file: path.basename(outputPath)});

        var compiled = compile(source, env, aliases, {
            nodejs: opts.nodejs,
            filename: filename,
            run: opts.run,
            exported: exported,
            sourceMap: sourceMap
        });
        if(opts.run) {
            // Execute the JavaScript output.
            var output = vm.runInNewContext(compiled.output, sandbox, 'eval');
        } else {
            // Write the JavaScript output.
            fs.writeFile(outputPath, compiled.output + '//@ sourceMappingURL=' + path.basename(outputPath) + '.map\n', 'utf8');
            fs.writeFile(outputPath + '.map', sourceMap.toString(), 'utf8');
            writeModule(env, exported, filename.replace(extensions, '.roym'));
        }
    });
};

var processFlags = function(argv, opts) {
    if(argv.length === 0) {
        nodeRepl(opts);
        return;
    }

    switch(argv[0]) {
    case "-v":
    case "--version":
        console.log("Roy: " + opts.info.description);
        console.log(opts.info.version);
        process.exit();
         break;
    case "-h":
    case "--help":
        console.log("Roy: " + opts.info.description + "\n");
        console.log("-b --browser   : wrap the output in a top level function");
        console.log("   --strict    : include \"use strict\";");
        console.log("-h --help      : show this help");
        console.log("-p             : run without prelude (standard library)");
        console.log("-r [file]      : run Roy-code without JavaScript output");
        console.log("-s --stdio     : read script from stdin and output to stdout");
        console.log("-v --version   : show current version");
        return;
    case "-s":
    case "--stdio":
        var source = '';
        process.stdin.resume();
        process.stdin.setEncoding('utf8');
        process.stdin.on('data', function(data) {
            source += data;
        });
        process.stdin.on('end', function() {
            console.log(compile(source, null, null, opts).output);
        });
        return;
    case "-p":
        opts.includePrelude = false;
        /* falls through */
    case "-r":
        opts.run = true;
        argv.shift();
        break;
    case "-b":
    case "--browser":
        opts.nodejs = false;
        argv.shift();
        break;
    case "--strict":
        opts.strict = true;
        argv.shift();
        break;
    case "-c":
    case "--color":
        // The default now
        nodeRepl(opts);
        return;
    default:
        runRoy(argv, opts);
        return;
    }

    processFlags(argv, opts);
};

var processMeta = function(commands, env, aliases, sources) {
    var compiled,
        prettyPrint = require('./prettyprint').prettyPrint,
        source;

    switch(commands[0]) {
    case ":q":
        // Exit
        process.exit();
        break;
    case ":l":
        // Load
        source = getFileContents(commands[1]);
        return compile(source, env, aliases, {nodejs: true, filename: ".", run: true});
    case ":t":
        if(commands[1] in env) {
            console.log(env[commands[1]].toString());
        } else {
            colorLog(33, commands[1], "is not defined.");
        }
        break;
    case ":s":
        // Source
        if(sources[commands[1]]) {
            colorLog(33, commands[1], "=", prettyPrint(sources[commands[1]]));
        } else {
            if(commands[1]){
                colorLog(33, commands[1], "is not defined.");
            }else{
                console.log("Usage :s command ");
                console.log(":s [identifier] :: show original code about identifier.");
            }
        }
        break;
    case ":?":
        // Help
        colorLog(32, "Commands available from the prompt");
        console.log(":l -- load and run an external file");
        console.log(":q -- exit REPL");
        console.log(":s -- show original code about identifier");
        console.log(":t -- show the type of the identifier");
        console.log(":? -- show help");
        break;
    default:
        colorLog(31, "Invalid command");
        console.log(":? for help");
        break;
    }
};

var main = function() {
    var argv = process.argv.slice(2);

    // Roy package information
    var fs = require('fs');
    var path = require('path');

    // Meta-commands configuration
    var opts = {
        info: JSON.parse(fs.readFileSync(path.dirname(__dirname) + '/package.json', 'utf8')),
        nodejs: true,
        run: false,
        includePrelude: true,
        strict: false
    };

    processFlags(argv, opts);
};
module.exports = main;

if(module && !module.parent) {
    main();
}
<commit_msg>Handle do/match better.<commit_after>var compile = require('./compile').compile,
    lexer = require('./lexer'),
    loadModule = require('./modules').loadModule,
    parser = require('../lib/parser').parser,
    types = require('./types'),
    nodeToType = require('./typeinference').nodeToType,
    _ = require('underscore');

var getSandbox = function() {
    var sandbox = {require: require, exports: exports};

    var name;
    for(name in global) {
        sandbox[name] = global[name];
    }

    return sandbox;
};

var getFileContents = function(filename) {
    var fs = require('fs'),
        exts = ["", ".roy", ".lroy"],
        filenames = _.map(exts, function(ext){
            return filename + ext;
        }),
        foundFilename,
        source,
        err,
        i;

    // Check to see if an extension is specified, if so, don't bother
    // checking others
    if (/\..+$/.test(filename)) {
        source = fs.readFileSync(filename, 'utf8');
        filenames = [filename];
    } else {
        foundFilename = _.find(filenames, function(filename) {
            return fs.existsSync(filename);
        });
        if(foundFilename) {
            source = fs.readFileSync(foundFilename, 'utf8');
        }
    }

    if(!source) {
        throw new Error("File(s) not found: " + filenames.join(", "));
    }

    return source;
};

var colorLog = function(color) {
    var args = [].slice.call(arguments, 1);

    args[0] = '\u001b[' + color + 'm' + args[0];
    args[args.length - 1] = args[args.length - 1] + '\u001b[0m';

    console.log.apply(console, args);
};

var nodeRepl = function(opts) {
    var readline = require('readline'),
        path = require('path'),
        vm = require('vm');

    var stdout = process.stdout;
    var stdin = process.openStdin();
    var repl = readline.createInterface(stdin, stdout);

    var env = {};
    var sources = {};
    var aliases = {};
    var sandbox = getSandbox();

    var block = [];
    var inBlock = false;

    // Prologue
    console.log("Roy: " + opts.info.description);
    console.log(opts.info.author);
    console.log(":? for help");

    // Include the standard library
    var fs = require('fs');
    var prelude = fs.readFileSync(path.dirname(__dirname) + '/lib/prelude.roy', 'utf8');
    vm.runInNewContext(compile(prelude, env, {}, {nodejs: true}).output, sandbox, 'eval');
    repl.setPrompt('roy> ');
    repl.on('close', function() {
        stdin.destroy();
    });
    repl.on('line', function(line) {
        var compiled;
        var output;
        var joined;

        var tokens;
        var ast;

        // Check for a "metacommand"
        // e.g. ":q" or ":l test.roy"
        var metacommand = line.replace(/^\s+/, '').split(' ');
        try {
            if (!inBlock && /^:/.test(metacommand[0])) {
                compiled = processMeta(metacommand, env, aliases, sources);
            } else if (/(=|->|→|\(|\{|\[|\bthen|\b(do|match)\s+.+?)\s*$/.test(line)) {
                // A block is starting.
                // E.g.: let, lambda, object, match, etc.
                // Remember that, change the prompt to signify we're in a block,
                // and start keeping track of the lines in this block.
                inBlock = true;
                repl.setPrompt('.... ');
                block.push(line);
            } else if (inBlock && /\S/.test(line)) {
                // We're still in the block.
                block.push(line);
            } else {
                // End of a block.
                // Add the final line to the block, and reset our stuff.
                block.push(line);
                joined = block.join('\n');

                inBlock = false;
                repl.setPrompt('roy> ');
                block = [];

                // Remember the source if it's a binding
                tokens = lexer.tokenise(joined);
                ast = parser.parse(tokens);

                if (typeof ast.body[0] != 'undefined') {
                    ast.body[0].accept({
                        // Simple bindings.
                        // E.g.: let x = 37
                        visitLet: function(n) {
                            sources[n.name] = n.value;
                        },
                        // Bindings that are actually functions.
                        // E.g.: let f x = 37
                        visitFunction: function(n) {
                            sources[n.name] = n;
                        }
                    });
                }

                // Just eval it
                compiled = compile(joined, env, aliases, {nodejs: true, filename: ".", run: true});
            }

            if(compiled) {
                output = vm.runInNewContext(compiled.output, sandbox, 'eval');

                if(typeof output != 'undefined') {
                    colorLog(32, (typeof output == 'object' ? JSON.stringify(output) : output) + " : " + compiled.type);
                }
            }
        } catch(e) {
            colorLog(31, (e.stack || e.toString()));
            // Reset the block because something wasn't formatted properly.
            block = [];
        }
        repl.prompt();
    });
    repl.prompt();
};

var writeModule = function(env, exported, filename) {
    var fs = require('fs');

    var moduleOutput = _.map(exported, function(v, k) {
        if(v instanceof types.TagType) {
            return 'type ' + v.toString().replace(/#/g, '');
        }
        return k + ': ' + v.toString();
    }).join('\n') + '\n';
    fs.writeFile(filename, moduleOutput, 'utf8');
};

var importModule = function(name, env, opts) {
    var addTypesToEnv = function(moduleTypes) {
        _.each(moduleTypes, function(v, k) {
            var dataType = [new types.TagNameType(k)];
            _.each(function() {
                dataType.push(new types.Variable());
            });
            env[k] = new types.TagType(dataType);
        });
    };

    var moduleTypes;
    if(opts.nodejs) {
        // Need to convert to absolute paths for the CLI
        if(opts.run) {
            var path = require("path");
            name = path.resolve(path.dirname(opts.filename), name);
        }

        moduleTypes = loadModule(name, opts);
        addTypesToEnv(moduleTypes.types);
        var variable = name.substr(name.lastIndexOf("/") + 1);
        env[variable] = new types.Variable();
        var props = {};
        _.each(moduleTypes.env, function(v, k) {
            props[k] = nodeToType(v, env, {});
        });
        env[variable] = new types.ObjectType(props);

        console.log("Using sync CommonJS module:", name);

        return variable + " = require(" + JSON.stringify(name) + ")";
    } else {
        moduleTypes = loadModule(name, opts);
        addTypesToEnv(moduleTypes.types);
        _.each(moduleTypes.env, function(v, k) {
            env[k] = nodeToType(v, env, {});
        });

        if(console) console.log("Using browser module:", name);

        return "";
    }
};

var runRoy = function(argv, opts) {
    var fs = require('fs');
    var path = require('path');
    var vm = require('vm');

    var extensions = /\.l?roy$/;
    var literateExtension = /\.lroy$/;

    var exported;
    var env = {};
    var aliases = {};
    var sandbox = getSandbox();

    if(opts.run) {
        // Include the standard library
        if(opts.includePrelude) {
            argv.unshift(path.dirname(__dirname) + '/lib/prelude.roy');
        }
    } else {
        var modules = [];
        if(!argv.length || argv[0] != 'lib/prelude.roy') {
            modules.push(path.dirname(__dirname) + '/lib/prelude');
        }
        _.each(modules, function(module) {
            var moduleTypes = loadModule(module, {filename: '.'});
            _.each(moduleTypes.env, function(v, k) {
                env[k] = new types.Variable();
                env[k] = nodeToType(v, env, aliases);
            });
        });
    }

    _.each(argv, function(filename) {
        // Read the file content.
        var source = getFileContents(filename);

        if(filename.match(literateExtension)) {
            // Strip out the Markdown.
            source = source.match(/^ {4,}.+$/mg).join('\n').replace(/^ {4}/gm, '');
        } else {
            console.assert(filename.match(extensions), 'Filename must end with ".roy" or ".lroy"');
        }

        exported = {};
        var outputPath = filename.replace(extensions, '.js');
        var SourceMapGenerator = require('source-map').SourceMapGenerator;
        var sourceMap = new SourceMapGenerator({file: path.basename(outputPath)});

        var compiled = compile(source, env, aliases, {
            nodejs: opts.nodejs,
            filename: filename,
            run: opts.run,
            exported: exported,
            sourceMap: sourceMap
        });
        if(opts.run) {
            // Execute the JavaScript output.
            var output = vm.runInNewContext(compiled.output, sandbox, 'eval');
        } else {
            // Write the JavaScript output.
            fs.writeFile(outputPath, compiled.output + '//@ sourceMappingURL=' + path.basename(outputPath) + '.map\n', 'utf8');
            fs.writeFile(outputPath + '.map', sourceMap.toString(), 'utf8');
            writeModule(env, exported, filename.replace(extensions, '.roym'));
        }
    });
};

var processFlags = function(argv, opts) {
    if(argv.length === 0) {
        nodeRepl(opts);
        return;
    }

    switch(argv[0]) {
    case "-v":
    case "--version":
        console.log("Roy: " + opts.info.description);
        console.log(opts.info.version);
        process.exit();
         break;
    case "-h":
    case "--help":
        console.log("Roy: " + opts.info.description + "\n");
        console.log("-b --browser   : wrap the output in a top level function");
        console.log("   --strict    : include \"use strict\";");
        console.log("-h --help      : show this help");
        console.log("-p             : run without prelude (standard library)");
        console.log("-r [file]      : run Roy-code without JavaScript output");
        console.log("-s --stdio     : read script from stdin and output to stdout");
        console.log("-v --version   : show current version");
        return;
    case "-s":
    case "--stdio":
        var source = '';
        process.stdin.resume();
        process.stdin.setEncoding('utf8');
        process.stdin.on('data', function(data) {
            source += data;
        });
        process.stdin.on('end', function() {
            console.log(compile(source, null, null, opts).output);
        });
        return;
    case "-p":
        opts.includePrelude = false;
        /* falls through */
    case "-r":
        opts.run = true;
        argv.shift();
        break;
    case "-b":
    case "--browser":
        opts.nodejs = false;
        argv.shift();
        break;
    case "--strict":
        opts.strict = true;
        argv.shift();
        break;
    case "-c":
    case "--color":
        // The default now
        nodeRepl(opts);
        return;
    default:
        runRoy(argv, opts);
        return;
    }

    processFlags(argv, opts);
};

var processMeta = function(commands, env, aliases, sources) {
    var compiled,
        prettyPrint = require('./prettyprint').prettyPrint,
        source;

    switch(commands[0]) {
    case ":q":
        // Exit
        process.exit();
        break;
    case ":l":
        // Load
        source = getFileContents(commands[1]);
        return compile(source, env, aliases, {nodejs: true, filename: ".", run: true});
    case ":t":
        if(commands[1] in env) {
            console.log(env[commands[1]].toString());
        } else {
            colorLog(33, commands[1], "is not defined.");
        }
        break;
    case ":s":
        // Source
        if(sources[commands[1]]) {
            colorLog(33, commands[1], "=", prettyPrint(sources[commands[1]]));
        } else {
            if(commands[1]){
                colorLog(33, commands[1], "is not defined.");
            }else{
                console.log("Usage :s command ");
                console.log(":s [identifier] :: show original code about identifier.");
            }
        }
        break;
    case ":?":
        // Help
        colorLog(32, "Commands available from the prompt");
        console.log(":l -- load and run an external file");
        console.log(":q -- exit REPL");
        console.log(":s -- show original code about identifier");
        console.log(":t -- show the type of the identifier");
        console.log(":? -- show help");
        break;
    default:
        colorLog(31, "Invalid command");
        console.log(":? for help");
        break;
    }
};

var main = function() {
    var argv = process.argv.slice(2);

    // Roy package information
    var fs = require('fs');
    var path = require('path');

    // Meta-commands configuration
    var opts = {
        info: JSON.parse(fs.readFileSync(path.dirname(__dirname) + '/package.json', 'utf8')),
        nodejs: true,
        run: false,
        includePrelude: true,
        strict: false
    };

    processFlags(argv, opts);
};
module.exports = main;

if(module && !module.parent) {
    main();
}
<|endoftext|>


<commit_before>/**
 * Copyright 2015 CANAL+ Group
 *
 * 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.
 */

// XML-Schema
// <http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd>

const assert = require("canal-js-utils/assert");
const find = require("lodash/collection/find");
const defaults = require("lodash/object/defaults");

const iso8601Duration = /^P(([\d.]*)Y)?(([\d.]*)M)?(([\d.]*)D)?T?(([\d.]*)H)?(([\d.]*)M)?(([\d.]*)S)?/;
const rangeRe = /([0-9]+)-([0-9]+)/;
const frameRateRe = /([0-9]+)(\/([0-9]+))?/;

// TODO(pierre): support more than juste timeline index type
function calcLastRef(index) {
  const { ts, r, d } = index.timeline[index.timeline.length - 1];
  return ((ts + (r+1)*d) / index.timescale);
}

function feedAttributes(node, base) {
  const attrs = attributes[node.nodeName];

  assert(attrs, "parser: no attributes for " + node.nodeName);

  const obj = base || {};
  for (let i = 0; i < attrs.length; i++) {
    const { k, fn, n, def } = attrs[i];
    if (node.hasAttribute(k)) {
      obj[n || k] = fn(node.getAttribute(k));
    } else if (def != null) {
      obj[n || k] = def;
    }
  }
  return obj;
}

function parseString(str) {
  return str;
}

function parseBoolean(str) {
  return str == "true";
}

function parseIntOrBoolean(str) {
  if (str == "true") {
    return true;
  }
  if (str == "false") {
    return false;
  }
  return parseInt(str);
}

function parseDateTime(str) {
  return new Date(Date.parse(str));
}

function parseDuration(date) {
  if (!date) {
    return 0;
  }

  const match = iso8601Duration.exec(date);
  assert(match, `parser: ${date} is not a valid ISO8601 duration`);

  return (
    parseFloat(match[2]  || 0) * 365 * 24 * 60 * 60 +
    parseFloat(match[4]  || 0) * 30 * 24 * 60 * 60 + // not precise +
    parseFloat(match[6]  || 0) * 24 * 60 * 60 +
    parseFloat(match[8]  || 0) * 60 * 60 +
    parseFloat(match[10] || 0) * 60 +
    parseFloat(match[12] || 0)
  );
}

function parseFrameRate(str) {
  const match = frameRateRe.exec(str);
  if (!match) {
    return -1;
  }

  const nom = parseInt(match[1]) || 0;
  const den = parseInt(match[2]) || 0;
  return den > 0
    ? nom / den
    : nom;
}

function parseRatio(str) {
  return str;
}

function parseByteRange(str) {
  const match = rangeRe.exec(str);
  if (!match) {
    return null;
  } else {
    return [+match[1], +match[2]];
  }
}

const RepresentationBaseType = [
  { k: "profiles",          fn: parseString },
  { k: "width",             fn: parseInt },
  { k: "height",            fn: parseInt },
  { k: "frameRate",         fn: parseFrameRate },
  { k: "audioSamplingRate", fn: parseString },
  { k: "mimeType",          fn: parseString },
  { k: "segmentProfiles",   fn: parseString },
  { k: "codecs",            fn: parseString },
  { k: "maximumSAPPeriod",  fn: parseFloat },
  { k: "maxPlayoutRate",    fn: parseFloat },
  { k: "codingDependency",  fn: parseBoolean },
];

const SegmentBaseType = [
  { k: "timescale",                fn: parseInt },
  { k: "presentationTimeOffset",   fn: parseFloat, def: 0 },
  { k: "indexRange",               fn: parseByteRange },
  { k: "indexRangeExact",          fn: parseBoolean },
  { k: "availabilityTimeOffset",   fn: parseFloat },
  { k: "availabilityTimeComplete", fn: parseBoolean },
];

const MultipleSegmentBaseType = SegmentBaseType.concat([
  { k: "duration",    fn: parseInt },
  { k: "startNumber", fn: parseInt },
]);

const attributes = {
  "ContentProtection": [
    { k: "schemeIdUri", fn: parseString },
    { k: "value", fn: parseString },
  ],

  "SegmentURL": [
    { k: "media",      fn: parseString },
    { k: "mediaRange", fn: parseByteRange },
    { k: "index",      fn: parseString },
    { k: "indexRange", fn: parseByteRange },
  ],

  "S": [
    { k: "t", fn: parseInt, n: "ts"},
    { k: "d", fn: parseInt },
    { k: "r", fn: parseInt },
  ],

  "SegmentTimeline": [],
  "SegmentBase": SegmentBaseType,
  "SegmentTemplate": MultipleSegmentBaseType.concat([
    { k: "initialization",     fn: parseInitializationAttribute },
    { k: "index",              fn: parseString },
    { k: "media",              fn: parseString },
    { k: "bitstreamSwitching", fn: parseString },
  ]),
  "SegmentList": MultipleSegmentBaseType,

  "ContentComponent": [
    { k: "id",          fn: parseString },
    { k: "lang",        fn: parseString },
    { k: "contentType", fn: parseString },
    { k: "par",         fn: parseRatio },
  ],

  "Representation": RepresentationBaseType.concat([
    { k: "id",             fn: parseString },
    { k: "bandwidth",      fn: parseInt, n: "bitrate" },
    { k: "qualityRanking", fn: parseInt },
  ]),

  "AdaptationSet": RepresentationBaseType.concat([
    { k: "id",                  fn: parseString },
    { k: "group",               fn: parseInt },
    { k: "lang",                fn: parseString },
    { k: "contentType",         fn: parseString },
    { k: "par",                 fn: parseRatio },
    { k: "minBandwidth",        fn: parseInt, n: "minBitrate" },
    { k: "maxBandwidth",        fn: parseInt, n: "maxBitrate" },
    { k: "minWidth",            fn: parseInt },
    { k: "maxWidth",            fn: parseInt },
    { k: "minHeight",           fn: parseInt },
    { k: "maxHeight",           fn: parseInt },
    { k: "minFrameRate",        fn: parseFrameRate },
    { k: "maxFrameRate",        fn: parseFrameRate },
    { k: "segmentAlignment",    fn: parseIntOrBoolean },
    { k: "subsegmentAlignment", fn: parseIntOrBoolean },
    { k: "bitstreamSwitching",  fn: parseBoolean },
  ]),

  "Period": [
    { k: "id",                 fn: parseString },
    { k: "start",              fn: parseDuration },
    { k: "duration",           fn: parseDuration },
    { k: "bitstreamSwitching", fn: parseBoolean },
  ],

  "MPD": [
    { k: "id",                         fn: parseString },
    { k: "profiles",                   fn: parseString },
    { k: "type",                       fn: parseString },
    { k: "availabilityStartTime",      fn: parseDateTime },
    { k: "availabilityEndTime",        fn: parseDateTime },
    { k: "publishTime",                fn: parseDateTime },
    { k: "mediaPresentationDuration",  fn: parseDuration, n: "duration" },
    { k: "minimumUpdatePeriod",        fn: parseDuration },
    { k: "minBufferTime",              fn: parseDuration },
    { k: "timeShiftBufferDepth",       fn: parseDuration },
    { k: "suggestedPresentationDelay", fn: parseDuration },
    { k: "maxSegmentDuration",         fn: parseDuration },
    { k: "maxSubsegmentDuration",      fn: parseDuration },
  ],
};

function reduceChildren(root, fn, init) {
  let node = root.firstElementChild, r = init;
  while (node) {
    r = fn(r, node.nodeName, node);
    node = node.nextElementSibling;
  }
  return r;
}

function parseContentProtection(root, contentProtectionParser) {
  return contentProtectionParser(feedAttributes(root), root);
}

function parseSegmentBase(root) {
  const index = reduceChildren(root, (res, name, node) => {
    if (name == "Initialization") {
      res.initialization = parseInitialization(node);
    }
    return res;
  }, feedAttributes(root));
  if (root.nodeName == "SegmentBase") {
    index.indexType = "base";
    index.timeline = [];
  }
  return index;
}

function parseMultipleSegmentBase(root) {
  return reduceChildren(root, (res, name, node) => {
    if (name == "SegmentTimeline") {
      res.indexType = "timeline";
      res.timeline = parseSegmentTimeline(node);
    }
    return res;
  }, parseSegmentBase(root));
}

function parseSegmentTimeline(root) {
  return reduceChildren(root, (arr, name, node) => {
    const len = arr.length;
    const seg = feedAttributes(node);
    if (seg.ts == null) {
      const prev = (len > 0) && arr[len - 1];
      seg.ts = prev
        ? prev.ts + prev.d * (prev.r + 1)
        : 0;
    }
    if (seg.r == null) {
      seg.r = 0;
    }
    arr.push(seg);
    return arr;
  }, []);
}

function parseInitializationAttribute(attrValue) {
  return { media: attrValue, range: undefined };
}

function parseInitialization(root) {
  let range, media;

  if (root.hasAttribute("range")) {
    range = parseByteRange(root.getAttribute("range"));
  }

  if (root.hasAttribute("sourceURL")) {
    media = root.getAttribute("sourceURL");
  }

  return { range, media };
}

function parseSegmentTemplate(root) {
  const base = parseMultipleSegmentBase(root);
  if (!base.indexType) {
    base.indexType = "template";
  }
  return base;
}

function parseSegmentList(root) {
  const base = parseMultipleSegmentBase(root);
  base.list = [];
  base.indexType = "list";
  return reduceChildren(root, (res, name, node) => {
    if (name == "SegmentURL") {
      res.list.push(feedAttributes(node));
    }
    return res;
  }, base);
}

function parseRepresentation(root) {
  const rep = reduceChildren(root, (res, name, node) => {
    switch(name) {
     // case "FramePacking": break;
     // case "AudioChannelConfiguration": break;
     // case "ContentProtection": res.contentProtection = parseContentProtection(node); break;
     // case "EssentialProperty": break;
     // case "SupplementalProperty": break;
     // case "InbandEventStream": break;
    case "BaseURL": res.baseURL = node.textContent; break;
    // case "SubRepresentation": break;
    case "SegmentBase": res.index = parseSegmentBase(node); break;
    case "SegmentList": res.index = parseSegmentList(node); break;
    case "SegmentTemplate": res.index = parseSegmentTemplate(node); break;
    }
    return res;
  }, {});

  return feedAttributes(root, rep);
}

function parseContentComponent(root) {
  return feedAttributes(root);
}

function parseAdaptationSet(root, contentProtectionParser) {
  const res = reduceChildren(root, (res, name, node) => {
    switch(name) {
    // case "Accessibility": break;
    // case "Role": break;
    // case "Rating": break;
    // case "Viewpoint": break;
    case "ContentProtection": res.contentProtection = parseContentProtection(node, contentProtectionParser); break;
    case "ContentComponent": res.contentComponent = parseContentComponent(node); break;
    case "BaseURL": res.baseURL = node.textContent; break;
    case "SegmentBase": res.index = parseSegmentBase(node); break;
    case "SegmentList": res.index = parseSegmentList(node); break;
    case "SegmentTemplate": res.index = parseSegmentTemplate(node); break;
    case "Representation":
      const rep = parseRepresentation(node);
      if (rep.id == null) {
        rep.id = res.representations.length;
      }
      res.representations.push(rep); break;
    }
    return res;
  }, { representations: [] });

  return feedAttributes(root, res);
}

function parsePeriod(root, contentProtectionParser) {
  const attrs = feedAttributes(root, reduceChildren(root, (res, name, node) => {
    switch(name) {
    case "BaseURL": res.baseURL = node.textContent; break;
    case "AdaptationSet":
      const ada = parseAdaptationSet(node, contentProtectionParser);
      if (ada.id == null) {
        ada.id = res.adaptations.length;
      }
      res.adaptations.push(ada); break;
    }
    return res;
  }, { adaptations: [] }));

  if (attrs.baseURL) {
    attrs.adaptations.forEach(
      (adaptation) => defaults(adaptation, { baseURL: attrs.baseURL }));
  }

  return attrs;
}

function parseFromDocument(document, contentProtectionParser) {
  const root = document.documentElement;
  assert.equal(root.nodeName, "MPD", "parser: document root should be MPD");

  let manifest = reduceChildren(root, (res, name, node) => {
    switch(name) {
    case "BaseURL": res.baseURL = node.textContent; break;
    case "Location": res.locations.push(node.textContent); break;
    case "Period": res.periods.push(parsePeriod(node, contentProtectionParser)); break;
    }
    return res;
  }, {
    transportType: "dash",
    periods: [],
    locations: [],
  });

  manifest = feedAttributes(root, manifest);

  if (/isoff-live/.test(manifest.profiles)) {
    const adaptations = manifest.periods[0].adaptations;
    const videoAdaptation = find(adaptations, (a) => a.mimeType == "video/mp4");

    const videoIndex = videoAdaptation && videoAdaptation.index;

    if (__DEV__) {
      assert(videoIndex && (videoIndex.indexType == "timeline" || videoIndex.indexType == "template"));
      assert(manifest.availabilityStartTime);
    }

    let lastRef;
    if (videoIndex.timeline) {
      lastRef = calcLastRef(videoIndex);
    }
    else {
      lastRef = Date.now() / 1000 - 60;
    }

    manifest.availabilityStartTime = manifest.availabilityStartTime.getTime() / 1000;
    manifest.presentationLiveGap = Date.now() / 1000 - (lastRef + manifest.availabilityStartTime);
  }

  return manifest;
}

function parseFromString(manifest, contentProtectionParser) {
  return parseFromDocument(new DOMParser().parseFromString(manifest, "application/xml"), contentProtectionParser);
}

function parser(manifest, contentProtectionParser) {
  if (typeof manifest == "string") {
    return parseFromString(manifest, contentProtectionParser);
  } else {
    return parseFromDocument(manifest, contentProtectionParser);
  }
}

parser.parseFromString   = parseFromString;
parser.parseFromDocument = parseFromDocument;

module.exports = parser;
<commit_msg>Add default to mpd type attribute, fix live checking<commit_after>/**
 * Copyright 2015 CANAL+ Group
 *
 * 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.
 */

// XML-Schema
// <http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd>

const assert = require("canal-js-utils/assert");
const find = require("lodash/collection/find");
const defaults = require("lodash/object/defaults");

const iso8601Duration = /^P(([\d.]*)Y)?(([\d.]*)M)?(([\d.]*)D)?T?(([\d.]*)H)?(([\d.]*)M)?(([\d.]*)S)?/;
const rangeRe = /([0-9]+)-([0-9]+)/;
const frameRateRe = /([0-9]+)(\/([0-9]+))?/;

// TODO(pierre): support more than juste timeline index type
function calcLastRef(index) {
  const { ts, r, d } = index.timeline[index.timeline.length - 1];
  return ((ts + (r+1)*d) / index.timescale);
}

function feedAttributes(node, base) {
  const attrs = attributes[node.nodeName];

  assert(attrs, "parser: no attributes for " + node.nodeName);

  const obj = base || {};
  for (let i = 0; i < attrs.length; i++) {
    const { k, fn, n, def } = attrs[i];
    if (node.hasAttribute(k)) {
      obj[n || k] = fn(node.getAttribute(k));
    } else if (def != null) {
      obj[n || k] = def;
    }
  }
  return obj;
}

function parseString(str) {
  return str;
}

function parseBoolean(str) {
  return str == "true";
}

function parseIntOrBoolean(str) {
  if (str == "true") {
    return true;
  }
  if (str == "false") {
    return false;
  }
  return parseInt(str);
}

function parseDateTime(str) {
  return new Date(Date.parse(str));
}

function parseDuration(date) {
  if (!date) {
    return 0;
  }

  const match = iso8601Duration.exec(date);
  assert(match, `parser: ${date} is not a valid ISO8601 duration`);

  return (
    parseFloat(match[2]  || 0) * 365 * 24 * 60 * 60 +
    parseFloat(match[4]  || 0) * 30 * 24 * 60 * 60 + // not precise +
    parseFloat(match[6]  || 0) * 24 * 60 * 60 +
    parseFloat(match[8]  || 0) * 60 * 60 +
    parseFloat(match[10] || 0) * 60 +
    parseFloat(match[12] || 0)
  );
}

function parseFrameRate(str) {
  const match = frameRateRe.exec(str);
  if (!match) {
    return -1;
  }

  const nom = parseInt(match[1]) || 0;
  const den = parseInt(match[2]) || 0;
  return den > 0
    ? nom / den
    : nom;
}

function parseRatio(str) {
  return str;
}

function parseByteRange(str) {
  const match = rangeRe.exec(str);
  if (!match) {
    return null;
  } else {
    return [+match[1], +match[2]];
  }
}

const RepresentationBaseType = [
  { k: "profiles",          fn: parseString },
  { k: "width",             fn: parseInt },
  { k: "height",            fn: parseInt },
  { k: "frameRate",         fn: parseFrameRate },
  { k: "audioSamplingRate", fn: parseString },
  { k: "mimeType",          fn: parseString },
  { k: "segmentProfiles",   fn: parseString },
  { k: "codecs",            fn: parseString },
  { k: "maximumSAPPeriod",  fn: parseFloat },
  { k: "maxPlayoutRate",    fn: parseFloat },
  { k: "codingDependency",  fn: parseBoolean },
];

const SegmentBaseType = [
  { k: "timescale",                fn: parseInt },
  { k: "presentationTimeOffset",   fn: parseFloat, def: 0 },
  { k: "indexRange",               fn: parseByteRange },
  { k: "indexRangeExact",          fn: parseBoolean },
  { k: "availabilityTimeOffset",   fn: parseFloat },
  { k: "availabilityTimeComplete", fn: parseBoolean },
];

const MultipleSegmentBaseType = SegmentBaseType.concat([
  { k: "duration",    fn: parseInt },
  { k: "startNumber", fn: parseInt },
]);

const attributes = {
  "ContentProtection": [
    { k: "schemeIdUri", fn: parseString },
    { k: "value", fn: parseString },
  ],

  "SegmentURL": [
    { k: "media",      fn: parseString },
    { k: "mediaRange", fn: parseByteRange },
    { k: "index",      fn: parseString },
    { k: "indexRange", fn: parseByteRange },
  ],

  "S": [
    { k: "t", fn: parseInt, n: "ts"},
    { k: "d", fn: parseInt },
    { k: "r", fn: parseInt },
  ],

  "SegmentTimeline": [],
  "SegmentBase": SegmentBaseType,
  "SegmentTemplate": MultipleSegmentBaseType.concat([
    { k: "initialization",     fn: parseInitializationAttribute },
    { k: "index",              fn: parseString },
    { k: "media",              fn: parseString },
    { k: "bitstreamSwitching", fn: parseString },
  ]),
  "SegmentList": MultipleSegmentBaseType,

  "ContentComponent": [
    { k: "id",          fn: parseString },
    { k: "lang",        fn: parseString },
    { k: "contentType", fn: parseString },
    { k: "par",         fn: parseRatio },
  ],

  "Representation": RepresentationBaseType.concat([
    { k: "id",             fn: parseString },
    { k: "bandwidth",      fn: parseInt, n: "bitrate" },
    { k: "qualityRanking", fn: parseInt },
  ]),

  "AdaptationSet": RepresentationBaseType.concat([
    { k: "id",                  fn: parseString },
    { k: "group",               fn: parseInt },
    { k: "lang",                fn: parseString },
    { k: "contentType",         fn: parseString },
    { k: "par",                 fn: parseRatio },
    { k: "minBandwidth",        fn: parseInt, n: "minBitrate" },
    { k: "maxBandwidth",        fn: parseInt, n: "maxBitrate" },
    { k: "minWidth",            fn: parseInt },
    { k: "maxWidth",            fn: parseInt },
    { k: "minHeight",           fn: parseInt },
    { k: "maxHeight",           fn: parseInt },
    { k: "minFrameRate",        fn: parseFrameRate },
    { k: "maxFrameRate",        fn: parseFrameRate },
    { k: "segmentAlignment",    fn: parseIntOrBoolean },
    { k: "subsegmentAlignment", fn: parseIntOrBoolean },
    { k: "bitstreamSwitching",  fn: parseBoolean },
  ]),

  "Period": [
    { k: "id",                 fn: parseString },
    { k: "start",              fn: parseDuration },
    { k: "duration",           fn: parseDuration },
    { k: "bitstreamSwitching", fn: parseBoolean },
  ],

  "MPD": [
    { k: "id",                         fn: parseString },
    { k: "profiles",                   fn: parseString },
    { k: "type",                       fn: parseString, def: "static" },
    { k: "availabilityStartTime",      fn: parseDateTime },
    { k: "availabilityEndTime",        fn: parseDateTime },
    { k: "publishTime",                fn: parseDateTime },
    { k: "mediaPresentationDuration",  fn: parseDuration, n: "duration" },
    { k: "minimumUpdatePeriod",        fn: parseDuration },
    { k: "minBufferTime",              fn: parseDuration },
    { k: "timeShiftBufferDepth",       fn: parseDuration },
    { k: "suggestedPresentationDelay", fn: parseDuration },
    { k: "maxSegmentDuration",         fn: parseDuration },
    { k: "maxSubsegmentDuration",      fn: parseDuration },
  ],
};

function reduceChildren(root, fn, init) {
  let node = root.firstElementChild, r = init;
  while (node) {
    r = fn(r, node.nodeName, node);
    node = node.nextElementSibling;
  }
  return r;
}

function parseContentProtection(root, contentProtectionParser) {
  return contentProtectionParser(feedAttributes(root), root);
}

function parseSegmentBase(root) {
  const index = reduceChildren(root, (res, name, node) => {
    if (name == "Initialization") {
      res.initialization = parseInitialization(node);
    }
    return res;
  }, feedAttributes(root));
  if (root.nodeName == "SegmentBase") {
    index.indexType = "base";
    index.timeline = [];
  }
  return index;
}

function parseMultipleSegmentBase(root) {
  return reduceChildren(root, (res, name, node) => {
    if (name == "SegmentTimeline") {
      res.indexType = "timeline";
      res.timeline = parseSegmentTimeline(node);
    }
    return res;
  }, parseSegmentBase(root));
}

function parseSegmentTimeline(root) {
  return reduceChildren(root, (arr, name, node) => {
    const len = arr.length;
    const seg = feedAttributes(node);
    if (seg.ts == null) {
      const prev = (len > 0) && arr[len - 1];
      seg.ts = prev
        ? prev.ts + prev.d * (prev.r + 1)
        : 0;
    }
    if (seg.r == null) {
      seg.r = 0;
    }
    arr.push(seg);
    return arr;
  }, []);
}

function parseInitializationAttribute(attrValue) {
  return { media: attrValue, range: undefined };
}

function parseInitialization(root) {
  let range, media;

  if (root.hasAttribute("range")) {
    range = parseByteRange(root.getAttribute("range"));
  }

  if (root.hasAttribute("sourceURL")) {
    media = root.getAttribute("sourceURL");
  }

  return { range, media };
}

function parseSegmentTemplate(root) {
  const base = parseMultipleSegmentBase(root);
  if (!base.indexType) {
    base.indexType = "template";
  }
  return base;
}

function parseSegmentList(root) {
  const base = parseMultipleSegmentBase(root);
  base.list = [];
  base.indexType = "list";
  return reduceChildren(root, (res, name, node) => {
    if (name == "SegmentURL") {
      res.list.push(feedAttributes(node));
    }
    return res;
  }, base);
}

function parseRepresentation(root) {
  const rep = reduceChildren(root, (res, name, node) => {
    switch(name) {
     // case "FramePacking": break;
     // case "AudioChannelConfiguration": break;
     // case "ContentProtection": res.contentProtection = parseContentProtection(node); break;
     // case "EssentialProperty": break;
     // case "SupplementalProperty": break;
     // case "InbandEventStream": break;
    case "BaseURL": res.baseURL = node.textContent; break;
    // case "SubRepresentation": break;
    case "SegmentBase": res.index = parseSegmentBase(node); break;
    case "SegmentList": res.index = parseSegmentList(node); break;
    case "SegmentTemplate": res.index = parseSegmentTemplate(node); break;
    }
    return res;
  }, {});

  return feedAttributes(root, rep);
}

function parseContentComponent(root) {
  return feedAttributes(root);
}

function parseAdaptationSet(root, contentProtectionParser) {
  const res = reduceChildren(root, (res, name, node) => {
    switch(name) {
    // case "Accessibility": break;
    // case "Role": break;
    // case "Rating": break;
    // case "Viewpoint": break;
    case "ContentProtection": res.contentProtection = parseContentProtection(node, contentProtectionParser); break;
    case "ContentComponent": res.contentComponent = parseContentComponent(node); break;
    case "BaseURL": res.baseURL = node.textContent; break;
    case "SegmentBase": res.index = parseSegmentBase(node); break;
    case "SegmentList": res.index = parseSegmentList(node); break;
    case "SegmentTemplate": res.index = parseSegmentTemplate(node); break;
    case "Representation":
      const rep = parseRepresentation(node);
      if (rep.id == null) {
        rep.id = res.representations.length;
      }
      res.representations.push(rep); break;
    }
    return res;
  }, { representations: [] });

  return feedAttributes(root, res);
}

function parsePeriod(root, contentProtectionParser) {
  const attrs = feedAttributes(root, reduceChildren(root, (res, name, node) => {
    switch(name) {
    case "BaseURL": res.baseURL = node.textContent; break;
    case "AdaptationSet":
      const ada = parseAdaptationSet(node, contentProtectionParser);
      if (ada.id == null) {
        ada.id = res.adaptations.length;
      }
      res.adaptations.push(ada); break;
    }
    return res;
  }, { adaptations: [] }));

  if (attrs.baseURL) {
    attrs.adaptations.forEach(
      (adaptation) => defaults(adaptation, { baseURL: attrs.baseURL }));
  }

  return attrs;
}

function parseFromDocument(document, contentProtectionParser) {
  const root = document.documentElement;
  assert.equal(root.nodeName, "MPD", "parser: document root should be MPD");

  let manifest = reduceChildren(root, (res, name, node) => {
    switch(name) {
    case "BaseURL": res.baseURL = node.textContent; break;
    case "Location": res.locations.push(node.textContent); break;
    case "Period": res.periods.push(parsePeriod(node, contentProtectionParser)); break;
    }
    return res;
  }, {
    transportType: "dash",
    periods: [],
    locations: [],
  });

  manifest = feedAttributes(root, manifest);

  if (/dynamic/.test(manifest.type)) {
    const adaptations = manifest.periods[0].adaptations;
    const videoAdaptation = find(adaptations, (a) => a.mimeType == "video/mp4");

    const videoIndex = videoAdaptation && videoAdaptation.index;

    if (__DEV__) {
      assert(videoIndex && (videoIndex.indexType == "timeline" || videoIndex.indexType == "template"));
      assert(manifest.availabilityStartTime);
    }

    let lastRef;
    if (videoIndex.timeline) {
      lastRef = calcLastRef(videoIndex);
    }
    else {
      lastRef = Date.now() / 1000 - 60;
    }

    manifest.availabilityStartTime = manifest.availabilityStartTime.getTime() / 1000;
    manifest.presentationLiveGap = Date.now() / 1000 - (lastRef + manifest.availabilityStartTime);
  }

  return manifest;
}

function parseFromString(manifest, contentProtectionParser) {
  return parseFromDocument(new DOMParser().parseFromString(manifest, "application/xml"), contentProtectionParser);
}

function parser(manifest, contentProtectionParser) {
  if (typeof manifest == "string") {
    return parseFromString(manifest, contentProtectionParser);
  } else {
    return parseFromDocument(manifest, contentProtectionParser);
  }
}

parser.parseFromString   = parseFromString;
parser.parseFromDocument = parseFromDocument;

module.exports = parser;
<|endoftext|>


<commit_before>angular.module('oauth.box', ['oauth.utils'])
  .factory('$ngCordovaBox', box);

function box($q, $http, $cordovaOauthUtility) {
  return { signin: oauthBox };

  /*
   * Sign into the Box service
   *
   * @param    string clientId
   * @param    string clientSecret
   * @param    string appState
   * @param    object options
   * @return   promise
   */
  function oauthBox(clientId, clientSecret, appState, options) {
    var deferred = $q.defer();
    if(window.cordova) {
      if($cordovaOauthUtility.isInAppBrowserInstalled()) {
        var redirect_uri = "http://localhost/callback";
        if(options !== undefined) {
          if(options.hasOwnProperty("redirect_uri")) {
              redirect_uri = options.redirect_uri;
          }
        }
        var browserRef = window.cordova.InAppBrowser.open('https://app.box.com/api/oauth2/authorize/?client_id=' + clientId + '&redirect_uri=' + redirect_uri + '&state=' + appState + '&response_type=code', '_blank', 'location=no,clearsessioncache=yes,clearcache=yes');
        browserRef.addEventListener('loadstart', function(event) {
          if((event.url).indexOf(redirect_uri) === 0) {
            requestToken = (event.url).split("code=")[1];
            $http.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
            $http({method: "post", url: "https://app.box.com/api/oauth2/token", data: "client_id=" + clientId + "&client_secret=" + clientSecret + "&redirect_uri=" + redirect_uri + "&grant_type=authorization_code" + "&code=" + requestToken })
              .success(function(data) {
                deferred.resolve(data);
              })
              .error(function(data, status) {
                deferred.reject("Problem authenticating");
              })
              .finally(function() {
                setTimeout(function() {
                    browserRef.close();
                }, 10);
              });
          }
        });
        browserRef.addEventListener('exit', function(event) {
          deferred.reject("The sign in flow was canceled");
        });
      } else {
        deferred.reject("Could not find InAppBrowser plugin");
      }
    } else {
      deferred.reject("Cannot authenticate via a web browser");
    }

    return deferred.promise;
  }
}

box.$inject = ['$q', '$http', '$cordovaOauthUtility'];
<commit_msg>IIFE box<commit_after>(function() {
  'use strict';

  angular.module('oauth.box', ['oauth.utils'])
    .factory('$ngCordovaBox', box);

  function box($q, $http, $cordovaOauthUtility) {
    return { signin: oauthBox };

    /*
     * Sign into the Box service
     *
     * @param    string clientId
     * @param    string clientSecret
     * @param    string appState
     * @param    object options
     * @return   promise
     */
    function oauthBox(clientId, clientSecret, appState, options) {
      var deferred = $q.defer();
      if(window.cordova) {
        if($cordovaOauthUtility.isInAppBrowserInstalled()) {
          var redirect_uri = "http://localhost/callback";
          if(options !== undefined) {
            if(options.hasOwnProperty("redirect_uri")) {
                redirect_uri = options.redirect_uri;
            }
          }
          var browserRef = window.cordova.InAppBrowser.open('https://app.box.com/api/oauth2/authorize/?client_id=' + clientId + '&redirect_uri=' + redirect_uri + '&state=' + appState + '&response_type=code', '_blank', 'location=no,clearsessioncache=yes,clearcache=yes');
          browserRef.addEventListener('loadstart', function(event) {
            if((event.url).indexOf(redirect_uri) === 0) {
              requestToken = (event.url).split("code=")[1];
              $http.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
              $http({method: "post", url: "https://app.box.com/api/oauth2/token", data: "client_id=" + clientId + "&client_secret=" + clientSecret + "&redirect_uri=" + redirect_uri + "&grant_type=authorization_code" + "&code=" + requestToken })
                .success(function(data) {
                  deferred.resolve(data);
                })
                .error(function(data, status) {
                  deferred.reject("Problem authenticating");
                })
                .finally(function() {
                  setTimeout(function() {
                      browserRef.close();
                  }, 10);
                });
            }
          });
          browserRef.addEventListener('exit', function(event) {
            deferred.reject("The sign in flow was canceled");
          });
        } else {
          deferred.reject("Could not find InAppBrowser plugin");
        }
      } else {
        deferred.reject("Cannot authenticate via a web browser");
      }

      return deferred.promise;
    }
  }

  box.$inject = ['$q', '$http', '$cordovaOauthUtility'];
})();
<|endoftext|>


<commit_before>import assign from 'lodash.assign';
import omit from 'lodash.omit';

export const styles = Object.freeze({
  pageNumber: 1,
  limitOffset: 2,
});

export const errors = Object.freeze({
  invalidStyle: 'Invalid pagination style',
  calculateOffsetNoLimit: 'Can\'t calculate offsets without a limit',
  calculatePageNoLimit: 'Can\'t calculate pages without a limit'
});

const getResponseBody = function(response) {
  return response.json();
};

const defaultOptions = {
  limitQueryParam: 'limit',
  offsetQueryParam: 'offset',
  pageQueryParam: 'page',
  style: styles.pageNumber
};

const defaultQueryParams = {
  limit: null,
  offset: 0,
  page: 1
};

export class Paginator {
  constructor(request, requestOptions = null, queryParams = {}, options = {}) {
    this._cache = Object.create(null);
    this._parsedResponse = null;
    this._request = request;
    this._requestOptions = assign({}, requestOptions);

    this._setOptions(options);
    this._setQueryParams(queryParams);
  }

  get page() {
    return this._page;
  }

  next() {
    const incrementPage = (pageCount) => {
      if (this._page < pageCount) {
        this._page++;
      }
    };

    return this.fetchPageCount()
      .then(incrementPage)
      .then(() => this.current());
  }

  prev() {
    if (this._page > 1) {
      this._page--;
    }

    return this.current();
  }

  first() {
    this._page = 1;

    return this.current();
  }

  last() {
    const setLastPage = (pageCount) => {
      this._page = pageCount;
    };

    return this.fetchPageCount()
      .then(setLastPage)
      .then(() => this.current());
  }

  current() {
    return this.fetchPage(this._page);
  }

  fetchPage(page) {
    if (this._hasResponse(page)) {
      const response = this._getResponse(page);

      return Promise.resolve(response.clone());
    }

    const request = this._sendRequest(page);
    const cacheResponse = (response) => {
      this._cacheResponse(page, response);
    };
    const makeResponseClone = function() {
      return request.then((res) => res.clone());
    };
    const parseResponse = this._parseResponse.bind(this);
    const setLimitIfMissing = (parsed) => {
      if (!this._limit) {
        this._limit = parsed.limit;
      }
    };

    return request
      .then(cacheResponse)
      .then(makeResponseClone)
      .then(getResponseBody)
      .then(parseResponse)
      .then(setLimitIfMissing)
      .then(makeResponseClone);
  }

  fetchPageCount() {
    return this._fetchParsedResponse()
      .then((parsed) => parsed.pageCount);
  }

  clone(page = 1) {
    const req = this._request;
    const reqOpts = this._requestOptions;
    const queryParams = this._makeQueryParams(page);
    const opts = this._options;

    return new Paginator(req, reqOpts, queryParams, opts);
  }

  _sendRequest(page) {
    const requestOptions = assign({}, this._requestOptions);
    const queryParams = this._makeQueryParams(page);

    return this._request(requestOptions, queryParams);
  }

  _makeQueryParams(page) {
    const options = this._options;
    const queryParams = assign({}, this._extraQueryParams);

    switch (options.style) {
      case styles.pageNumber:
        queryParams[options.pageQueryParam] = page;
        break;
      case styles.limitOffset:
        queryParams[options.limitQueryParam] = this._limit;
        queryParams[options.offsetQueryParam] = this._calculateOffset(page);
        break;
      default:
        throw new Error(errors.invalidStyle);
    }

    return queryParams;
  }

  _getResponse(page) {
    return this._cache[page];
  }

  _cacheResponse(page, response) {
    this._cache[page] = response;
  }

  _hasResponse(page) {
    return !!this._cache[page];
  }

  _fetchParsedResponse(page = 1) {
    if (this._parsedResponse) {
      return this._parsedResponse;
    }

    const parseResponse = this._parseResponse.bind(this);

    this._parsedResponse = this.fetchPage(page)
      .then(getResponseBody)
      .then(parseResponse);

    return this._parsedResponse;
  }

  _parseResponse(responseBody) {
    const {count, results} = responseBody;
    const limit = this._inferLimit(responseBody);
    let pageCount = 1;

    if (count && limit) {
      pageCount = Math.ceil(count / limit);
    }

    return {
      count,
      limit,
      pageCount,
      results
    };
  }

  _setOptions(options) {
    this._options = assign({}, defaultOptions, options);
  }

  _setQueryParams(raw) {
    const parsed = this._parseQueryParams(raw);

    this._limit = parsed.limit;
    this._extraQueryParams = parsed.extra;

    switch (this._options.style) {
      case styles.pageNumber:
        this._page = parsed.page;
        break;
      case styles.limitOffset:
        this._page = this._calculatePage(parsed.offset);
        break;
      default:
        throw new Error(errors.invalidStyle);
    }
  }

  _filterQueryParams(queryParams) {
    const options = this._options;
    const paginatorProperties = [
      options.limitQueryParam,
      options.offsetQueryParam,
      options.pageQueryParam
    ];

    return omit(queryParams, paginatorProperties);
  }

  _parseQueryParams(raw) {
    const {
      limitQueryParam,
      offsetQueryParam,
      pageQueryParam
    } = this._options;

    const parsed = assign({}, defaultQueryParams);

    if (raw.hasOwnProperty(limitQueryParam)) {
      parsed.limit = raw[limitQueryParam];
    }
    if (raw.hasOwnProperty(offsetQueryParam)) {
      parsed.offset = raw[offsetQueryParam];
    }
    if (raw.hasOwnProperty(pageQueryParam)) {
      parsed.page = raw[pageQueryParam];
    }

    parsed.extra = this._filterQueryParams(raw);

    return parsed;
  }

  _calculateOffset(page) {
    const limit = this._limit;

    if (page === 1) {
      return 0;
    }
    if (!limit) {
      throw new Error(errors.calculateOffsetNoLimit);
    }

    return limit * page - limit;
  }

  _calculatePage(offset) {
    const limit = this._limit;

    if (offset === 0) {
      return 1;
    }
    if (!limit) {
      throw new Error(errors.calculatePageNoLimit);
    }

    return (offset + limit) / limit;
  }

  _inferLimit(responseBody) {
    const count = responseBody.count;
    const resultCount = responseBody.results.length;

    if (count > resultCount) {
      return resultCount;
    }

    return null;
  }
}

Paginator.styles = styles;
Paginator.errors = errors;

export default Paginator;
<commit_msg>Refactor the paginator to cache promises instead of responses<commit_after>import assign from 'lodash.assign';
import omit from 'lodash.omit';

export const styles = Object.freeze({
  pageNumber: 1,
  limitOffset: 2,
});

export const errors = Object.freeze({
  invalidStyle: 'Invalid pagination style',
  calculateOffsetNoLimit: 'Can\'t calculate offsets without a limit',
  calculatePageNoLimit: 'Can\'t calculate pages without a limit'
});

const getResponseBody = function(response) {
  return response.json();
};

const defaultOptions = {
  limitQueryParam: 'limit',
  offsetQueryParam: 'offset',
  pageQueryParam: 'page',
  style: styles.pageNumber
};

const defaultQueryParams = {
  limit: null,
  offset: 0,
  page: 1
};

export class Paginator {
  constructor(request, requestOptions = null, queryParams = {}, options = {}) {
    this._cache = Object.create(null);
    this._parsedResponse = null;
    this._request = request;
    this._requestOptions = assign({}, requestOptions);

    this._setOptions(options);
    this._setQueryParams(queryParams);
  }

  get page() {
    return this._page;
  }

  next() {
    const incrementPage = (pageCount) => {
      if (this._page < pageCount) {
        this._page++;
      }
    };

    return this.fetchPageCount()
      .then(incrementPage)
      .then(() => this.current());
  }

  prev() {
    if (this._page > 1) {
      this._page--;
    }

    return this.current();
  }

  first() {
    this._page = 1;

    return this.current();
  }

  last() {
    const setLastPage = (pageCount) => {
      this._page = pageCount;
    };

    return this.fetchPageCount()
      .then(setLastPage)
      .then(() => this.current());
  }

  current() {
    return this.fetchPage(this._page);
  }

  fetchPage(page) {
    let request;

    const makeResponseClone = function() {
      return request.then((res) => res.clone());
    };

    if (this._hasRequest(page)) {
      request = this._getRequest(page);

      return makeResponseClone();
    }

    request = this._sendRequest(page);

    this._cacheRequest(page, request);

    const parseResponseBody = this._parseResponseBody.bind(this);
    const setLimitIfMissing = (parsed) => {
      if (!this._limit) {
        this._limit = parsed.limit;
      }
    };

    return makeResponseClone()
      .then(getResponseBody)
      .then(parseResponseBody)
      .then(setLimitIfMissing)
      .then(makeResponseClone);
  }

  fetchPageCount() {
    return this._fetchParsedResponse()
      .then((parsed) => parsed.pageCount);
  }

  clone(page = 1) {
    const req = this._request;
    const reqOpts = this._requestOptions;
    const queryParams = this._makeQueryParams(page);
    const opts = this._options;

    return new Paginator(req, reqOpts, queryParams, opts);
  }

  _sendRequest(page) {
    const requestOptions = assign({}, this._requestOptions);
    const queryParams = this._makeQueryParams(page);

    return this._request(requestOptions, queryParams);
  }

  _makeQueryParams(page) {
    const options = this._options;
    const queryParams = assign({}, this._extraQueryParams);

    switch (options.style) {
      case styles.pageNumber:
        queryParams[options.pageQueryParam] = page;
        break;
      case styles.limitOffset:
        queryParams[options.limitQueryParam] = this._limit;
        queryParams[options.offsetQueryParam] = this._calculateOffset(page);
        break;
      default:
        throw new Error(errors.invalidStyle);
    }

    return queryParams;
  }

  _getRequest(page) {
    return this._cache[page];
  }

  _cacheRequest(page, response) {
    this._cache[page] = response;
  }

  _hasRequest(page) {
    return !!this._cache[page];
  }

  _fetchParsedResponse(page = 1) {
    if (this._parsedResponse) {
      return this._parsedResponse;
    }

    const parseResponseBody = this._parseResponseBody.bind(this);

    this._parsedResponse = this.fetchPage(page)
      .then(getResponseBody)
      .then(parseResponseBody);

    return this._parsedResponse;
  }

  _parseResponseBody(responseBody) {
    const {count, results} = responseBody;
    const limit = this._inferLimit(responseBody);
    let pageCount = 1;

    if (count && limit) {
      pageCount = Math.ceil(count / limit);
    }

    return {
      count,
      limit,
      pageCount,
      results
    };
  }

  _setOptions(options) {
    this._options = assign({}, defaultOptions, options);
  }

  _setQueryParams(raw) {
    const parsed = this._parseQueryParams(raw);

    this._limit = parsed.limit;
    this._extraQueryParams = parsed.extra;

    switch (this._options.style) {
      case styles.pageNumber:
        this._page = parsed.page;
        break;
      case styles.limitOffset:
        this._page = this._calculatePage(parsed.offset);
        break;
      default:
        throw new Error(errors.invalidStyle);
    }
  }

  _filterQueryParams(queryParams) {
    const options = this._options;
    const paginatorProperties = [
      options.limitQueryParam,
      options.offsetQueryParam,
      options.pageQueryParam
    ];

    return omit(queryParams, paginatorProperties);
  }

  _parseQueryParams(raw) {
    const {
      limitQueryParam,
      offsetQueryParam,
      pageQueryParam
    } = this._options;

    const parsed = assign({}, defaultQueryParams);

    if (raw.hasOwnProperty(limitQueryParam)) {
      parsed.limit = raw[limitQueryParam];
    }
    if (raw.hasOwnProperty(offsetQueryParam)) {
      parsed.offset = raw[offsetQueryParam];
    }
    if (raw.hasOwnProperty(pageQueryParam)) {
      parsed.page = raw[pageQueryParam];
    }

    parsed.extra = this._filterQueryParams(raw);

    return parsed;
  }

  _calculateOffset(page) {
    const limit = this._limit;

    if (page === 1) {
      return 0;
    }
    if (!limit) {
      throw new Error(errors.calculateOffsetNoLimit);
    }

    return limit * page - limit;
  }

  _calculatePage(offset) {
    const limit = this._limit;

    if (offset === 0) {
      return 1;
    }
    if (!limit) {
      throw new Error(errors.calculatePageNoLimit);
    }

    return (offset + limit) / limit;
  }

  _inferLimit(responseBody) {
    const count = responseBody.count;
    const resultCount = responseBody.results.length;

    if (count > resultCount) {
      return resultCount;
    }

    return null;
  }
}

Paginator.styles = styles;
Paginator.errors = errors;

export default Paginator;
<|endoftext|>


<commit_before>import {types as tt} from "./tokentype"
import {Parser} from "./state"
import {lineBreak, skipWhiteSpace} from "./whitespace"

const pp = Parser.prototype

// ## Parser utilities

const literal = /^(?:'((?:[^']|\.)*)'|"((?:[^"]|\.)*)"|;)/
pp.strictDirective = function(start) {
  for (;;) {
    skipWhiteSpace.lastIndex = start
    start += skipWhiteSpace.exec(this.input)[0].length
    let match = literal.exec(this.input.slice(start))
    if (!match) return false
    if ((match[1] || match[2]) == "use strict") return true
    start += match[0].length
  }
}

// Predicate that tests whether the next token is of the given
// type, and if yes, consumes it as a side effect.

pp.eat = function(type) {
  if (this.type === type) {
    this.next()
    return true
  } else {
    return false
  }
}

// Tests whether parsed token is a contextual keyword.

pp.isContextual = function(name) {
  return this.type === tt.name && this.value === name
}

// Consumes contextual keyword if possible.

pp.eatContextual = function(name) {
  return this.value === name && this.eat(tt.name)
}

// Asserts that following token is given contextual keyword.

pp.expectContextual = function(name) {
  if (!this.eatContextual(name)) this.unexpected()
}

// Test whether a semicolon can be inserted at the current position.

pp.canInsertSemicolon = function() {
  return this.type === tt.eof ||
    this.type === tt.braceR ||
    lineBreak.test(this.input.slice(this.lastTokEnd, this.start))
}

pp.insertSemicolon = function() {
  if (this.canInsertSemicolon()) {
    if (this.options.onInsertedSemicolon)
      this.options.onInsertedSemicolon(this.lastTokEnd, this.lastTokEndLoc)
    return true
  }
}

// Consume a semicolon, or, failing that, see if we are allowed to
// pretend that there is a semicolon at this position.

pp.semicolon = function() {
  if (!this.eat(tt.semi) && !this.insertSemicolon()) this.unexpected()
}

pp.afterTrailingComma = function(tokType, notNext) {
  if (this.type == tokType) {
    if (this.options.onTrailingComma)
      this.options.onTrailingComma(this.lastTokStart, this.lastTokStartLoc)
    if (!notNext)
      this.next()
    return true
  }
}

// Expect a token of a given type. If found, consume it, otherwise,
// raise an unexpected token error.

pp.expect = function(type) {
  this.eat(type) || this.unexpected()
}

// Raise an unexpected token error.

pp.unexpected = function(pos) {
  this.raise(pos != null ? pos : this.start, "Unexpected token")
}

export class DestructuringErrors {
  constructor() {
    this.shorthandAssign = this.trailingComma = this.parenthesizedAssign = this.parenthesizedBind = -1
  }
}

pp.checkPatternErrors = function(refDestructuringErrors, isAssign) {
  if (!refDestructuringErrors) return
  if (refDestructuringErrors.trailingComma > -1)
    this.raiseRecoverable(refDestructuringErrors.trailingComma, "Comma is not permitted after the rest element")
  let parens = isAssign ? refDestructuringErrors.parenthesizedAssign : refDestructuringErrors.parenthesizedBind
  if (parens > -1) this.raiseRecoverable(parens, "Parenthesized pattern")
}

pp.checkExpressionErrors = function(refDestructuringErrors, andThrow) {
  let pos = refDestructuringErrors ? refDestructuringErrors.shorthandAssign : -1
  if (!andThrow) return pos >= 0
  if (pos > -1) this.raise(pos, "Shorthand property assignments are valid only in destructuring patterns")
}

pp.checkYieldAwaitInDefaultParams = function() {
  if (this.yieldPos && (!this.awaitPos || this.yieldPos < this.awaitPos))
    this.raise(this.yieldPos, "Yield expression cannot be a default value")
  if (this.awaitPos)
    this.raise(this.awaitPos, "Await expression cannot be a default value")
}

pp.isSimpleAssignTarget = function(expr) {
  if (expr.type === "ParenthesizedExpression")
    return this.isSimpleAssignTarget(expr.expression)
  return expr.type === "Identifier" || expr.type === "MemberExpression"
}
<commit_msg>Change DestructuringErrors class to function<commit_after>import {types as tt} from "./tokentype"
import {Parser} from "./state"
import {lineBreak, skipWhiteSpace} from "./whitespace"

const pp = Parser.prototype

// ## Parser utilities

const literal = /^(?:'((?:[^']|\.)*)'|"((?:[^"]|\.)*)"|;)/
pp.strictDirective = function(start) {
  for (;;) {
    skipWhiteSpace.lastIndex = start
    start += skipWhiteSpace.exec(this.input)[0].length
    let match = literal.exec(this.input.slice(start))
    if (!match) return false
    if ((match[1] || match[2]) == "use strict") return true
    start += match[0].length
  }
}

// Predicate that tests whether the next token is of the given
// type, and if yes, consumes it as a side effect.

pp.eat = function(type) {
  if (this.type === type) {
    this.next()
    return true
  } else {
    return false
  }
}

// Tests whether parsed token is a contextual keyword.

pp.isContextual = function(name) {
  return this.type === tt.name && this.value === name
}

// Consumes contextual keyword if possible.

pp.eatContextual = function(name) {
  return this.value === name && this.eat(tt.name)
}

// Asserts that following token is given contextual keyword.

pp.expectContextual = function(name) {
  if (!this.eatContextual(name)) this.unexpected()
}

// Test whether a semicolon can be inserted at the current position.

pp.canInsertSemicolon = function() {
  return this.type === tt.eof ||
    this.type === tt.braceR ||
    lineBreak.test(this.input.slice(this.lastTokEnd, this.start))
}

pp.insertSemicolon = function() {
  if (this.canInsertSemicolon()) {
    if (this.options.onInsertedSemicolon)
      this.options.onInsertedSemicolon(this.lastTokEnd, this.lastTokEndLoc)
    return true
  }
}

// Consume a semicolon, or, failing that, see if we are allowed to
// pretend that there is a semicolon at this position.

pp.semicolon = function() {
  if (!this.eat(tt.semi) && !this.insertSemicolon()) this.unexpected()
}

pp.afterTrailingComma = function(tokType, notNext) {
  if (this.type == tokType) {
    if (this.options.onTrailingComma)
      this.options.onTrailingComma(this.lastTokStart, this.lastTokStartLoc)
    if (!notNext)
      this.next()
    return true
  }
}

// Expect a token of a given type. If found, consume it, otherwise,
// raise an unexpected token error.

pp.expect = function(type) {
  this.eat(type) || this.unexpected()
}

// Raise an unexpected token error.

pp.unexpected = function(pos) {
  this.raise(pos != null ? pos : this.start, "Unexpected token")
}

export function DestructuringErrors() {
  this.shorthandAssign =
  this.trailingComma =
  this.parenthesizedAssign =
  this.parenthesizedBind =
    -1
}

pp.checkPatternErrors = function(refDestructuringErrors, isAssign) {
  if (!refDestructuringErrors) return
  if (refDestructuringErrors.trailingComma > -1)
    this.raiseRecoverable(refDestructuringErrors.trailingComma, "Comma is not permitted after the rest element")
  let parens = isAssign ? refDestructuringErrors.parenthesizedAssign : refDestructuringErrors.parenthesizedBind
  if (parens > -1) this.raiseRecoverable(parens, "Parenthesized pattern")
}

pp.checkExpressionErrors = function(refDestructuringErrors, andThrow) {
  let pos = refDestructuringErrors ? refDestructuringErrors.shorthandAssign : -1
  if (!andThrow) return pos >= 0
  if (pos > -1) this.raise(pos, "Shorthand property assignments are valid only in destructuring patterns")
}

pp.checkYieldAwaitInDefaultParams = function() {
  if (this.yieldPos && (!this.awaitPos || this.yieldPos < this.awaitPos))
    this.raise(this.yieldPos, "Yield expression cannot be a default value")
  if (this.awaitPos)
    this.raise(this.awaitPos, "Await expression cannot be a default value")
}

pp.isSimpleAssignTarget = function(expr) {
  if (expr.type === "ParenthesizedExpression")
    return this.isSimpleAssignTarget(expr.expression)
  return expr.type === "Identifier" || expr.type === "MemberExpression"
}
<|endoftext|>