DylanSp / wheel-lang

A small toy imperative language (with some OOP features) for demonstrating and practicing language design/implementation.
MIT License
14 stars 0 forks source link

Module system #42

Closed DylanSp closed 3 years ago

DylanSp commented 4 years ago

Look at structure of evaluator.ts; may be able to make evaluateBlock(), evaluateExpr() methods on Environment, make top-level driver load stdlib modules (see #35).

DylanSp commented 4 years ago

May want to also restructure evaluator.ts even more; separate evaluation of a block/module from evaluation of a (potentially multi-module) program?

DylanSp commented 4 years ago

Proposed syntax:

module SomeModule
{
  // ...
  export someObj;
  export someFunc;
}

import SomeModule;
module Main
{
  printObj(SomeModule.someObj);
  SomeModule.someFunc();
}

Do I want to require referencing by module name? Probably better than just dumping everything into one namespace. Alternatively, require explicit import list, i.e. import someObj, someFunc from SomeModule;

DylanSp commented 4 years ago

What the evaluator can do is go through all files passed in, construct a Map<Identifier, Block> mapping module names to their list of statements, then when a module is imported evaluate the module.

DylanSp commented 4 years ago

Re: syntax, maybe put export list after closing brace?

DylanSp commented 4 years ago

Unfortunately, my current thoughts lead to side-effecting imports, but I don't really see a way around that.

DylanSp commented 4 years ago

I would probably make it a runtime error to return from the top level of a module, except for Main.

DylanSp commented 4 years ago

Alternative syntax:

module SomeModule
{
  // ...
}
export someObj, someFunc;
DylanSp commented 4 years ago

I'm thinking the only import syntax will be import someObj, someFunc from SomeModule;, which puts someObj and someFunc in the namespace without qualification; I don't really want to deal with evaluating namespaces as part of identifiers.

DylanSp commented 4 years ago

How to handle importing the same module twice, should it be run twice? EDIT: Looking at Python for comparison, no.

Maybe construct DAG of dependencies before evaluation (in a separate pass after parsing), then do a toposort and evaluate in that order?

DylanSp commented 4 years ago

Change type of evaluateModule() to (module: Module) => Either<RuntimeFailure, [Value, Map<Identifier, Value>]>. First element of tuple is what's returned from top level of module (not a runtime error, it just gets ignored in most cases), second element is a map of exported identifiers to their values.

DylanSp commented 4 years ago
type ExportResult = { exportResultKind: "noSuchModule"; }
  | { exportResultKind: "noSuchExport" }
  | { exportResultKind: "validExport"; exportedValue: Value; };

class ExportedValues {
  private values: Map<ModuleName, Option<Map<Identifier, Value>>>;

  public constructor() {
    this.values = new Map<ModuleName, Option<Map<Identifier, Value>>>();
  }

  // this gets called after parsing
  public loadModuleNames(Array<ModuleName> moduleNames) {
    moduleNames.forEach(moduleName => {
      // insert (ModuleName, None) into values
    });
  }

  // called when evaluating an import statement
  public ExportResult getExportedValue(ModuleName moduleName, Identifier exportName) {
    // if moduleName not in values, return "noSuchModule"

    // if values[moduleName] is none, evaluate module

    // if values[moduleName].value[exportName] is none, return "noSuchExport"
    return values[moduleName].value[exportName].value;
  }

  private loadModule(/* params */) {
    // ...
  }
}
DylanSp commented 4 years ago

Will this have issues with circular dependencies? Should I forbid those? (Probably)

DylanSp commented 3 years ago

Module system is implemented, tested, and has examples; only thing outstanding is to rework full_pipeline.ts and its tests.

Module stuff, for reference when writing README later: