lib/scope.js

Summary

Maintainability
A
2 hrs
Test Coverage
A
100%
"use strict";

const Types = require("./types");
const { bn, toHex } = require("./bn");

const shorts = { $: "c", "&": "r", "*": "p", "@": "l", contract: "C", packet: "P", struct: "S", union: "U" };

class Scope {
  constructor(next, contract) {
    this.contract = contract;
    this.contracts = {};
    this.functions = {};
    this.global = next ? false : true;
    this.mmap = 0;
    this.params = [];
    this.smap = [];
    this.statements = [];
    this.types = {};
    this.variables = {};

    if(next) this.next = next;
  }

  addContract(contract) {
    const res = this.addStruct("contract", contract);
    const { name } = contract;

    delete res.attrs;
    delete res.size;

    this.contracts[name] = res;
  }

  addFunction(name, func) {
    const { extended, first, scope, signature, type } = func;
    const f = { extended, first, name, scope, signature, type };

    ["export", "defined", "pure", "stackless", "view"].map(m => {
      if(func[m]) f[m] = func[m];
    });

    this.functions[signature] = f;
  }

  addStatement(statement) {
    this.statements.push(statement);
  }

  addStruct(what, struct, declaration) {
    let { name, scope } = struct;
    let attrs = [];
    let stype = "";
    let offset = 0;
    let short = "";

    if(declaration) {
      stype = what + " " + name;

      return (this.types[stype] = { short: shorts[what] + name.length + name, type: stype, undefined: true });
    }

    scope.params.map(att => {
      const { dim, name, type } = att;
      const attr = { name, offset, type };
      const size = what === "packet" ? type.size : Math.ceil(type.size / 32);
      let elements = 1;

      attrs.push(attr);

      offset += size * elements;
      stype += type.type + "; ";
      short += type.short;
    });

    short = shorts[what] + (name ? name.length + name : attrs.length + short);
    stype = what + " " + (name ? name : `{ ${stype}}`);

    const size = offset * (what === "packet" ? 1 : 32);
    const res = { attrs, defined: true, short, size, type: stype };

    res[what] = true;

    return (this.types[stype] = res);
  }

  addType(name, type) {
    const base = Types[type];

    if(base) return (this.types[name] = base);

    if(this.types[name]) return;
    if(this.types[type]) return (this.types[name] = this.types[type]);

    const last = type[type.length - 1];
    const res = { size: 32, type };

    if(last === "&" || last === "@") res.reference = true;
    if(last === "*" || last === "%") res.pointer = true;
    if(last === ")") {
      const types = type.substring(1, type.length - 1).split(", ");
      const retype = e => {
        if(Types[e]) return Types[e].short;

        return this.types[e].short;
      };

      res.list = true;
      res.size = types.length * 32;
      res.type = type;
      res.short = "L" + types.length + types.map(retype).join("");
    }

    if(res.pointer || res.reference) {
      const deref = type.substring(0, type.length - 1);
      const prev = this.getType(deref);

      if(! prev) this.addType(deref, deref);

      res.deref = prev ? prev : this.getType(deref);
      res.short = shorts[last] + res.deref.short;
    }

    this.types[name] = this.types[type] = res;
  }

  addTypeArray(deref, dim) {
    const type = `${deref.type}[${dim[dim.isType ? "type" : "hex"]}]`;

    if(this.types[type]) return this.types[type];

    const short2 = deref.short + (dim.isType ? this.getType(dim.type).short : dim.hex.substr(1));
    const short = `A${short2.length}${short2}`;

    return (this.types[type] = { deref, pointer: true, short, type, ...(dim.isType ? {} : { size: Math.ceil(deref.size / 32) * 32 * bn(dim.hex).toNumber() }) });
  }

  addVar(name, variable) {
    const { attr, dim, param, val, type } = variable;
    const v = { name, type };

    ["export", "memory", "offset", "register", "storage"].map(m => {
      if(variable[m]) v[m] = variable[m];
    });

    if(! dim.length) v.lvalue = true;

    if(this.contract) {
      if(v.storage) {
        v.smap = this.smap.length;
        this.smap.push(v);
      } else {
        let { size } = v.type;

        if(size < 32) size = 32;

        v.mmap = this.mmap;
        this.mmap += size;
      }
    }

    if(val !== undefined) v.val = val;

    this.variables[name] = v;
    if(attr || param) this.params.push(v);
  }

  getFunction(name, last) {
    const ret = this.functions[name];

    return ret ? ret : last ? null : this.next ? this.next.getFunction(name) : null;
  }

  getType(name, last) {
    if(Types[name]) return Types[name];

    const ret = this.types[name];

    return ret ? ret : last ? null : this.next ? this.next.getType(name) : null;
  }

  getVariable(name, last) {
    const ret = this.variables[name];

    return ret ? ret : last ? null : this.next ? this.next.getVariable(name) : null;
  }
}

module.exports = Scope;