123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- // scope.js
- // MIT licensed, see LICENSE file
- // Copyright (c) 2013-2015 Olov Lassus <olov.lassus@gmail.com>
- "use strict";
- const assert = require("assert");
- const stringmap = require("stringmap");
- const stringset = require("stringset");
- const is = require("simple-is");
- const fmt = require("simple-fmt");
- function Scope(args) {
- assert(is.someof(args.kind, ["hoist", "block", "catch-block"]));
- assert(is.object(args.node));
- assert(args.parent === null || is.object(args.parent));
- // kind === "hoist": function scopes, program scope, injected globals
- // kind === "block": ES6 block scopes
- // kind === "catch-block": catch block scopes
- this.kind = args.kind;
- // the AST node the block corresponds to
- this.node = args.node;
- // parent scope
- this.parent = args.parent;
- // children scopes for easier traversal (populated internally)
- this.children = [];
- // scope declarations. decls[variable_name] = {
- // kind: "fun" for functions,
- // "param" for function parameters,
- // "caught" for catch parameter
- // "var",
- // "const",
- // "let"
- // node: the AST node the declaration corresponds to
- // from: source code index from which it is visible at earliest
- // (only stored for "const", "let" [and "var"] nodes)
- // }
- this.decls = stringmap();
- // names of all variables declared outside this hoist scope but
- // referenced in this scope (immediately or in child).
- // only stored on hoist scopes for efficiency
- // (because we currently generate lots of empty block scopes)
- this.propagates = (this.kind === "hoist" ? stringset() : null);
- // scopes register themselves with their parents for easier traversal
- if (this.parent) {
- this.parent.children.push(this);
- }
- }
- Scope.prototype.print = function(indent) {
- indent = indent || 0;
- const scope = this;
- const names = this.decls.keys().map(function(name) {
- return fmt("{0} [{1}]", name, scope.decls.get(name).kind);
- }).join(", ");
- const propagates = this.propagates ? this.propagates.items().join(", ") : "";
- console.log(fmt("{0}{1}: {2}. propagates: {3}", fmt.repeat(" ", indent), this.node.type, names, propagates));
- this.children.forEach(function(c) {
- c.print(indent + 2);
- });
- };
- Scope.prototype.add = function(name, kind, node, referableFromPos) {
- assert(is.someof(kind, ["fun", "param", "var", "caught", "const", "let"]));
- function isConstLet(kind) {
- return is.someof(kind, ["const", "let"]);
- }
- let scope = this;
- // search nearest hoist-scope for fun, param and var's
- // const, let and caught variables go directly in the scope (which may be hoist, block or catch-block)
- if (is.someof(kind, ["fun", "param", "var"])) {
- while (scope.kind !== "hoist") {
- // if (scope.decls.has(name) && isConstLet(scope.decls.get(name).kind)) { // could be caught
- // return error(getline(node), "{0} is already declared", name);
- // }
- scope = scope.parent;
- }
- }
- // name exists in scope and either new or existing kind is const|let => error
- // if (scope.decls.has(name) && (isConstLet(scope.decls.get(name).kind) || isConstLet(kind))) {
- // return error(getline(node), "{0} is already declared", name);
- // }
- const declaration = {
- kind: kind,
- node: node,
- };
- if (referableFromPos) {
- assert(is.someof(kind, ["var", "const", "let"]));
- declaration.from = referableFromPos;
- }
- scope.decls.set(name, declaration);
- };
- Scope.prototype.getKind = function(name) {
- assert(is.string(name));
- const decl = this.decls.get(name);
- return decl ? decl.kind : null;
- };
- Scope.prototype.getNode = function(name) {
- assert(is.string(name));
- const decl = this.decls.get(name);
- return decl ? decl.node : null;
- };
- Scope.prototype.getFromPos = function(name) {
- assert(is.string(name));
- const decl = this.decls.get(name);
- return decl ? decl.from : null;
- };
- Scope.prototype.hasOwn = function(name) {
- return this.decls.has(name);
- };
- Scope.prototype.remove = function(name) {
- return this.decls.remove(name);
- };
- Scope.prototype.doesPropagate = function(name) {
- return this.propagates.has(name);
- };
- Scope.prototype.markPropagates = function(name) {
- this.propagates.add(name);
- };
- Scope.prototype.closestHoistScope = function() {
- let scope = this;
- while (scope.kind !== "hoist") {
- scope = scope.parent;
- }
- return scope;
- };
- Scope.prototype.lookup = function(name) {
- for (let scope = this; scope; scope = scope.parent) {
- if (scope.decls.has(name)) {
- return scope;
- } else if (scope.kind === "hoist") {
- scope.propagates.add(name);
- }
- }
- return null;
- };
- module.exports = Scope;
|