// The reader's state: where it is, and what has gone wrong.
//
// There is no environment in the usual sense. Was is a notation for Wax rather
// than a language above it, so nothing here resolves a name, infers a type or
// invents an instruction -- the reader knows only how to turn a group into the
// node it names.
///|
priv struct Reader {
fname : String
line_starts : Array[Int]
src : @er.SourceId
diagnostics : Array[@er.Report]
/// The type names this file declares.
///
/// The reader needs them for exactly one thing: `ints[0]` is a one-element
/// array literal when `ints` is a type and an index when it is a value, and
/// unlike Wax -- which puts the type inside the brackets, `[ints| 0]` -- was
/// has nowhere to put a marker that shrubbery keeps. A trailing comma does
/// not survive the reader, and `|` is the alternatives marker.
type_names : Map[String, Unit]
mut counter : Int
}
///|
fn Reader::new(fname~ : String, text~ : String, src~ : @er.SourceId) -> Reader {
let line_starts = [0]
let cs = text.to_array()
for i, c in cs {
if c == '\n' {
line_starts.push(i + 1)
}
}
{ fname, line_starts, src, diagnostics: [], type_names: Map([]), counter: 0, }
}
///|
/// Report and keep reading, so one mistake does not hide the rest.
fn Reader::error(
self : Reader,
msg : String,
span : Span,
help? : String,
) -> Unit {
let mut r = @er.Report::error(msg)
if has_source(span) {
r = r.with_label(@er.Label::primary(self.src, span))
}
match help {
Some(h) => r = r.with_help(h)
None => ()
}
self.diagnostics.push(r)
}
///|
/// A was span as a Wax source location, so that a Wax type error lands on the
/// .was line that caused it.
fn Reader::loc(self : Reader, span : Span) -> @basic.Location {
if !has_source(span) {
return self.fresh_loc()
}
{
start: self.position(span.start),
end: self.position(span.start + span.len),
}
}
///|
fn Reader::position(self : Reader, offset : Int) -> @basic.Position {
let mut lo = 0
let mut hi = self.line_starts.length() - 1
while lo < hi {
let mid = (lo + hi + 1) / 2
if self.line_starts[mid] <= offset {
lo = mid
} else {
hi = mid - 1
}
}
{ fname: self.fname, lnum: lo + 1, bol: self.line_starts[lo], cnum: offset, }
}
///|
/// A location distinct from every other, for a node with no source.
fn Reader::fresh_loc(self : Reader) -> @basic.Location {
let k = self.counter
self.counter = k + 1
let start : @basic.Position = {
fname: "",
lnum: k + 1,
bol: k,
cnum: k,
}
{ start, end: { ..start, cnum: k + 1, }, }
}
///|
fn Reader::ident(self : Reader, name : String, span : Span) -> @ast.Ident {
{ name, loc: self.loc(span), }
}
///|
fn Reader::instr(
self : Reader,
d : @ast.InstrDesc[@basic.Location],
span : Span,
) -> @ast.Instr[@basic.Location] {
@ast.build(d, self.loc(span))
}
///|
/// A block of instructions with the span they came from.
fn Reader::blk(
self : Reader,
body : Array[@ast.Instr[@basic.Location]],
span : Span,
) -> @basic.Annotated[Array[@ast.Instr[@basic.Location]], @basic.Location] {
{ desc: body, info: self.loc(span), }
}
///|
/// A cursor over the terms of one group.
priv struct Cursor {
ts : Array[@sh.Node]
mut i : Int
r : Reader
}
///|
fn Cursor::new(ts : Array[@sh.Node], r : Reader) -> Cursor {
{ ts, i: 0, r, }
}
///|
fn Cursor::done(self : Cursor) -> Bool {
self.i >= self.ts.length()
}
///|
fn Cursor::peek(self : Cursor) -> @sh.Node? {
if self.done() {
None
} else {
Some(self.ts[self.i])
}
}
///|
fn Cursor::next(self : Cursor) -> @sh.Node? {
match self.peek() {
Some(n) => {
self.i += 1
Some(n)
}
None => None
}
}
///|
fn Cursor::here(self : Cursor) -> Span {
if self.ts.length() == 0 {
nowhere
} else if self.done() {
node_span(self.ts[self.ts.length() - 1])
} else {
node_span(self.ts[self.i])
}
}
///|
/// Consume a term when it is this operator.
fn Cursor::eat_op(self : Cursor, name : String) -> Bool {
match self.peek() {
Some(n) if is_op(n, name) => {
self.i += 1
true
}
_ => false
}
}
///|
/// Consume a term when it is this identifier.
fn Cursor::eat_id(self : Cursor, name : String) -> Bool {
match self.peek() {
Some(n) if is_id(n, name) => {
self.i += 1
true
}
_ => false
}
}