Home Reference Source Test

lib/parser/semantic.js

const estraverse = require("estraverse");

const { TopEnv, SpecialForms } = require("../interp/environment.js");
const { Parser } = require("../parser/parse.js");

const SCOPE_OPERATORS = ["do", "if", "while", "for", "foreach", "fun", "->", "object"];
const SET_OPERATORS = [":=", "define", "def", "set", "="];

class Semantic {
  constructor(symboltable) {
    if(symboltable === undefined) {
      this.symboltable = Object.create(null);
    } else {
      this.symboltable = symboltable;
    }
  }

  check(tree) {
    const methods = this;

    tree = estraverse.replace(tree, {
      enter: function(node) {
        if (node.type === "apply") {
          node = methods.checkApplyEnter(node);
        }

        return node;
      },
      leave: function(node) {
        if (node.type === "apply") {
          node = methods.checkApplyLeave(node);
        }

        return node;
      },
      keys: {
        apply: ["args"],
        word: [],
        value: [],
        regex: []
      }
    });

    return tree;
  }

  static check(tree) {
    return new Semantic().check(tree);
  }

  checkApplyEnter(node) {
    if (node.operator.type == "word") {
      const operator_name = node.operator.name;

      // Check if operator is defined in SpecialForms
      let operator =
        operator_name in SpecialForms ? SpecialForms[operator_name] : undefined;

      // Check if operator is defined in TopEnv
      if (operator === undefined) {
        operator = operator_name in TopEnv ? TopEnv[operator_name] : undefined;
      }

      // Check that the number of argument passed to the operator is correct
      if (
        operator !== undefined &&
        operator.n_args !== undefined &&
        !operator.n_args(node.args.length)
      ) {
        throw new SyntaxError(
          `Bad number of args passed to ${operator_name}. (Got: ${
            node.args.length
          }. Expected: ${operator.n_args})`
        );
      }

      if (SCOPE_OPERATORS.includes(operator_name)) {
        this.symboltable = Object.create(this.symboltable);

        // node.symboltable = this.symboltable;
      }

      if (SET_OPERATORS.includes(operator_name)) {
        node.args[0] = this.addToSymboltable(node.args[0]);
      }
    }
    // console.log(this.symboltable);
    return node;
  }

  addToSymboltable(node) {
    const traits = {};

    while (node.type === "apply") {
      if (node.operator.name === "const") {
        traits["const"] = true;

        node = node.args[0];
      }
    }

    if (
      Object.prototype.hasOwnProperty.call(this.symboltable, node.name) &&
      this.symboltable[node.name].const
    ) {
      throw new TypeError(`${node.name} is const and can't be reassigned!`);
    } else {
      this.symboltable[node.name] = traits;
    }

    return node;
  }

  checkApplyLeave(node) {
    if (node.operator.type == "word") {
      const operator_name = node.operator.name;

      if (SCOPE_OPERATORS.includes(operator_name)) {
        this.symboltable = Object.getPrototypeOf(this.symboltable);
      }
    }

    // console.log(this.symboltable);

    return node;
  }
}

const tree = Parser.parse(`
  do(
    :=(const(x), 3),
    do(
      :=(const(x), 100),
      print(x)
    ),
    print(x)
  )`);

Semantic.check(tree);

module.exports = {
  Semantic
};