Add support for local labels
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
use compact_str::CompactString;
|
||||
|
||||
use crate::world::Board;
|
||||
|
||||
use super::{
|
||||
@@ -14,6 +16,8 @@ pub fn process_labels(board: &mut Board) {
|
||||
.collect();
|
||||
let mut registry = Registry::new();
|
||||
|
||||
resolve_local_labels(&mut stats);
|
||||
|
||||
// Replace each label with its sanitized equivalent
|
||||
for stat in stats.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) {
|
||||
let mut label_names = vec![];
|
||||
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]) {
|
||||
for stat in stats.iter_mut() {
|
||||
let mut last_name = None;
|
||||
@@ -167,4 +207,11 @@ mod test {
|
||||
process_labels(&mut 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 {
|
||||
let src = if let Some(local) = &label.local {
|
||||
local
|
||||
let mut result = CompactString::const_new("");
|
||||
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 {
|
||||
&label.name
|
||||
result.push_str(&label.name);
|
||||
};
|
||||
CompactString::new(src)
|
||||
result
|
||||
}
|
||||
|
||||
fn is_valid_label(s: &CompactString) -> bool {
|
||||
@@ -275,7 +282,8 @@ mod test {
|
||||
};
|
||||
let inputs = [
|
||||
simple("foo"),
|
||||
full("alt", "foo", ""),
|
||||
full("ns1", "foo", ""),
|
||||
full("ns2", "foo", ""),
|
||||
full("", "foo", "thisloop"),
|
||||
full("", "foo", "thatloop"),
|
||||
full("", "bar", "thisloop"),
|
||||
@@ -296,11 +304,12 @@ mod test {
|
||||
let result = results.join("\n");
|
||||
assert_snapshot!(result, @r#"
|
||||
LabelName { namespace: None, name: "foo", local: None } => foo
|
||||
LabelName { namespace: Some("alt"), name: "foo", local: None } => foo0
|
||||
LabelName { namespace: None, name: "foo", local: Some("thisloop") } => thisloop
|
||||
LabelName { namespace: None, name: "foo", local: Some("thatloop") } => thatloop
|
||||
LabelName { namespace: None, name: "bar", local: Some("thisloop") } => thisloop0
|
||||
LabelName { namespace: None, name: "bar", local: Some("thatloop") } => thatloop0
|
||||
LabelName { namespace: Some("ns1"), name: "foo", local: None } => _foo
|
||||
LabelName { namespace: Some("ns2"), name: "foo", local: None } => _foo0
|
||||
LabelName { namespace: None, name: "foo", local: Some("thisloop") } => _thisloop
|
||||
LabelName { namespace: None, name: "foo", local: Some("thatloop") } => _thatloop
|
||||
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: "bar2", local: None } => bar2
|
||||
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
|
||||
Reference in New Issue
Block a user