Skip to content

Design: Refactoring and functorization

gerdstolpmann edited this page Sep 16, 2016 · 4 revisions

Currently, the module design of OMake isn't really attractive. Components are not isolated but share a lot of type definitions and helper functions. This is a burden for long-time maintenance, e.g. we couldn't easily replace the evaluater by a bytecode compiler.

The idea is to isolate the following components, so that they are either standalone modules without prerequisites, or are defined as functors that take the prerequisite components as functor arguments:

  • Fundamentals
    • data types, etc.
  • File system access
    • Omake_node, plus a module that abstracts any file system access
  • Evaluator
    • Omake_value_type, Omake_ir
    • lexer, parser, compiler
    • no built-ins
  • Executor
    • runs external commands
  • Shell
    • defines commands that can be run either internally or externally
    • abstracts over pipelines, redirections, etc.
  • Builder
    • Omake_rule, Omake_target, Omake_build
    • dependency hierarchy (rules, targets, etc.)
    • file system cache (e.g. which files have already been built)
    • runs over the deps and emits commands to execute

The pivotal module has not been mentioned yet:

  • Environment
    • contains various data for the above-mentioned components

Every component (except the fundamentals) has the environment as prerequisite.

Fundamentals

Go away from libmojave. Reimplement with stdlib.

File system

Roughly, the file operations from the Unix module where paths are represented as Omake_node.Node.t.

module type FS = sig
  val open_file : Omake_node.Node.t -> ... -> channel
  ...
end

Environmental prerequisite:

module type ENV_FS = sig
  (* Concept of current working directory, and translation string <-> node *)
  type t
  val venv_chdir            : t -> Lm_location.t -> string -> t
  val venv_chdir_dir        : t -> Lm_location.t -> Omake_node.Dir.t -> t
  val venv_chdir_tmp        : t -> Omake_node.Dir.t -> t
  val venv_dirname          : t -> Omake_node.Dir.t -> string
  val venv_nodename         : t -> Omake_node.Node.t -> string
  val venv_dir              : t -> Omake_node.Dir.t
end

Initially, we provide an implementation for the local file system (or maybe two, one for Unix, one for Windows):

module Local_FS : functor (E:ENV_FS) -> FS

Evaluator

The evaluator consists of a parser and compiler:

string/file -> (AST ->) IR -> CIR

The AST is only an internal concept and not used outside. The IR is the intermediate representation as it already exists. The CIR is the option of a compiled IR. Initially we take CIR = IR.

Another important type is the value type (Omake_value_type). Most values are just data and unproblematic. Some values, in particular closures, also refer to code. We require that value can be marshaled, and hence we cannot include code pointers. The solution is to always reference the IR term that is compiled (and require that the IR can also be marshaled), and to recompile after unmarshalling. E.g.

(* current definition: *)
ValFun of env * keyword_param_value list * Omake_ir.param list * Omake_ir.exp list * Omake_ir.export

(* new definition: *)
ValFun of env * keyword_param_value list * Omake_ir.param list * Omake_ir.block * int * Omake_ir.export

where we assume that Omake_ir.block is the IR of the smallest enclosing block that can be independently compiled, and where the int is a pointer into the compiled block (e.g. a bytecode address - we may add further infos to make this safer in case the compile scheme changes).

The module type of the evaluator is a combo of Omake_ast_ir and Omake_env, e.g.

module type EVAL = sig
  type ir
  type cir
  type env
  type value = ... (* concrete, perhaps factored out *)

  val parse_string : string -> ir
  val parse_file : string -> ir
  val compile : ir -> cir

  val eval : env -> cir -> value
  val force : env -> value -> value
end

The prerequisites are the file system (for reading files), and parts of the environment:

module type ENV_EVAL = sig
  type t
  val venv_add_var     : t -> Omake_ir.var_info -> Omake_value_type.t -> t
  val venv_add_phony   : t -> Lm_location.t -> Omake_value_type.target list -> t

  val venv_add_args      : t -> Omake_value_type.pos -> Lm_location.t -> Omake_value_type.env -> Omake_ir.param list -> Omake_value_type.t list -> Omake_value_type.keyword_param_value list -> Omake_value_type.keyword_value list -> t
  val venv_with_args     : t -> Omake_value_type.pos -> Lm_location.t -> Omake_ir.param list -> Omake_value_type.t list -> Omake_value_type.keyword_param_value list -> Omake_value_type.keyword_value list -> t

  ...
end

The functor is then:

module Eval : functor (Fs:FS) (Env:ENV_EVAL) -> EVAL

Executor

See separate doc about "process abstraction".

Shell

TBD

Builder

TBD

Main

The main program instantiates the functors one after the other. First at this point we define the environment module Omake_env.