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

com.twineworks.tweakflow.lang.analysis.scope.Linker Maven / Gradle / Ivy

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2017 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.lang.analysis.scope;

import com.twineworks.tweakflow.lang.analysis.AnalysisStage;
import com.twineworks.tweakflow.lang.ast.UnitNode;
import com.twineworks.tweakflow.lang.ast.aliases.AliasNode;
import com.twineworks.tweakflow.lang.ast.exports.ExportNode;
import com.twineworks.tweakflow.lang.ast.expressions.ReferenceNode;
import com.twineworks.tweakflow.lang.ast.imports.ImportMemberNode;
import com.twineworks.tweakflow.lang.ast.imports.ModuleImportNode;
import com.twineworks.tweakflow.lang.ast.imports.NameImportNode;
import com.twineworks.tweakflow.lang.ast.structure.InteractiveNode;
import com.twineworks.tweakflow.lang.ast.structure.InteractiveSectionNode;
import com.twineworks.tweakflow.lang.ast.structure.ModuleNode;
import com.twineworks.tweakflow.lang.errors.LangError;
import com.twineworks.tweakflow.lang.errors.LangException;
import com.twineworks.tweakflow.lang.analysis.AnalysisUnit;
import com.twineworks.tweakflow.lang.analysis.AnalysisSet;
import com.twineworks.tweakflow.lang.scope.Scope;
import com.twineworks.tweakflow.lang.scope.Scopes;
import com.twineworks.tweakflow.lang.scope.Symbol;
import com.twineworks.tweakflow.lang.scope.SymbolTarget;

import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Map;

public class Linker {

  /*

    linker resolves names that are potentially defined in other modules

    alias $conf as conf

    import lib_a as a from "foo"    # connects name import lib_a with the exported name symbol from "foo"
    import * as m from "foo"        # connects module import m with the "foo" module (name) symbol

    alias m as q                    # connects module import q with the m name symbol

    alias $std.strings as s         # connects name import with with the std module symbol
    alias $std as stdlib            # connects name import with the std module symbol

    export $std.strings [as strings]

   */


  public static void link(AnalysisSet analysisSet){

    // chain keeps tracks of imports, aliases, exports etc. being followed:
    // if a symbol points back to itself directly or indirectly
    // a cyclic reference error is thrown
    ArrayDeque chain = new ArrayDeque<>();

    for (AnalysisUnit unit : analysisSet.getUnits().values()) {

      if (unit.getStage().getProgress() >= AnalysisStage.LINKED.getProgress()) continue;

      linkUnit(unit.getUnit(), chain);

      unit.setStage(AnalysisStage.LINKED);

    }

  }

  private static void linkUnit(UnitNode node, ArrayDeque chain){

    if (node instanceof ModuleNode){
      linkModule((ModuleNode) node, chain);
    }

    if (node instanceof InteractiveNode){
      linkInteractive((InteractiveNode) node, chain);
    }

  }

  private static void linkModule(ModuleNode module, ArrayDeque chain){

    // link imports
    for (ImportMemberNode importMemberNode: module.getImportsMap().values()) {
      Symbol symbol = importMemberNode.getSymbol();
      link(symbol, chain);
    }

    // link aliases
    for (AliasNode aliasNode : module.getAliases()) {
      link(aliasNode.getSymbol(), chain);
    }

    // link exports
    for (ExportNode exportNode : module.getExports()) {
      link(exportNode.getExportedSymbol(), chain);
    }

  }

  private static void linkInteractive(InteractiveNode interactiveNode, ArrayDeque chain){

    // example:
    //
    // interactive
    //   in_scope `fixtures/tweakflow/interactive/module_a.tf`
    //    x: a
    //   in_scope `fixtures/tweakflow/interactive/module_b.tf`
    //    y: b

    for (InteractiveSectionNode sectionNode : interactiveNode.getSections()) {
      // sections are in unit scope, so they have access to all loaded files
      // the reference has only one component: the path of the target module
      // target module scope is used to evaluate the section vars in

      // find the unit symbol of target module
      ReferenceNode inScopeRef = sectionNode.getInScopeRef();
      Symbol inScopeTarget = Scopes.resolve(inScopeRef); // throws if module is not found

      // make it the enclosing scope of the vars
      inScopeRef.setReferencedSymbol(inScopeTarget);
      Scope varScope = sectionNode.getVars().getScope();
      varScope.setEnclosingScope(inScopeTarget);
    }


  }

