// The lowering environment: names, spans, and what is known about each of them.
///|
/// Everything the lowering needs to know that is not in the expression it is
/// looking at.
///|
/// Where a module's text came from, so that a Wax type error found later can
/// name the right file and line.
///
/// A generator that built the AST rather than parsing it passes an empty
/// `text`, and every node in that module gets a fresh synthetic location
/// instead.
pub(all) struct ModuleSource {
fname : String
text : String
src : @er.SourceId
}
///|
/// A module and where it came from: what `lower_program` compiles.
pub(all) struct Input {
module_ : @wap.Module
source : ModuleSource
}
///|
/// The line offsets of one file, for turning a span into a line and column.
priv struct LineMap {
fname : String
starts : Array[Int]
src : @er.SourceId
/// True when the module has no source text, so every node in it needs a
/// synthetic location instead of a mapped one.
text_empty : Bool
}
///|
fn LineMap::of(s : ModuleSource) -> LineMap {
let starts = [0]
let cs = s.text.to_array()
for i, c in cs {
if c == '\n' {
starts.push(i + 1)
}
}
{ fname: s.fname, starts, src: s.src, text_empty: s.text.length() == 0, }
}
///|
priv struct Lowering {
/// The module being lowered. Every table here is keyed by QUALIFIED name --
/// `"hashing.mix_i32"` -- and this says which module a bare name belongs to.
mut current : String
/// Every module in the program, so that `hashing.f` can be told from a
/// field access on a local called `hashing`.
modules : Map[String, Unit]
/// Which modules each module may name, by the importing module.
imports : Map[String, Map[String, Unit]]
/// The name each module DECLARES, by the dotted path it was found at. An
/// import names a path; everything after it names the module, and those are
/// not required to agree.
module_of_path : Map[String, String]
/// Names another module is allowed to reach.
visible : Map[String, Unit]
/// One per module, keyed by module name.
line_maps : Map[String, LineMap]
mut here : LineMap
spans : @build.Spans
/// Declared types, by their wap name.
types : Map[String, @wap.TypeDef]
/// For an enumeration, the ordinal of each of its members.
enum_members : Map[String, Map[String, Int]]
/// Which enumeration a bare member name belongs to. A name in two
/// enumerations is ambiguous and has to be qualified.
member_owner : Map[String, String]
/// Function signatures, for typing a call.
funcs : Map[String, FnSig]
/// Method signatures, keyed `type.method`.
methods : Map[String, FnSig]
/// Which types have methods of a given name, for the dispatcher.
method_owners : Map[String, Array[String]]
/// Global constants.
globals : Map[String, @wap.Type]
/// Names that came from `import was` or `import "host"` and are therefore
/// already spelled the way the emitted module spells them.
foreign : Map[String, Unit]
/// Structurally-keyed generated types: inline arrays and function types.
anon : Map[String, String]
/// Type declarations the lowering had to invent.
extra_types : Array[
@basic.Annotated[(@ast.Ident, @ast.SubType), @basic.Location],
]
/// Locals in scope, innermost last.
scopes : Array[Map[String, @wap.Type]]
/// The loops enclosing the expression being lowered, innermost last.
loops : Array[LoopFrame]
diagnostics : Array[@er.Report]
mut counter : Int
}
///|
/// A function's shape, as the lowering needs it.
priv struct FnSig {
params : Array[@wap.Type]
results : Array[@wap.Type]
/// The name the emitted module uses.
emitted : String
}
///|
/// One enclosing loop: the labels `break` and `continue` resolve to.
priv struct LoopFrame {
name : String?
/// The label a `continue` branches to -- the loop's own.
continue_label : String
/// The label a `break` branches to, if one has been generated.
mut break_label : String?
/// Whether anything actually used the break label, so an unused block
/// wrapper is not emitted.
mut break_used : Bool
/// Whether a `continue` branched to the loop's own label.
mut continue_used : Bool
}
///|
/// A fresh lowering for one module.
fn Lowering::new(module_name : String, entry~ : ModuleSource) -> Lowering {
{
current: module_name,
modules: Map([]),
imports: Map([]),
module_of_path: Map([]),
visible: Map([]),
line_maps: Map([]),
here: LineMap::of(entry),
spans: @build.Spans::new(fname=""),
types: Map([]),
enum_members: Map([]),
member_owner: Map([]),
funcs: Map([]),
methods: Map([]),
method_owners: Map([]),
globals: Map([]),
foreign: Map([]),
anon: Map([]),
extra_types: [],
scopes: [Map([])],
loops: [],
diagnostics: [],
counter: 0,
}
}
///|
/// Point the span mapper at a module's file.
fn Lowering::enter(self : Lowering, name : String) -> Unit {
self.current = name
match self.line_maps.get(name) {
Some(m) => self.here = m
None => ()
}
}
///|
/// Report a problem and keep going, so one mistake does not hide the rest.
fn Lowering::error(
self : Lowering,
msg : String,
span : @wap.Span,
help? : String,
) -> Unit {
let mut r = @er.Report::error(msg)
if @wap.has_source(span) {
r = r.with_label(@er.Label::primary(self.here.src, span))
}
match help {
Some(h) => r = r.with_help(h)
None => ()
}
self.diagnostics.push(r)
}
///|
/// A wap span as a Wax source location.
///
/// Mapping real offsets rather than handing everything a synthetic span is what
/// makes a Wax type error point into the .wap file the programmer wrote. A node
/// with no source -- one the lowering invented, or one a code generator built
/// without source text -- gets a fresh synthetic location instead, because two
/// bindings in one function may never share a span.
fn Lowering::loc(self : Lowering, span : @wap.Span) -> @basic.Location {
if !@wap.has_source(span) || self.here.text_empty {
return self.spans.fresh()
}
let start = self.position(span.start)
let end = self.position(span.start + span.len)
{ start, end, }
}
///|
fn Lowering::position(self : Lowering, offset : Int) -> @basic.Position {
// The last line start at or before `offset`.
let mut lo = 0
let starts = self.here.starts
let mut hi = starts.length() - 1
while lo < hi {
let mid = (lo + hi + 1) / 2
if starts[mid] <= offset {
lo = mid
} else {
hi = mid - 1
}
}
{ fname: self.here.fname, lnum: lo + 1, bol: starts[lo], cnum: offset, }
}
///|
/// A location that is distinct from every other, for a node the lowering
/// invented.
fn Lowering::fresh_loc(self : Lowering) -> @basic.Location {
self.spans.fresh()
}
///|
/// A generated name, unique within the module.
fn Lowering::gensym(self : Lowering, stem : String) -> String {
self.counter += 1
stem + "__" + self.counter.to_string()
}
///|
/// A bare name, resolved to the module that owns it.
///
/// A name written with a dot already says which module it means. A bare one
/// belongs to the module being lowered -- there is no implicit import, so a
/// name from elsewhere is always written `module.name`.
fn Lowering::qualify(self : Lowering, name : String) -> String {
if self.foreign.contains(name) || self.current == "" {
return name
}
if name.contains(".") {
return name
}
self.current + "." + name
}
///|
/// The name the emitted module uses for a qualified wap name.
///
/// This is the `pv_`/`pvt_` convention in `stdlib/collections/persistent_vector.wax`
/// done by the compiler instead of by hand. One wasm module comes out, with one
/// flat namespace, exactly as before -- the prefixes are just no longer typed.
fn Lowering::mangle_qualified(self : Lowering, qualified : String) -> String {
if self.foreign.contains(qualified) {
return qualified
}
qualified.replace_all(old=".", new="__")
}
///|
/// Qualify and mangle in one step, for a name as it was written.
fn Lowering::mangle(self : Lowering, name : String) -> String {
self.mangle_qualified(self.qualify(name))
}
///|
/// The emitted name of a method.
fn Lowering::mangle_method(
self : Lowering,
typ : String,
name : String,
) -> String {
self.mangle_qualified(self.qualify(typ) + "__" + name)
}
///|
/// An identifier at a span.
fn Lowering::ident(
self : Lowering,
name : String,
span : @wap.Span,
) -> @ast.Ident {
{ name, loc: self.loc(span), }
}
// ------------------------------------------------------------------ scopes
///|
/// The qualified name a `module.thing` reference means, when that is what it
/// is.
///
/// A dot is a field access on a value and a qualifier on a module, and the two
/// are told apart the only way they can be: a local wins, and what is left has
/// to be a module the current one imported.
fn Lowering::module_ref(
self : Lowering,
recv : @wap.Node,
name : String,
) -> String? {
match recv.it {
Var(m) =>
if self.local_type(m) is Some(_) {
None
} else if self.modules.contains(m) {
Some(m + "." + name)
} else {
None
}
_ => None
}
}
///|
/// Refuse a reference to something another module did not mark `pub`.
fn Lowering::check_visible(
self : Lowering,
qualified : String,
span : @wap.Span,
) -> Unit {
if !qualified.contains(".") {
return
}
let owner = qualified.split(".").collect()[0].to_owned()
if owner == self.current || !self.modules.contains(owner) {
return
}
if !self.imports.get(self.current).map_or(false, s => s.contains(owner)) {
self.error(
"`" + self.current + "` does not import `" + owner + "`",
span,
help="add `import " + owner + "` at the top of the module",
)
return
}
if !self.visible.contains(qualified) {
self.error(
"`" + qualified + "` is not `pub`",
span,
help="a declaration is private to its module unless it is written `pub`",
)
}
}
///|
/// Enter a nested scope.
fn Lowering::push_scope(self : Lowering) -> Unit {
self.scopes.push(Map([]))
}
///|
/// Leave the innermost scope.
fn Lowering::pop_scope(self : Lowering) -> Unit {
let _ = self.scopes.pop()
}
///|
/// Record a local's type.
fn Lowering::bind(self : Lowering, name : String, typ : @wap.Type) -> Unit {
self.scopes[self.scopes.length() - 1][name] = typ
}
///|
/// The declared type of a local, if it is one.
fn Lowering::local_type(self : Lowering, name : String) -> @wap.Type? {
for i = self.scopes.length() - 1; i >= 0; i = i - 1 {
match self.scopes[i].get(name) {
Some(t) => return Some(t)
None => ()
}
}
None
}