scope.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. // scope.js
  2. // MIT licensed, see LICENSE file
  3. // Copyright (c) 2013-2015 Olov Lassus <olov.lassus@gmail.com>
  4. "use strict";
  5. const assert = require("assert");
  6. const stringmap = require("stringmap");
  7. const stringset = require("stringset");
  8. const is = require("simple-is");
  9. const fmt = require("simple-fmt");
  10. function Scope(args) {
  11. assert(is.someof(args.kind, ["hoist", "block", "catch-block"]));
  12. assert(is.object(args.node));
  13. assert(args.parent === null || is.object(args.parent));
  14. // kind === "hoist": function scopes, program scope, injected globals
  15. // kind === "block": ES6 block scopes
  16. // kind === "catch-block": catch block scopes
  17. this.kind = args.kind;
  18. // the AST node the block corresponds to
  19. this.node = args.node;
  20. // parent scope
  21. this.parent = args.parent;
  22. // children scopes for easier traversal (populated internally)
  23. this.children = [];
  24. // scope declarations. decls[variable_name] = {
  25. // kind: "fun" for functions,
  26. // "param" for function parameters,
  27. // "caught" for catch parameter
  28. // "var",
  29. // "const",
  30. // "let"
  31. // node: the AST node the declaration corresponds to
  32. // from: source code index from which it is visible at earliest
  33. // (only stored for "const", "let" [and "var"] nodes)
  34. // }
  35. this.decls = stringmap();
  36. // names of all variables declared outside this hoist scope but
  37. // referenced in this scope (immediately or in child).
  38. // only stored on hoist scopes for efficiency
  39. // (because we currently generate lots of empty block scopes)
  40. this.propagates = (this.kind === "hoist" ? stringset() : null);
  41. // scopes register themselves with their parents for easier traversal
  42. if (this.parent) {
  43. this.parent.children.push(this);
  44. }
  45. }
  46. Scope.prototype.print = function(indent) {
  47. indent = indent || 0;
  48. const scope = this;
  49. const names = this.decls.keys().map(function(name) {
  50. return fmt("{0} [{1}]", name, scope.decls.get(name).kind);
  51. }).join(", ");
  52. const propagates = this.propagates ? this.propagates.items().join(", ") : "";
  53. console.log(fmt("{0}{1}: {2}. propagates: {3}", fmt.repeat(" ", indent), this.node.type, names, propagates));
  54. this.children.forEach(function(c) {
  55. c.print(indent + 2);
  56. });
  57. };
  58. Scope.prototype.add = function(name, kind, node, referableFromPos) {
  59. assert(is.someof(kind, ["fun", "param", "var", "caught", "const", "let"]));
  60. function isConstLet(kind) {
  61. return is.someof(kind, ["const", "let"]);
  62. }
  63. let scope = this;
  64. // search nearest hoist-scope for fun, param and var's
  65. // const, let and caught variables go directly in the scope (which may be hoist, block or catch-block)
  66. if (is.someof(kind, ["fun", "param", "var"])) {
  67. while (scope.kind !== "hoist") {
  68. // if (scope.decls.has(name) && isConstLet(scope.decls.get(name).kind)) { // could be caught
  69. // return error(getline(node), "{0} is already declared", name);
  70. // }
  71. scope = scope.parent;
  72. }
  73. }
  74. // name exists in scope and either new or existing kind is const|let => error
  75. // if (scope.decls.has(name) && (isConstLet(scope.decls.get(name).kind) || isConstLet(kind))) {
  76. // return error(getline(node), "{0} is already declared", name);
  77. // }
  78. const declaration = {
  79. kind: kind,
  80. node: node,
  81. };
  82. if (referableFromPos) {
  83. assert(is.someof(kind, ["var", "const", "let"]));
  84. declaration.from = referableFromPos;
  85. }
  86. scope.decls.set(name, declaration);
  87. };
  88. Scope.prototype.getKind = function(name) {
  89. assert(is.string(name));
  90. const decl = this.decls.get(name);
  91. return decl ? decl.kind : null;
  92. };
  93. Scope.prototype.getNode = function(name) {
  94. assert(is.string(name));
  95. const decl = this.decls.get(name);
  96. return decl ? decl.node : null;
  97. };
  98. Scope.prototype.getFromPos = function(name) {
  99. assert(is.string(name));
  100. const decl = this.decls.get(name);
  101. return decl ? decl.from : null;
  102. };
  103. Scope.prototype.hasOwn = function(name) {
  104. return this.decls.has(name);
  105. };
  106. Scope.prototype.remove = function(name) {
  107. return this.decls.remove(name);
  108. };
  109. Scope.prototype.doesPropagate = function(name) {
  110. return this.propagates.has(name);
  111. };
  112. Scope.prototype.markPropagates = function(name) {
  113. this.propagates.add(name);
  114. };
  115. Scope.prototype.closestHoistScope = function() {
  116. let scope = this;
  117. while (scope.kind !== "hoist") {
  118. scope = scope.parent;
  119. }
  120. return scope;
  121. };
  122. Scope.prototype.lookup = function(name) {
  123. for (let scope = this; scope; scope = scope.parent) {
  124. if (scope.decls.has(name)) {
  125. return scope;
  126. } else if (scope.kind === "hoist") {
  127. scope.propagates.add(name);
  128. }
  129. }
  130. return null;
  131. };
  132. module.exports = Scope;