  private static void link(Symbol symbol, ArrayDeque chain){

    if (symbol.isLocal()){
      return;
    }

    if (symbol.isRefResolved()){
      return;
    }

    if (chain.contains(symbol)){
      throw new LangException(LangError.CYCLIC_REFERENCE, symbol.getNode().getSourceInfo())
          .put("chain", chain)
          .put("symbol", symbol)
          .put("node", symbol.getNode())
          .put("reference", symbol.getRefNode());
    }

    if (symbol.isNameImport()){
      linkNameImport(symbol, chain);
    }
    else if (symbol.isModuleImport()){
      linkModuleImport(symbol, chain);
    }
    else if (symbol.isAlias()){
      linkAlias(symbol, chain);
    }
    else if (symbol.isExport()){
      linkExport(symbol, chain);
    }
    else {
      throw new AssertionError("Unknown symbol type");
    }

  }

  private static void linkExport(Symbol symbol, ArrayDeque chain) {

    ReferenceNode source = symbol.getRefNode();

    // export m.foo.bar as new_name
    //        ^ root of source
    // root of the alias might be a local component, an alias, an import, or missing
    // it must resolve or fail

    ReferenceNode root = (ReferenceNode) new ReferenceNode()
        .setAnchor(source.getAnchor())
        .setElements(Collections.singletonList(source.getElements().get(0)))
        .setScope(source.getScope())
        .setSourceInfo(source.getSourceInfo());

    Symbol rootSymbol = Scopes.resolve(root); // throws if not resolved

    chain.push(symbol);
    link(rootSymbol, chain); // throws on circular dependency
    chain.pop();

    Symbol sourceSymbol = Scopes.resolve(source);
    symbol.setRef(sourceSymbol);
    symbol.setTarget(sourceSymbol.getTarget());

  }

  private static void linkModuleImport(Symbol symbol, ArrayDeque chain) {
    ModuleImportNode imp = (ModuleImportNode) symbol.getNode();
    ModuleNode moduleNode = (ModuleNode) imp.getImportedCompilationUnit().getUnit();
    symbol.setRef(moduleNode.getSymbol());
    symbol.setTarget(SymbolTarget.MODULE);
  }

  private static void linkAlias(Symbol symbol, ArrayDeque chain) {

    ReferenceNode source = symbol.getRefNode();

    // alias m.foo.bar as new_name
    //       ^ root of source
    // root of the alias might be a local component, another alias, an import, or missing
    // it must resolve or fail

    ReferenceNode root = (ReferenceNode) new ReferenceNode()
        .setAnchor(source.getAnchor())
        .setElements(Collections.singletonList(source.getElements().get(0)))
        .setScope(source.getScope())
        .setSourceInfo(source.getSourceInfo());

    Symbol rootSymbol = Scopes.resolve(root); // throws if not resolved

    chain.push(symbol);
    link(rootSymbol, chain); // throws on circular dependency
    chain.pop();

    Symbol sourceSymbol = Scopes.resolve(source);
    symbol.setRef(sourceSymbol);
    symbol.setTarget(sourceSymbol.getTarget());

  }

  private static void linkNameImport(Symbol symbol, ArrayDeque chain){

    // find the export in question in referenced module
    NameImportNode imp = (NameImportNode) symbol.getNode();
    ModuleNode moduleNode = (ModuleNode) imp.getImportedCompilationUnit().getUnit();
    Map exports = moduleNode.getUnitScope().getPublicScope().getSymbols();

    if (!exports.containsKey(imp.getExportName())){
      throw new LangException(LangError.CANNOT_FIND_EXPORT, imp.getSourceInfo());
    }
    Symbol exp = exports.get(imp.getExportName());

    if (exp.isLocal()){
      symbol.setRef(exp);
      symbol.setTarget(exp.getTarget());
    }
    // export nodes
    else {
      chain.push(symbol);
      link(exp, chain);
      chain.pop();
      symbol.setRef(exp);
      symbol.setTarget(exp.getTarget());
    }

  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy