Add support for local labels
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
use compact_str::CompactString;
|
||||||
|
|
||||||
use crate::world::Board;
|
use crate::world::Board;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@@ -14,6 +16,8 @@ pub fn process_labels(board: &mut Board) {
|
|||||||
.collect();
|
.collect();
|
||||||
let mut registry = Registry::new();
|
let mut registry = Registry::new();
|
||||||
|
|
||||||
|
resolve_local_labels(&mut stats);
|
||||||
|
|
||||||
// Replace each label with its sanitized equivalent
|
// Replace each label with its sanitized equivalent
|
||||||
for stat in stats.iter_mut() {
|
for stat in stats.iter_mut() {
|
||||||
for chunk in stat.iter_mut() {
|
for chunk in stat.iter_mut() {
|
||||||
@@ -51,6 +55,41 @@ pub fn process_labels(board: &mut Board) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve ".local" labels to "name.local" form.
|
||||||
|
fn resolve_local_labels(stats: &mut [ParsedStat]) {
|
||||||
|
for stat in stats.iter_mut() {
|
||||||
|
let mut section = CompactString::const_new("");
|
||||||
|
for chunk in stat.iter_mut() {
|
||||||
|
match chunk {
|
||||||
|
Chunk::Label {
|
||||||
|
is_ref,
|
||||||
|
is_anon: false,
|
||||||
|
name: label,
|
||||||
|
} => {
|
||||||
|
// Handle cases where either one of (section name, label) is missing.
|
||||||
|
// If both are present ("name.local") then the label is already fully resolved
|
||||||
|
// and there is nothing to do.
|
||||||
|
if label.name.is_empty() {
|
||||||
|
// Expand :.local to :name.local
|
||||||
|
assert!(label.local.is_some());
|
||||||
|
label.name = section.clone()
|
||||||
|
} else if label.local.is_none() {
|
||||||
|
// Interpret label :name as start of new section.
|
||||||
|
// Only label definitions do this; label references have no effect.
|
||||||
|
if !*is_ref {
|
||||||
|
section = label.name.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simultaneously:
|
||||||
|
/// 1. Assign names to anonymous labels.
|
||||||
|
/// 2. Resolve anonymous backward references to their label names.
|
||||||
fn anonymous_forward_pass(stats: &mut [ParsedStat], registry: &mut Registry) {
|
fn anonymous_forward_pass(stats: &mut [ParsedStat], registry: &mut Registry) {
|
||||||
let mut label_names = vec![];
|
let mut label_names = vec![];
|
||||||
for stat in stats.iter_mut() {
|
for stat in stats.iter_mut() {
|
||||||
@@ -84,6 +123,7 @@ fn anonymous_forward_pass(stats: &mut [ParsedStat], registry: &mut Registry) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve anonymous forward references to their label names.
|
||||||
fn anonymous_backward_pass(stats: &mut [ParsedStat]) {
|
fn anonymous_backward_pass(stats: &mut [ParsedStat]) {
|
||||||
for stat in stats.iter_mut() {
|
for stat in stats.iter_mut() {
|
||||||
let mut last_name = None;
|
let mut last_name = None;
|
||||||
@@ -167,4 +207,11 @@ mod test {
|
|||||||
process_labels(&mut board);
|
process_labels(&mut board);
|
||||||
assert_snapshot!(board_to_text(board));
|
assert_snapshot!(board_to_text(board));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_local_labels() {
|
||||||
|
let mut board = board_from_text("tests/labels/local.txt");
|
||||||
|
process_labels(&mut board);
|
||||||
|
assert_snapshot!(board_to_text(board));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-10
@@ -115,12 +115,19 @@ impl Registry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn preferred_name(label: &LabelName) -> CompactString {
|
fn preferred_name(label: &LabelName) -> CompactString {
|
||||||
let src = if let Some(local) = &label.local {
|
let mut result = CompactString::const_new("");
|
||||||
local
|
if label.namespace.is_some() || label.local.is_some() {
|
||||||
|
// Prevent namespaces and local labels from starting with a digit.
|
||||||
|
// This ensures stuff like `#take gems 100.99orless` won't compile
|
||||||
|
// to `#take gems 10099orless` (would parse incorrectly).
|
||||||
|
result.push('_');
|
||||||
|
}
|
||||||
|
if let Some(local) = &label.local {
|
||||||
|
result.push_str(local);
|
||||||
} else {
|
} else {
|
||||||
&label.name
|
result.push_str(&label.name);
|
||||||
};
|
};
|
||||||
CompactString::new(src)
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_valid_label(s: &CompactString) -> bool {
|
fn is_valid_label(s: &CompactString) -> bool {
|
||||||
@@ -275,7 +282,8 @@ mod test {
|
|||||||
};
|
};
|
||||||
let inputs = [
|
let inputs = [
|
||||||
simple("foo"),
|
simple("foo"),
|
||||||
full("alt", "foo", ""),
|
full("ns1", "foo", ""),
|
||||||
|
full("ns2", "foo", ""),
|
||||||
full("", "foo", "thisloop"),
|
full("", "foo", "thisloop"),
|
||||||
full("", "foo", "thatloop"),
|
full("", "foo", "thatloop"),
|
||||||
full("", "bar", "thisloop"),
|
full("", "bar", "thisloop"),
|
||||||
@@ -296,11 +304,12 @@ mod test {
|
|||||||
let result = results.join("\n");
|
let result = results.join("\n");
|
||||||
assert_snapshot!(result, @r#"
|
assert_snapshot!(result, @r#"
|
||||||
LabelName { namespace: None, name: "foo", local: None } => foo
|
LabelName { namespace: None, name: "foo", local: None } => foo
|
||||||
LabelName { namespace: Some("alt"), name: "foo", local: None } => foo0
|
LabelName { namespace: Some("ns1"), name: "foo", local: None } => _foo
|
||||||
LabelName { namespace: None, name: "foo", local: Some("thisloop") } => thisloop
|
LabelName { namespace: Some("ns2"), name: "foo", local: None } => _foo0
|
||||||
LabelName { namespace: None, name: "foo", local: Some("thatloop") } => thatloop
|
LabelName { namespace: None, name: "foo", local: Some("thisloop") } => _thisloop
|
||||||
LabelName { namespace: None, name: "bar", local: Some("thisloop") } => thisloop0
|
LabelName { namespace: None, name: "foo", local: Some("thatloop") } => _thatloop
|
||||||
LabelName { namespace: None, name: "bar", local: Some("thatloop") } => thatloop0
|
LabelName { namespace: None, name: "bar", local: Some("thisloop") } => _thisloop0
|
||||||
|
LabelName { namespace: None, name: "bar", local: Some("thatloop") } => _thatloop0
|
||||||
LabelName { namespace: None, name: "bar1", local: None } => bar1
|
LabelName { namespace: None, name: "bar1", local: None } => bar1
|
||||||
LabelName { namespace: None, name: "bar2", local: None } => bar2
|
LabelName { namespace: None, name: "bar2", local: None } => bar2
|
||||||
LabelName { namespace: None, name: "bar123", local: None } => bar_
|
LabelName { namespace: None, name: "bar123", local: None } => bar_
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
source: src/labels/process.rs
|
||||||
|
expression: board_to_text(board)
|
||||||
|
---
|
||||||
|
'Slightly contrived example: two routines
|
||||||
|
'using the same local label ".loop"
|
||||||
|
:run_w
|
||||||
|
#walk w
|
||||||
|
:_loop
|
||||||
|
#try w stop
|
||||||
|
#_loop
|
||||||
|
'
|
||||||
|
:run_e
|
||||||
|
#walk e
|
||||||
|
:_loop0
|
||||||
|
#try e stop
|
||||||
|
#_loop0
|
||||||
|
'
|
||||||
|
:stop
|
||||||
|
#walk i
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
'Slightly contrived example: two routines
|
||||||
|
'using the same local label ".loop"
|
||||||
|
:run_w
|
||||||
|
#walk w
|
||||||
|
:.loop
|
||||||
|
#try w stop
|
||||||
|
#.loop
|
||||||
|
'
|
||||||
|
:run_e
|
||||||
|
#walk e
|
||||||
|
:.loop
|
||||||
|
#try e stop
|
||||||
|
#.loop
|
||||||
|
'
|
||||||
|
:stop
|
||||||
|
#walk i
|
||||||
Reference in New Issue
Block a user