All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.twineworks.tweakflow.util.VarTable Maven / Gradle / Ivy

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 Twineworks GmbH
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package com.twineworks.tweakflow.util;

import com.twineworks.tweakflow.lang.TweakFlow;
import com.twineworks.tweakflow.lang.errors.LangException;
import com.twineworks.tweakflow.lang.interpreter.DebugHandler;
import com.twineworks.tweakflow.lang.interpreter.SimpleDebugHandler;
import com.twineworks.tweakflow.lang.load.loadpath.LoadPath;
import com.twineworks.tweakflow.lang.load.loadpath.LoadPathLocation;
import com.twineworks.tweakflow.lang.load.loadpath.MemoryLocation;
import com.twineworks.tweakflow.lang.parse.ParseResult;
import com.twineworks.tweakflow.lang.parse.Parser;
import com.twineworks.tweakflow.lang.parse.SourceInfo;
import com.twineworks.tweakflow.lang.parse.units.ParseUnit;
import com.twineworks.tweakflow.lang.runtime.Runtime;

import java.util.*;

public class VarTable {

  public static class Builder implements com.twineworks.tweakflow.util.Builder{

    String prologue = "";
    String modulePath = "var_table_module";
    String varLibraryName = "var_table";
    LinkedHashMap vars = new LinkedHashMap<>();
    LoadPath loadPath = new LoadPath.Builder().addStdLocation().build();
    DebugHandler debugHandler = new SimpleDebugHandler();

    public Builder setVarLibraryName(String varLibraryName){
      Objects.requireNonNull(varLibraryName, "varLibraryName path cannot be null");
      if (varLibraryName.isEmpty()) throw new IllegalArgumentException("varLibraryName cannot be empty");
      this.varLibraryName = varLibraryName;
      return this;
    }

    public Builder setModulePath(String modulePath){
      Objects.requireNonNull(modulePath, "module path cannot be null");
      if (modulePath.isEmpty()) throw new IllegalArgumentException("module path cannot be empty");
      this.modulePath = modulePath;
      return this;
    }

    public Builder setDebugHandler(DebugHandler debugHandler){
      this.debugHandler = debugHandler;
      return this;
    }

    public Builder setLoadPath(LoadPath loadPath){
      Objects.requireNonNull(loadPath, "load path cannot be null");
      this.loadPath = loadPath;
      return this;
    }

    public Builder setPrologue(String prologue){
      this.prologue = prologue;
      return this;
    }

    public Builder addVar(String name, String expression){
      Objects.requireNonNull(name, "variable name cannot be null");
      if (name.isEmpty()) throw new IllegalArgumentException("variable name cannot be empty");

      if (vars.containsKey(name)){
        throw new IllegalArgumentException("var "+name+" already exists");
      }

      vars.put(name, expression);
      return this;
    }

    @Override
    public VarTable build() {
      return new VarTable(loadPath, modulePath, prologue, varLibraryName, vars, debugHandler);
    }
  }

  private final String modulePath;
  private final String varLibraryName;
  private final String prologue;
  private final LinkedHashMap vars;
  private final LoadPath loadPath;
  private final LinkedHashMap varLines;
  private final LinkedHashMap varParseErrors;
  private LangException prologueParseError;
  private final String moduleText;
  private final ParseUnit moduleParseUnit;
  private int lastLibraryLine;

  private int lineCount(String str) {
    if (str == null || str.isEmpty()) return 0;
    int lines = 1;
    int pos = 0;
    while ((pos = str.indexOf("\n", pos) + 1) != 0) {
      lines+=1;
    }
    return lines;
  }

