Home Reference Source

scripts/path-controller/util/expr.js

import * as vectormath from './vectormath.js';
import {lexer, tokdef, token, parser, PUTLParseError} from './parseutil.js';

let tk = (n, r, f) => new tokdef(n, r, f);

let count = (str, match) => {
  let c = 0;
  do {
    let i = str.search(match);
    if (i < 0) {
      break;
    }

    c++;

    str = str.slice(i+1, str.length);
  } while (1);

  return c;
}


let tokens = [
  tk("ID", /[a-zA-Z$_]+[a-zA-Z0-9$_]*/),
  tk("NUM", /[0-9]+(\.[0-9]*)?/),
  tk("LPAREN", /\(/),
  tk("RPAREN", /\)/),
  tk("STRLIT", /"[^"]*"/, (t) => { // /".*(?<!\\)"/ <- not working outside of Chrome
    let v = t.value;
    t.lexer.lineno += count(t.value, "\n");
    return t;
  }),
  tk("WS", /[ \t\n\r]/, (t) => {
    t.lexer.lineno += count(t.value, "\n");
    //drop token by not returning it
  }),
  tk("COMMA", /\,/),
  tk("COLON", /:/),
  tk("LSBRACKET", /\[/),
  tk("RSBRACKET", /\]/),
  tk("LBRACKET", /\{/),
  tk("RBRACKET", /\}/),
  tk("DOT", /\./),
  tk("PLUS", /\+/),
  tk("MINUS", /\-/),
  tk("TIMES", /\*/),
  tk("DIVIDE", /\//),
  tk("EXP", /\*\*/),
  tk("LAND", /\&\&/),
  tk("BAND", /\&/),
  tk("LOR", /\|\|/),
  tk("BOR", /\|/),
  tk("EQUALS", /=/),
  tk("LEQUALS", /\<\=/),
  tk("GEQUALS", /\>\=/),
  tk("LTHAN", /\</),
  tk("GTHAN", /\>/),
  tk("MOD", /\%/),
  tk("XOR", /\^/),
  tk("BITINV", /\~/)
];

let lex = new lexer(tokens, (t) => {
  console.log("Token error");
  return true;
});

let parse = new parser(lex);

let binops = new Set([
  ".", "/", "*", "**", "^", "%", "&", "+", "-", "&&", "||", "&", "|", "<",
  ">", "==", "=", "<=", ">="//, "(", ")"
]);

let precedence;

if (1) {
  let table = [
    ["**"],
    ["*", "/"],
    ["+", "-"],
    ["."],
    ["="],
    ["("],
    [")"]
//    [","],
//    ["("]
  ]

  let pr = {};
  for (let i = 0; i < table.length; i++) {
    for (let c of table[i]) {
      pr[c] = i;
    }
  }

  precedence = pr;
}


function indent(n, chr="  ") {
  let s = "";
  for (let i=0; i<n; i++) {
    s += chr;
  }

  return s;
}

export class Node extends Array {
  constructor(type) {
    super();
    this.type = type;
    this.parent = undefined;
  }

  push(n) {
    n.parent = this;
    return super.push(n);
  }

  remove(n) {
    let i = this.indexOf(n);

    if (i < 0) {
      console.log(n);
      throw new Error("item not in array");
    }

    while (i < this.length) {
      this[i] = this[i+1];
      i++;
    }

    n.parent = undefined;
    this.length--;

    return this;
  }

  insert(starti, n) {
    let i = this.length-1;
    this.length++;

    if (n.parent) {
      n.parent.remove(n);
    }

    while (i > starti) {
      this[i] = this[i-1];
      i--;
    }

    n.parent = this;
    this[starti] = n;

    return this;
  }

  replace(n, n2) {
    if (n2.parent) {
      n2.parent.remove(n2);
    }

    this[this.indexOf(n)] = n2;
    n.parent = undefined;
    n2.parent = this;

    return this;
  }

  toString(t=0) {
    let tab = indent(t, "-");

    let typestr = this.type;

    if (this.value !== undefined) {
      typestr +=  " : " + this.value;
    } else if (this.op !== undefined) {
      typestr += " (" + this.op + ")";
    }

    let s = tab + typestr + " {\n"
    for (let c of this) {
      s += c.toString(t+1);
    }
    s += tab + "}\n";

    return s;
  }
}

export function parseExpr(s) {
  let p = parse;

  function Value() {
    let t = p.next();


    if (t && t.value === "(") {
      t = p.next();
    }

    if (t === undefined) {
      p.error(undefined, "Expected a value");
      return;
    }

    let n = new Node();
    n.value = t.value;

    if (t.type === "ID") {
      n.type = "Ident";
    } else if (t.type === "NUM") {
      n.type = "Number";
    } else if (t.type === "STRLIT") {
      n.type = "StrLit";
    } else if (t.type === "MINUS") {
      let t2 = p.peek_i(0);
      if (t2 && t2.type === "NUM") {
        p.next();
        n.type = "Number";
        n.value = -t2.value;
      } else if (t2 && t2.type === "ID") {
        p.next();
        n.type = "Negate";

        let n2 = new Node();

        n2.type = "Ident"
        n2.value = t2.value;
        n.push(n2);
      } else {
        p.error(t, "Expected a value, not '" + t.value + "'");
      }
    } else {
      p.error(t, "Expected a value, not '" + t.value + "'");
    }

    return n;
  }


  function bin_next(depth=0) {
    let a = p.peek_i(0);
    let b = p.peek_i(1);

    if (b && b.value === ")") {
      b.type = a.type;
      b.value = a.value;
      p.next();

      let c = p.peek_i(2);
      if (c && binops.has(c.value)) {
        return {
          value : b,
          op : c.value,
          prec : -10
        }
      }
    }
    if (b && binops.has(b.value)) {
      return {
        value : a,
        op : b.value,
        prec : precedence[b.value]
      }
    } else {
      return Value(a);
    }
  }

  function BinOp(left, depth=0) {
    console.log(indent(depth) + "BinOp", left.toString());
    let op = p.next();
    let right;

    let n;
    let prec = precedence[op.value]

    let r = bin_next(depth+1);

    if (r instanceof Node) {
      right = r;
    } else {
      if (r.prec > prec) {
        if (!n) {
          n = new Node("BinOp")
          n.op = op.value;
          n.push(left);
        }

        n.push(Value())

        return n;
      } else {
        right = BinOp(Value(), depth+2);
      }
    }

    n = new Node("BinOp", op);
    n.op = op.value;
    n.push(right);
    n.push(left);

    console.log("\n\n", n.toString(), "\n\n");
    left = n;

    console.log(n.toString());

    return n;

  }

  function Start() {
    let ret = Value();

    while (!p.at_end()) {
      let t = p.peek_i(0);
      //let n = p.peek_i(1);

      if (t === undefined) {
        break;
        //return Value();
      }

      console.log(t.toString()) //, n.toString())

      if (binops.has(t.value)) {
        console.log("binary op!");
        ret = BinOp(ret);
      } else if (t.value === ",") {
        let n = new Node();
        n.type = "ExprList";

        p.next();

        n.push(ret);
        let n2 = Start();
        if (n2.type === "ExprList") {
          for (let c of n2) {
            n.push(c);
          }
        } else {
          n.push(n2);
        }

        return n;
      } else if (t.value === "(") {
        let n = new Node("FuncCall");
        n.push(ret);
        n.push(Start());
        p.expect("RPAREN")
        return n;
      } else if (t.value === ")") {

        return ret;
      } else {
        //ret = Value();
        //break;
        console.log(ret.toString())
        p.error(t, "Unexpected token " + t.value);
      }
    }

    return ret;
  }

  function Run() {
    let ret = [];

    while (!p.at_end()) {
      ret.push(Start());
    }

    return ret;
  }

  p.start = Run;
  return p.parse(s);

  /*
  lex.input(s);
  let t = lex.next();
  while (t) {
    console.log(t.toString());
    t = lex.next();
  }
  //*/
}