-
Notifications
You must be signed in to change notification settings - Fork 25
Design: Refactoring and functorization
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.
Go away from libmojave. Reimplement with stdlib.
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
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
See separate doc about "process abstraction".
TBD
TBD
The main program instantiates the functors one after the other. First at this point we define the environment module Omake_env
.