A ‘marker’ is an abstraction over Rowan that makes working with it nicer, and also allows for some fancier parsing techniques. You can think of them as a fancy version of the checkpoints we’ve been using up to this point. Here’s a rough sketch of the API we’ll create:
impl Parser {
fn start(&mut self) -> Marker;
}
struct Marker { ... }
impl Marker {
fn complete(self, p: &mut Parser, kind: SyntaxKind) -> CompletedMarker;
}
struct CompletedMarker { ... }
impl CompletedMarker {
fn precede(self, p: &mut Parser) -> Marker;
}
Additionally, we will ensure that Marker
panics if it has been dropped without being completed.
Parser::start
creates a new marker at the parser’s current position. After creating a marker, we can bump
tokens and start new nodes as normal; once we’re done we call complete
on the marker, which wraps, in a SyntaxKind
of our choice, all the tokens and nodes added since the marker’s creation.
Markers’ property of panicking if they haven’t been completed is one of the benefits they have over plain Parser::start_node
and finish_node
calls. If you forget to call finish_node
you get a panic with a cryptic message from Rowan. If you forget to complete a marker, though, you instead get a clearer panic message with a backtrace that tells you where the marker was dropped.1
If we later decide that we really should have wrapped that node in another node, we can call precede
on the CompletedMarker
that Marker::complete
returned to us. This precede
method gives us a marker, meaning that we can once again add to the syntax tree and later call Marker::complete
.
Note that the existence of Marker
means that we won’t need Parser::start_node
or finish_node
; we can just create markers and complete them later. Similarly, CompletedMarker
supersedes Parser::checkpoint
and Parser::start_node_at
.
Let’s begin by writing an implementation of Parser::start
:
// parser.rs
impl<'l, 'input> Parser<'l, 'input> {
// snip
fn start(&mut self) -> Marker {
let pos = self.events.len();
Marker::new(pos)
}
// snip
}
Let’s define Marker
:
mod event;
mod expr;
mod marker;
mod sink;
mod source;
use crate::lexer::{Lexeme, Lexer, SyntaxKind};
use crate::syntax::SyntaxNode;
use event::Event;
use expr::expr;
use marker::Marker;
use rowan::GreenNode;
use sink::Sink;
use source::Source;
// src/parser/marker.rs
pub(super) struct Marker {
pos: usize,
}
impl Marker {
pub(super) fn new(pos: usize) -> Self {
Self { pos }
}
}
The next step is to define Marker::complete
. Remember that what this does is wrap, in a given SyntaxKind
, all the nodes and tokens pushed to the syntax tree since the marker was instantiated. We could use Vec::insert
to insert the appropriate Event::StartNode
event at the marker’s position; this would throw off the positions of other markers. The solution is to add a ‘placeholder’ variant to Event
that we push when the marker is created. When Marker::complete
is called we can then replace this placeholder with the real StartNode
event, and then finish the node:
// event.rs
#[derive(Debug, Clone, PartialEq)]
pub(super) enum Event {
StartNode { kind: SyntaxKind },
StartNodeAt { kind: SyntaxKind, checkpoint: usize },
AddToken { kind: SyntaxKind, text: SmolStr },
FinishNode,
Placeholder,
}
// parser.rs
impl<'l, 'input> Parser<'l, 'input> {
// snip
fn start(&mut self) -> Marker {
let pos = self.events.len();
self.events.push(Event::Placeholder);
Marker::new(pos)
}
// snip
}
// marker.rs
use super::event::Event;
use super::Parser;
use crate::lexer::SyntaxKind;
// snip
impl Marker {
// snip
// we aren’t making this method return a CompletedMarker just yet ...
pub(super) fn complete(self, p: &mut Parser, kind: SyntaxKind) {
let event_at_pos = &mut p.events[self.pos];
assert_eq!(*event_at_pos, Event::Placeholder);
*event_at_pos = Event::StartNode { kind };
p.events.push(Event::FinishNode);
}
}
Note how we include a sanity check to ensure the event we’re changing is a placeholder, allowing us to catch bugs earlier.
We have to tell the sink what to do if it encounters a placeholder event; this should never happen (they should all be removed by Marker::complete
):
// sink.rs
impl<'l, 'input> Sink<'l, 'input> {
// snip
pub(super) fn finish(mut self) -> GreenNode {
// snip
for event in reordered_events {
match event {
Event::StartNode { kind } => {
// snip
}
Event::StartNodeAt { .. } => unreachable!(),
Event::AddToken { kind, text } => self.token(kind, text),
Event::FinishNode => self.builder.finish_node(),
Event::Placeholder => unreachable!(),
}
self.eat_trivia();
}
self.builder.finish()
}
// snip
}
Let’s ensure that markers are never accidentally dropped without being completed:
// marker.rs
pub(super) struct Marker {
pos: usize,
completed: bool,
}
impl Marker {
pub(super) fn new(pos: usize) -> Self {
Self {
pos,
completed: false,
}
}
pub(super) fn complete(mut self, p: &mut Parser, kind: SyntaxKind) {
self.completed = true;
let event_at_pos = &mut p.events[self.pos];
assert_eq!(*event_at_pos, Event::Placeholder);
*event_at_pos = Event::StartNode { kind };
p.events.push(Event::FinishNode);
}
}
impl Drop for Marker {
fn drop(&mut self) {
if !self.completed {
panic!("Markers need to be completed");
}
}
}
It’s annoying to have to maintain that boolean though; let’s use the drop_bomb crate to handle this for us:
# Cargo.toml
[dependencies]
drop_bomb = "0.1.5"
logos = "0.11.4"
num-derive = "0.3.3"
num-traits = "0.2.14"
rowan = "0.10.0"
// marker.rs
use super::event::Event;
use super::Parser;
use crate::lexer::SyntaxKind;
use drop_bomb::DropBomb;
pub(super) struct Marker {
pos: usize,
bomb: DropBomb,
}
impl Marker {
pub(super) fn new(pos: usize) -> Self {
Self {
pos,
bomb: DropBomb::new("Markers need to be completed"),
}
}
pub(super) fn complete(mut self, p: &mut Parser, kind: SyntaxKind) {
self.bomb.defuse();
let event_at_pos = &mut p.events[self.pos];
assert_eq!(*event_at_pos, Event::Placeholder);
*event_at_pos = Event::StartNode { kind };
p.events.push(Event::FinishNode);
}
}
DropBomb
panics when it’s dropped unless its defuse
method has been called.
Let’s try out our new marker support by replacing the usage of Parser::start_node
in Parser::parse
with markers:
// parser.rs
impl<'l, 'input> Parser<'l, 'input> {
// snip
fn parse(mut self) -> Vec<Event> {
let m = self.start();
expr(&mut self);
m.complete(&mut self, SyntaxKind::Root);
self.events
}
// snip
}
$ cargo t -q
running 35 tests
...................................
test result: ok. 35 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
It’s now time to remove our usage of checkpoints and transition to using precede
on CompletedMarker
. Fortunately for us there’s only one place where we use checkpoints: expr_binding_power
. The first modification we need to make is to store a CompletedMarker
of the the left-hand side of the binary expression, so that we can later call precede
on it:
// expr.rs
fn expr_binding_power(p: &mut Parser, minimum_binding_power: u8) {
let lhs = match p.peek() {
Some(SyntaxKind::Number) | Some(SyntaxKind::Ident) => p.bump(),
Some(SyntaxKind::Minus) => {
let op = PrefixOp::Neg;
let ((), right_binding_power) = op.binding_power();
// Eat the operator’s token.
p.bump();
p.start_node_at(checkpoint, SyntaxKind::PrefixExpr);
expr_binding_power(p, right_binding_power);
p.finish_node();
}
Some(SyntaxKind::LParen) => {
p.bump();
expr_binding_power(p, 0);
assert_eq!(p.peek(), Some(SyntaxKind::RParen));
p.bump();
}
_ => return, // we’ll handle errors later.
};
// snip
}
None of the match
’s arms have the type CompletedMarker
, though. Remember that we can only obtain a CompletedMarker
by completing a marker (what a surprise!); thus, it stands to reason that we have to both create a marker and complete it in each arm of the match
. Each of the things we parse into lhs
need their own SyntaxKind
:
// lexer.rs
pub(crate) enum SyntaxKind {
// snip
Root,
BinaryExpr,
Literal,
ParenExpr,
PrefixExpr,
VariableRef,
}
We can now make lhs
a CompletedMarker
:
// expr.rs
fn expr_binding_power(p: &mut Parser, minimum_binding_power: u8) {
let lhs = match p.peek() {
Some(SyntaxKind::Number) => {
let m = p.start();
p.bump();
m.complete(p, SyntaxKind::Literal)
}
Some(SyntaxKind::Ident) => {
let m = p.start();
p.bump();
m.complete(p, SyntaxKind::VariableRef)
}
Some(SyntaxKind::Minus) => {
let m = p.start();
let op = PrefixOp::Neg;
let ((), right_binding_power) = op.binding_power();
// Eat the operator’s token.
p.bump();
expr_binding_power(p, right_binding_power);
m.complete(p, SyntaxKind::PrefixExpr)
}
Some(SyntaxKind::LParen) => {
let m = p.start();
p.bump();
expr_binding_power(p, 0);
assert_eq!(p.peek(), Some(SyntaxKind::RParen));
p.bump();
m.complete(p, SyntaxKind::ParenExpr)
}
_ => return, // we’ll handle errors later.
};
// snip
}
Let’s make use of lhs
and eradicate the last remaining reference to checkpoints in expr_binding_power
. We’ll do this by calling precede
on lhs
before recursing, giving us a marker we can complete afterwards:
fn expr_binding_power(p: &mut Parser, minimum_binding_power: u8) {
let mut lhs = match p.peek() {
// snip
};
loop {
let op = match p.peek() {
Some(SyntaxKind::Plus) => InfixOp::Add,
Some(SyntaxKind::Minus) => InfixOp::Sub,
Some(SyntaxKind::Star) => InfixOp::Mul,
Some(SyntaxKind::Slash) => InfixOp::Div,
_ => return, // we’ll handle errors later.
};
let (left_binding_power, right_binding_power) = op.binding_power();
if left_binding_power < minimum_binding_power {
return;
}
// Eat the operator’s token.
p.bump();
let m = lhs.precede(p);
expr_binding_power(p, right_binding_power);
lhs = m.complete(p, SyntaxKind::BinaryExpr);
}
}
To make sure that we keep ‘wrapping around’ the expressions we’re parsing, we assign the result of completing the marker to lhs
.
We haven’t implemented CompletedMarker
, though, so lhs
really has the type of ()
and precede
isn’t defined. First, let’s make Marker::complete
return a CompletedMarker
:
// marker.rs
impl Marker {
// snip
pub(super) fn complete(mut self, p: &mut Parser, kind: SyntaxKind) -> CompletedMarker {
self.bomb.defuse();
let event_at_pos = &mut p.events[self.pos];
assert_eq!(*event_at_pos, Event::Placeholder);
*event_at_pos = Event::StartNode { kind };
p.events.push(Event::FinishNode);
CompletedMarker { pos: self.pos }
}
}
pub(super) struct CompletedMarker {
pos: usize,
}
The next missing piece is CompletedMarker::precede
. Implementing this is tricky; let’s think about how we might approach this problem as we write out a skeleton:
impl CompletedMarker {
pub(super) fn precede(self, p: &mut Parser) -> Marker {
let new_m = p.start();
todo!();
new_m
}
}
We have the same problem as with Marker::complete
; we can’t use Vec::insert
or a similar method to shuffle around p.events
, as that would invalidate the indices into events
that other Marker
s and CompletedMarker
s use.
The solution is ingenious: we add another field to Event::StartNode
that stores a relative offset from that event forward to its parent. We modify the event at the CompletedMarker
’s position so that its forward parent is the marker returned by precede
. We can get this forward parent offset with new_m.pos - self.pos
.
↓ this distance here is the first event’s forward_parent
/- Event::StartNode { kind: Foo } <- the event at which
| ... the marker was created
| ... blah blah more events
| ...
\- Event::StartNode { kind: Placeholder } <- new_m
Here’s the implementation:
// event.rs
pub(super) enum Event {
StartNode {
kind: SyntaxKind,
forward_parent: Option<usize>,
},
StartNodeAt {
kind: SyntaxKind,
checkpoint: usize,
},
AddToken {
kind: SyntaxKind,
text: SmolStr,
},
FinishNode,
Placeholder,
}
// marker.rs
impl CompletedMarker {
pub(super) fn precede(self, p: &mut Parser) -> Marker {
let new_m = p.start();
if let Event::StartNode {
ref mut forward_parent,
..
} = p.events[self.pos]
{
*forward_parent = Some(new_m.pos - self.pos);
} else {
unreachable!();
}
new_m
}
}
Once again we panic (this time with unreachable!()
) if the event we’re trying to modify isn’t what we expect.
There are a number of references to Event::StartNode
that need to be updated due to the new field we’ve added:
impl Marker {
// snip
pub(super) fn complete(mut self, p: &mut Parser, kind: SyntaxKind) -> CompletedMarker {
self.bomb.defuse();
let event_at_pos = &mut p.events[self.pos];
assert_eq!(*event_at_pos, Event::Placeholder);
*event_at_pos = Event::StartNode {
kind,
forward_parent: None,
};
p.events.push(Event::FinishNode);
CompletedMarker { pos: self.pos }
}
}
// parser.rs
impl<'l, 'input> Parser<'l, 'input> {
// snip
fn start_node(&mut self, kind: SyntaxKind) {
self.events.push(Event::StartNode {
kind,
forward_parent: None,
});
}
// snip
}
The only other references to Event::StartNode
that need to be updated are in Sink::finish
, which we need to modify anyway to account for forward_parent
.
Remove the first section of Sink::finish
that handles checkpoints, since we aren’t using them anymore:
// sink.rs
impl<'l, 'input> Sink<'l, 'input> {
// snip
pub(super) fn finish(mut self) -> GreenNode {
for event in self.events {
match event {
Event::StartNode { kind } => {
self.builder.start_node(EldiroLanguage::kind_to_raw(kind))
}
Event::StartNodeAt { .. } => unreachable!(),
Event::AddToken { kind, text } => self.token(kind, text),
Event::FinishNode => self.builder.finish_node(),
Event::Placeholder => unreachable!(),
}
self.eat_trivia();
}
self.builder.finish()
}
// snip
}
To support forward_parent
we need to ‘pull forward’ the parent, start its node, and then start the node of the event we’re currently looking at. Keep in mind that forward_parent
is just an offset, so we need to add it to the index we’re currently up to:
impl<'l, 'input> Sink<'l, 'input> {
// snip
pub(super) fn finish(mut self) -> GreenNode {
for (idx, event) in self.events.into_iter().enumerate() {
match event {
Event::StartNode {
kind,
forward_parent,
} => {
if let Some(fp) = forward_parent {
if let Event::StartNode { kind, .. } = self.events[idx + fp] {
self.builder.start_node(EldiroLanguage::kind_to_raw(kind));
} else {
unreachable!()
}
}
self.builder.start_node(EldiroLanguage::kind_to_raw(kind));
}
Event::StartNodeAt { .. } => unreachable!(),
Event::AddToken { kind, text } => self.token(kind, text),
Event::FinishNode => self.builder.finish_node(),
Event::Placeholder => unreachable!(),
}
self.eat_trivia();
}
self.builder.finish()
}
// snip
}
We now have an error from trying to use self.events
after it was moved by into_iter
. Using references won’t work since we need to have ownership to call Sink::token
. Let’s try using raw indices into self.events
:
impl<'l, 'input> Sink<'l, 'input> {
// snip
pub(super) fn finish(mut self) -> GreenNode {
for idx in 0..self.events.len() {
match self.events[idx] {
Event::StartNode {
kind,
forward_parent,
} => {
if let Some(fp) = forward_parent {
if let Event::StartNode { kind, .. } = self.events[idx + fp] {
self.builder.start_node(EldiroLanguage::kind_to_raw(kind));
} else {
unreachable!()
}
}
self.builder.start_node(EldiroLanguage::kind_to_raw(kind));
}
Event::StartNodeAt { .. } => unreachable!(),
Event::AddToken { kind, text } => self.token(kind, text),
Event::FinishNode => self.builder.finish_node(),
Event::Placeholder => unreachable!(),
}
self.eat_trivia();
}
self.builder.finish()
}
// snip
}
We still have the same error, but now it’s situated at the match
instead of the loop. To get ownership we can replace events we have processed with the placeholder:
use super::event::Event;
use crate::lexer::{Lexeme, SyntaxKind};
use crate::syntax::EldiroLanguage;
use rowan::{GreenNode, GreenNodeBuilder, Language, SmolStr};
use std::mem;
// snip
impl<'l, 'input> Sink<'l, 'input> {
// snip
pub(super) fn finish(mut self) -> GreenNode {
for idx in 0..self.events.len() {
match mem::replace(&mut self.events[idx], Event::Placeholder) {
Event::StartNode {
kind,
forward_parent,
} => {
if let Some(fp) = forward_parent {
if let Event::StartNode { kind, .. } = self.events[idx + fp] {
self.builder.start_node(EldiroLanguage::kind_to_raw(kind));
} else {
unreachable!()
}
}
self.builder.start_node(EldiroLanguage::kind_to_raw(kind));
}
Event::StartNodeAt { .. } => unreachable!(),
Event::AddToken { kind, text } => self.token(kind, text),
Event::FinishNode => self.builder.finish_node(),
Event::Placeholder => unreachable!(),
}
self.eat_trivia();
}
self.builder.finish()
}
// snip
}
Running the tests shows that a lot of the tests have diffs from those extra wrapping nodes we had to add to lhs
. This is a great opportunity to use expect-test’s updating feature – wait, no, it isn’t! Look at the diffs; some of the tests that haven’t panicked are generating the wrong syntax trees. The ones that are panicking are doing so because the number of start_node
didn’t match the number of finish_node
calls.
Hmm, why is this happening?
Ah! When we index into self.events
to extract the forward parent, we don’t replace this event with the placeholder. This means that it is iterated over again, causing an overabundance of start_node
calls.
impl<'l, 'input> Sink<'l, 'input> {
// snip
pub(super) fn finish(mut self) -> GreenNode {
for idx in 0..self.events.len() {
match mem::replace(&mut self.events[idx], Event::Placeholder) {
Event::StartNode {
kind,
forward_parent,
} => {
if let Some(fp) = forward_parent {
if let Event::StartNode { kind, .. } =
mem::replace(&mut self.events[idx + fp], Event::Placeholder)
{
self.builder.start_node(EldiroLanguage::kind_to_raw(kind));
} else {
unreachable!()
}
}
self.builder.start_node(EldiroLanguage::kind_to_raw(kind));
}
Event::StartNodeAt { .. } => unreachable!(),
Event::AddToken { kind, text } => self.token(kind, text),
Event::FinishNode => self.builder.finish_node(),
Event::Placeholder => {}
}
self.eat_trivia();
}
self.builder.finish()
}
// snip
}
Note that we had to update the match arm that handles Event::Placeholder
to do nothing, since we now intentionally encounter placeholders (i.e. it isn’t a bug anymore if we see a placeholder).
None of our tests panic now! For the majority of them the parser outputs the correct syntax tree. However, there are a few, more complex test cases for which the new version is wrong. Take, for example, parse_left_associative_binary_expression
. We can run this test individually by running cargo t left_assoc
(or any other unique substring of the test name). Let’s manually update the expected syntax tree so that we can tell whether the parser is broken:
// expr.rs
#[cfg(test)]
mod tests {
// snip
#[test]
fn parse_left_associative_binary_expression() {
check(
"1+2+3+4",
expect![[r#"
Root@0..7
BinaryExpr@0..7
BinaryExpr@0..5
BinaryExpr@0..3
Literal@0..1
Number@0..1 "1"
Plus@1..2 "+"
Literal@2..3
Number@2..3 "2"
Plus@3..4 "+"
Literal@4..5
Number@4..5 "3"
Plus@5..6 "+"
Literal@6..7
Number@6..7 "4""#]],
);
}
// snip
}
Running the test again shows that the parser is outputting an incorrect syntax tree:
Root@0..7
BinaryExpr@0..3
Literal@0..1
Number@0..1 "1"
Plus@1..2 "+"
Literal@2..3
Number@2..3 "2"
Plus@3..4 "+"
BinaryExpr@4..7
BinaryExpr@4..5
Literal@4..5
Number@4..5 "3"
Plus@5..6 "+"
Literal@6..7
Number@6..7 "4"
The BinaryExpr
nodes aren’t being nested correctly. Let’s print self.events
at the start of Sink::finish
to see the events the parser generated:
// sink.rs
impl<'l, 'input> Sink<'l, 'input> {
// snip
pub(super) fn finish(mut self) -> GreenNode {
dbg!(&self.events);
// snip
}
// snip
}
Here’s what that gives us:
[crates/eldiro/src/parser/sink.rs:25] &self.events = [
StartNode {
kind: Root,
forward_parent: None,
},
StartNode {
kind: Literal,
forward_parent: Some(
4,
),
},
AddToken {
kind: Number,
text: "1",
},
FinishNode,
AddToken {
kind: Plus,
text: "+",
},
StartNode {
kind: BinaryExpr,
forward_parent: Some(
6,
),
},
StartNode {
kind: Literal,
forward_parent: None,
},
AddToken {
kind: Number,
text: "2",
},
FinishNode,
FinishNode,
AddToken {
kind: Plus,
text: "+",
},
StartNode {
kind: BinaryExpr,
forward_parent: Some(
6,
),
},
StartNode {
kind: Literal,
forward_parent: None,
},
AddToken {
kind: Number,
text: "3",
},
FinishNode,
FinishNode,
AddToken {
kind: Plus,
text: "+",
},
StartNode {
kind: BinaryExpr,
forward_parent: None,
},
StartNode {
kind: Literal,
forward_parent: None,
},
AddToken {
kind: Number,
text: "4",
},
FinishNode,
FinishNode,
FinishNode,
]
What a mess! I have a hunch that there’s something wrong with how we handle forward_parent
in the sink, since the nesting of BinaryExpr
s – something that happens through CompletedMarker::precede
and therefore forward_parent
– is broken. Let’s start at the first occurrence of an event with a forward parent: the second event. This node’s forward parent is 4
, meaning that it is four events down, which is a StartNode { kind: BinaryExpr }
. Curiously, this node itself has a forward parent of 6
, which also has a forward parent! We need to start_node
the last forward parent in the chain first, then the one before that and the one before that until we get to the node that started the chain. Currently in the sink we only handle the original node and its forward parent, but no levels deeper than that. Instead, we should keep checking for forward parents in a loop, accumulating them in a vector. Afterwards we can loop through this vector in reverse (to ensure the last node in the chain of forward parents is started first) and call start_node
on each one.
Enough talking, let’s write the code:
impl<'l, 'input> Sink<'l, 'input> {
// snip
pub(super) fn finish(mut self) -> GreenNode {
for idx in 0..self.events.len() {
match mem::replace(&mut self.events[idx], Event::Placeholder) {
Event::StartNode {
kind,
forward_parent,
} => {
let mut kinds = vec![kind];
let mut idx = idx;
let mut forward_parent = forward_parent;
// Walk through the forward parent of the forward parent, and the forward parent
// of that, and of that, etc. until we reach a StartNode event without a forward
// parent.
while let Some(fp) = forward_parent {
idx += fp;
forward_parent = if let Event::StartNode {
kind,
forward_parent,
} =
mem::replace(&mut self.events[idx], Event::Placeholder)
{
kinds.push(kind);
forward_parent
} else {
unreachable!()
};
}
for kind in kinds.into_iter().rev() {
self.builder.start_node(EldiroLanguage::kind_to_raw(kind));
}
}
Event::StartNodeAt { .. } => unreachable!(),
Event::AddToken { kind, text } => self.token(kind, text),
Event::FinishNode => self.builder.finish_node(),
Event::Placeholder => {}
}
self.eat_trivia();
}
self.builder.finish()
}
// snip
}
$ cargo t left_assoc
running 1 test
test parser::expr::tests::parse_left_associative_binary_expression ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 34 filtered out
At least that one test we’ve been looking at is passing. If we try running our entire suite, we’ll find that every single test failure is due to the changes we made to lhs
. It is time to update all our failing tests automatically:
$ UPDATE_EXPECT=1 cargo t -q
running 35 tests
...................................
test result: ok. 35 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
All that’s left is to delete Parser::start_node
, Parser::start_node_at
, Parser::finish_node
, Parser::checkpoint
, Event::StartNodeAt
, and the line from the match
in Sink::finish
that handles Event::StartNodeAt
.
Thanks for following this series up to here! The next part will consist of a few refactorings to keep the code clean.
It would be ideal if this was a compile time error; linear typing is the ability of a language to enforce at compile time that objects are used once, not more or less, and would come in handy here. ↩︎