<commit_before>import assert from './assert';
import UserActivation from './UserActivation';

function setsEqual(a, b) {
  if (a.size !== b.size) {
    return false;
  }

  for (const x of a) {
    if (!b.has(x)) {
      return false;
    }
  }

  return true;
}

class Connection {
  constructor(outPort, inPort, path) {
    this.outPort = outPort;
    this.inPort = inPort;
    this.path = path;
  }
}

class OutPort {
  constructor(containingDefinition, tempo) {
    this.containingDefinition = containingDefinition;
    this.tempo = tempo;
    this.connections = new Set();
  }
}

class InPort {
  constructor(containingDefinition, name, tempo, owner) {
    this.containingDefinition = containingDefinition;
    this.name = name;
    this.tempo = tempo;
    this.owner = owner;
    this.connection = null;
  }
}

// Within a UserDefinition, this represents the application of a native function
class NativeApplication {
  constructor(definition, functionArguments, settings) {
    this.definition = definition;
    this.functionArguments = functionArguments; // NOTE: these are unchangeable for now
    this.settings = settings;

    this.sortIndex = undefined;
    this.inputs = []; // array of InPort
    this.output = null; // either an OutPort or a Map from string to OutPort
  }
}

// Definition of a user-defined (not native) function
// containingDefinition is the UserDefinition that contains this one, or null if this is a root-level definition.
export default class UserDefinition {
  constructor(containingDefinition, signature) {
    this.containingDefinition = containingDefinition;

    this.containedDefinitions = new Set();
    this.definitionToUsingApplications = new Map(); // map from UserDefinition (in this or outer scope) to the Set of local NativeApplications that make use of it (take it as a "function argument")
    this.nativeApplications = new Set(); // These are kept in topological sort order

    this.definitionInputs = []; // array of OutPort
    this.definitionOutputs = null; // either InPort or Map from name to InPort or null

    if (signature) {
      for (const inp of signature.inputs) {
        this.definitionInputs.push(new OutPort(this, inp.tempo));
      }
      const inPortsOwner = {
        tag: 'def',
        definition: this,
      };
      if (signature.output) {
        assert(!signature.outputs);
        this.definitionOutput = new InPort(this, null, signature.output.tempo, inPortsOwner);
      } else if (signature.outputs) {
        this.definitionOutput = new Map();
        for (const n in signature.outputs) {
          this.definitionOutput.set(n, new InPort(this, n, signature.outputs[n].tempo, inPortsOwner));
        }
      }
    }

    this.connections = new Set(); // All connections whose out port is in this definition

    // All activations of this definition (UserActivation instances)
    this.activations = new Set();
  }

  // Create a new (initially empty) user-defined function definition, contained within this definition.
  addContainedUserDefinition(signature) {
    const definition = new UserDefinition(this, signature);

    this.containedDefinitions.add(definition);
    this.definitionToUsingApplications.set(definition, new Set());

    return definition;
  }

  addNativeApplication(definition, functionArguments) {
    // Validate functionArguments parameter
    if (functionArguments) {
      for (const [n, def] of functionArguments) {
        assert(n in definition.functionParameters); // can only provide args matching parameters
        if (def !== undefined) {
          // TODO: assert that def is either native or a user definition with a common parent
        }
      }
    }

    const app = new NativeApplication(definition, functionArguments, definition.defaultSettings);

    // Create port objects for the application
    // TODO: In the future, I think these port objects will need to be created on-demand as well (for variable positional arguments)
    const inPortsOwner = {
      tag: 'napp',
      nativeApplication: app,
    };
    for (const inp of definition.inputs) {
      app.inputs.push(new InPort(this, inp.name, inp.tempo, inPortsOwner));
    }
    if (definition.output) {
      assert(!definition.outputs);
      app.output = new OutPort(this, definition.output.tempo);
    } else if (definition.outputs) {
      app.output = new Map();
      for (const n in definition.outputs) {
        app.output.set(n, new OutPort(this, definition.outputs[n].tempo));
      }
    }

    this.nativeApplications.add(app);

    // Track function arguments used by this application so that we may send in values from outer
    // scopes if needed.
    if (functionArguments) {
      for (const def of functionArguments.values()) {
        if (def) {
          this.definitionToUsingApplications.get(def).add(app);
        }
      }
    }

    // Update topological sort
    // NOTE: While the sort shouldn't really have changed, we want to make sure that all
    // applications have valid sort indexes. We could also just assign this new application
    // a valid sort index (e.g. one higher than current highest) but this is easy/clear.
    this._updateTopologicalSort();

    // Let all activations of this definition know that a native application was added
    for (const act of this.activations) {
      act.addedNativeApplication(app);
    }

    this.recursiveActivationsUpdate();

    return app;
  }

  removeNativeApplication(nativeApplication) {
    // TODO: Remove all connections first, using an "internal" method that doesn't call recursiveActivationsUpdate

    for (const [def, apps] of definitionToUsingApplications) {
      apps.delete(nativeApplication);
    }

    this.nativeApplications.delete(app);

    // Let all activations of this definition know that a native application was removed
    for (const act of this.activations) {
      act.removedNativeApplication(app);
    }

    this.recursiveActivationsUpdate();
  }

  disconnectPort(portObj) {
    if (portObj instanceof InPort) {
      this._removeConnection(portObj.connection);
    } else {
      assert(portObj instanceof OutPort);
      for (const cxn of portObj.connections) {
        this._removeConnection(cxn);
      }
    }

    this.recursiveActivationsUpdate();
  }

  setApplicationSettings(nativeApplication, newSettings) {
    assert(this.nativeApplications.has(nativeApplication));

    // Let all activations of this definition know that this application's settings have changed
    for (const act of this.activations) {
      act.setApplicationSettings(nativeApplication, newSettings);
    }

    this.recursiveActivationsUpdate();<commit_msg>Do more work towards removing applications<commit_after>import assert from './assert';
import UserActivation from './UserActivation';

function setsEqual(a, b) {
  if (a.size !== b.size) {
    return false;
  }

  for (const x of a) {
    if (!b.has(x)) {
      return false;
    }
  }

  return true;
}

class Connection {
  constructor(outPort, inPort, path) {
    this.outPort = outPort;
    this.inPort = inPort;
    this.path = path;
  }
}

class OutPort {
  constructor(containingDefinition, tempo) {
    this.containingDefinition = containingDefinition;
    this.tempo = tempo;
    this.connections = new Set();
  }
}

class InPort {
  constructor(containingDefinition, name, tempo, owner) {
    this.containingDefinition = containingDefinition;
    this.name = name;
    this.tempo = tempo;
    this.owner = owner;
    this.connection = null;
  }
}

// Within a UserDefinition, this represents the application of a native function
class NativeApplication {
  constructor(definition, functionArguments, settings) {
    this.definition = definition;
    this.functionArguments = functionArguments; // NOTE: these are unchangeable for now
    this.settings = settings;

    this.sortIndex = undefined;
    this.inputs = []; // array of InPort
    this.output = null; // either an OutPort or a Map from string to OutPort
  }
}

// Definition of a user-defined (not native) function
// containingDefinition is the UserDefinition that contains this one, or null if this is a root-level definition.
export default class UserDefinition {
  constructor(containingDefinition, signature) {
    this.containingDefinition = containingDefinition;

    this.containedDefinitions = new Set();
    this.definitionToUsingApplications = new Map(); // map from UserDefinition (in this or outer scope) to the Set of local NativeApplications that make use of it (take it as a "function argument")
    this.nativeApplications = new Set(); // These are kept in topological sort order

    this.definitionInputs = []; // array of OutPort
    this.definitionOutputs = null; // either InPort or Map from name to InPort or null

    if (signature) {
      for (const inp of signature.inputs) {
        this.definitionInputs.push(new OutPort(this, inp.tempo));
      }
      const inPortsOwner = {
        tag: 'def',
        definition: this,
      };
      if (signature.output) {
        assert(!signature.outputs);
        this.definitionOutput = new InPort(this, null, signature.output.tempo, inPortsOwner);
      } else if (signature.outputs) {
        this.definitionOutput = new Map();
        for (const n in signature.outputs) {
          this.definitionOutput.set(n, new InPort(this, n, signature.outputs[n].tempo, inPortsOwner));
        }
      }
    }

    this.connections = new Set(); // All connections whose out port is in this definition

    // All activations of this definition (UserActivation instances)
    this.activations = new Set();
  }

  // Create a new (initially empty) user-defined function definition, contained within this definition.
  addContainedUserDefinition(signature) {
    const definition = new UserDefinition(this, signature);

    this.containedDefinitions.add(definition);
    this.definitionToUsingApplications.set(definition, new Set());

    return definition;
  }

  addNativeApplication(definition, functionArguments) {
    // Validate functionArguments parameter
    if (functionArguments) {
      for (const [n, def] of functionArguments) {
        assert(n in definition.functionParameters); // can only provide args matching parameters
        if (def !== undefined) {
          // TODO: assert that def is either native or a user definition with a common parent
        }
      }
    }

    const app = new NativeApplication(definition, functionArguments, definition.defaultSettings);

    // Create port objects for the application
    // TODO: In the future, I think these port objects will need to be created on-demand as well (for variable positional arguments)
    const inPortsOwner = {
      tag: 'napp',
      nativeApplication: app,
    };
    for (const inp of definition.inputs) {
      app.inputs.push(new InPort(this, inp.name, inp.tempo, inPortsOwner));
    }
    if (definition.output) {
      assert(!definition.outputs);
      app.output = new OutPort(this, definition.output.tempo);
    } else if (definition.outputs) {
      app.output = new Map();
      for (const n in definition.outputs) {
        app.output.set(n, new OutPort(this, definition.outputs[n].tempo));
      }
    }

    this.nativeApplications.add(app);

    // Track function arguments used by this application so that we may send in values from outer
    // scopes if needed.
    if (functionArguments) {
      for (const def of functionArguments.values()) {
        if (def) {
          this.definitionToUsingApplications.get(def).add(app);
        }
      }
    }

    // Update topological sort
    // NOTE: While the sort shouldn't really have changed, we want to make sure that all
    // applications have valid sort indexes. We could also just assign this new application
    // a valid sort index (e.g. one higher than current highest) but this is easy/clear.
    this._updateTopologicalSort();

    // Let all activations of this definition know that a native application was added
    for (const act of this.activations) {
      act.addedNativeApplication(app);
    }

    this.recursiveActivationsUpdate();

    return app;
  }

  removeNativeApplication(nativeApplication) {
    // Remove all connections first
    for (const inPort of nativeApplication.inputs) {
      this._disconnectPort(inPort);
    }
    if (nativeApplication.output instanceof Map) {
      for (const [n, outPort] of nativeApplication.output) {
        this._disconnectPort(outPort);
      }
    } else if (nativeApplication.output) {
      this._disconnectPort(nativeApplication.output);
    }

    for (const [def, apps] of definitionToUsingApplications) {
      apps.delete(nativeApplication);
    }

    this.nativeApplications.delete(app);

    // Let all activations of this definition know that a native application was removed
    for (const act of this.activations) {
      act.removedNativeApplication(app);
    }

    this.recursiveActivationsUpdate();
  }

  _disconnectPort(portObj) {
    if (portObj instanceof InPort) {
      this._removeConnection(portObj.connection);
    } else {
      assert(portObj instanceof OutPort);
      for (const cxn of portObj.connections) {
        this._removeConnection(cxn);
      }
    }
  }

  disconnectPort(portObj) {
    this._disconnectPort(portObj);

    this.recursiveActivationsUpdate();
  }

  setApplicationSettings(nativeApplication, newSettings) {
    assert(this.nativeApplications.has(nativeApplication));

    // Let all activations of this definition know that this application's settings have changed
    for (const act of this.activations) {
      act.setApplicationSettings(nativeApplication, newSettings);
    }

    this.recursiveActivationsUpdate();
{'fraction_non_alphanumeric': 0.09258988599824612, 'fraction_numerical': 0.0, 'mean_word_length': 3.107142857142857, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '233338', 'n_tokens_mistral': 3711, 'n_tokens_neox': 3589, 'n_words': 1176}