  private VarTable(LoadPath loadPath, String modulePath, String prologue, String varLibraryName, HashMap vars, DebugHandler debugHandler){

    this.modulePath = modulePath;
    this.prologue = prologue;
    this.varLibraryName = varLibraryName;

    this.vars = new LinkedHashMap<>(vars);
    this.varParseErrors = new LinkedHashMap<>();
    this.varLines = new LinkedHashMap<>();

    this.prologueParseError = prologueParseError();
    moduleText = makeModuleText();

    LoadPath.Builder loadPathBuilder = new LoadPath.Builder();

    for (LoadPathLocation loadPathLocation : loadPath.getLocations()) {
      loadPathBuilder.add(loadPathLocation);
    }

    MemoryLocation varTableLocation = new MemoryLocation.Builder()
        .add(modulePath, moduleText)
        .build();

    this.moduleParseUnit = varTableLocation.getParseUnit(modulePath);

    this.loadPath = loadPathBuilder.add(varTableLocation).build();

  }

  public ParseUnit getModuleParseUnit() {
    return moduleParseUnit;
  }

  public LangException getPrologueParseError() {
    return prologueParseError;
  }

  public LinkedHashMap getVarParseErrors() {
    return new LinkedHashMap<>(varParseErrors);
  }

  public boolean hasParseErrors(){
    return prologueParseError != null || !varParseErrors.isEmpty();
  }

  public Runtime compile() {
    return TweakFlow.compile(loadPath, modulePath);
  }

  public String varNameFor(SourceInfo sourceInfo){

    if (sourceInfo == null) return null;
    if (vars.size() == 0) return null;

    if (sourceInfo.getParseUnit() != moduleParseUnit) return null;
    int causeLine = sourceInfo.getLine();
    if (causeLine >= lastLibraryLine) return null;

    // source info points to var table
    ArrayList varNames = new ArrayList<>(vars.keySet());
    int firstVarLine = varLines.get(varNames.get(0));
    if (firstVarLine > causeLine) return null;

    // first var before or on cause
    for (int i=varLines.size()-1;i>=0;i--){
      String varName = varNames.get(i);
      int varLine = varLines.get(varName);
      if (varLine <= causeLine) return varName;
    }

    return null;

  }

  public String getModuleText() {
    return moduleText;
  }

  public String getModulePath() {
    return modulePath;
  }

  public String getVarLibraryName() {
    return varLibraryName;
  }

  private LangException varParseException(String exp){
    // expression must parse
    ParseUnit parseUnit = new MemoryLocation.Builder()
        .add("exp", exp)
        .build()
        .getParseUnit("exp");

    ParseResult parseResult = new Parser(parseUnit).parseExpression();
    return parseResult.getException();
  }

  private LangException prologueParseError(){
    // prologue must parse as valid module
    ParseUnit parseUnit = new MemoryLocation.Builder()
        .add("prologue", prologue)
        .build()
        .getParseUnit("prologue");

    ParseResult parseResult = new Parser(parseUnit).parseUnit();
    return parseResult.getException();
  }

  private String makeModuleText(){

    StringBuilder stringBuilder = new StringBuilder();
    String prefix = "";
    if (!prologue.isEmpty()){
      prefix = prologue+"\n";
    }

    prefix = prefix+"library "+ LangUtil.escapeIdentifier(varLibraryName)+" {\n";
    stringBuilder.append(prefix);
    int currentLine = lineCount(prefix);

    for (String varName : vars.keySet()) {

      String nameLine = LangUtil.escapeIdentifier(varName)+":\n";
      stringBuilder.append(nameLine);
      currentLine += lineCount(nameLine)-1;
      String varExp = vars.get(varName);
      varLines.put(varName, currentLine);

      LangException varException = varParseException(varExp);
      if (varException != null){
        varParseErrors.put(varName, varException);
        varExp = "nil";
      }
      currentLine += lineCount(varExp);
      stringBuilder.append(varExp);

      stringBuilder.append("\n;\n\n");
      currentLine += 2;
    }
    stringBuilder.append("}\n");

    lastLibraryLine = currentLine;

    return stringBuilder.toString();
  }

  public List varNames(){
    return new ArrayList<>(vars.keySet());
  }

  public String varExpression(String name){
    if (vars.containsKey(name)) return vars.get(name);
    throw new IllegalArgumentException("var "+name+" is not part of the table");
  }

  public int varExpressionLine(String name){
    if (varLines.containsKey(name)) return varLines.get(name);
    throw new IllegalArgumentException("var "+name+" is not part of the table");
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy