feat(client): TagExtractor — pure regex parse/strip of §12 tags
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
38
client/scripts/text/tag_extractor.gd
Normal file
38
client/scripts/text/tag_extractor.gd
Normal file
@@ -0,0 +1,38 @@
|
||||
class_name TagExtractor
|
||||
extends RefCounted
|
||||
## Pure §12 tag parsing. Extracts [FACT:], [ADJUST_DISPOSITION:], [MOVE:] tags
|
||||
## and returns the prose with all tags stripped. It never applies anything and
|
||||
## never validates a move (that needs NPC context from /npc/speak — a later
|
||||
## plan). Its outputs are exactly the inputs CanonLog's mutators consume.
|
||||
|
||||
static var _fact_re: RegEx = RegEx.create_from_string("\\[FACT:\\s*(.*?)\\]")
|
||||
static var _dispo_re: RegEx = RegEx.create_from_string("\\[ADJUST_DISPOSITION:\\s*([+-]?\\d+)\\]")
|
||||
static var _move_re: RegEx = RegEx.create_from_string("\\[MOVE:\\s*(\\w+)\\(([^)]*)\\)\\]")
|
||||
static var _any_re: RegEx = RegEx.create_from_string("\\[[A-Z_]+:[^\\]]*\\]")
|
||||
|
||||
|
||||
static func extract(prose: String) -> Dictionary:
|
||||
var facts: Array = []
|
||||
for m in _fact_re.search_all(prose):
|
||||
facts.append(m.get_string(1).strip_edges())
|
||||
|
||||
var dispositions: Array = []
|
||||
for m in _dispo_re.search_all(prose):
|
||||
dispositions.append(int(m.get_string(1)))
|
||||
|
||||
var moves: Array = []
|
||||
for m in _move_re.search_all(prose):
|
||||
var raw_args := m.get_string(2).strip_edges()
|
||||
var args: Array = []
|
||||
if raw_args != "":
|
||||
for a in raw_args.split(","):
|
||||
args.append(a.strip_edges())
|
||||
moves.append({"name": m.get_string(1), "args": args})
|
||||
|
||||
var clean := _any_re.sub(prose, "", true)
|
||||
# Collapse whitespace left where inline tags were removed.
|
||||
while clean.contains(" "):
|
||||
clean = clean.replace(" ", " ")
|
||||
clean = clean.strip_edges()
|
||||
|
||||
return {"clean_text": clean, "facts": facts, "dispositions": dispositions, "moves": moves}
|
||||
1
client/scripts/text/tag_extractor.gd.uid
Normal file
1
client/scripts/text/tag_extractor.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cib5yw5k0kip4
|
||||
40
client/tests/unit/test_tag_extractor.gd
Normal file
40
client/tests/unit/test_tag_extractor.gd
Normal file
@@ -0,0 +1,40 @@
|
||||
extends "res://addons/gut/test.gd"
|
||||
|
||||
const TagExtractor = preload("res://scripts/text/tag_extractor.gd")
|
||||
|
||||
|
||||
func test_fact_extracted_and_stripped():
|
||||
var r := TagExtractor.extract("A muddy road. [FACT: the eastern bridge is out]")
|
||||
assert_eq(r["facts"], ["the eastern bridge is out"])
|
||||
assert_eq(r["clean_text"], "A muddy road.")
|
||||
|
||||
|
||||
func test_positive_and_negative_disposition():
|
||||
assert_eq(TagExtractor.extract("\"Aye.\" [ADJUST_DISPOSITION: +5]")["dispositions"], [5])
|
||||
assert_eq(TagExtractor.extract("[ADJUST_DISPOSITION: -10]")["dispositions"], [-10])
|
||||
|
||||
|
||||
func test_move_parsed_with_args():
|
||||
var r := TagExtractor.extract("[MOVE: offer_quest(14)]")
|
||||
assert_eq(r["moves"][0]["name"], "offer_quest")
|
||||
assert_eq(r["moves"][0]["args"], ["14"])
|
||||
|
||||
|
||||
func test_move_with_no_args():
|
||||
var r := TagExtractor.extract("[MOVE: refuse()]")
|
||||
assert_eq(r["moves"][0]["name"], "refuse")
|
||||
assert_eq(r["moves"][0]["args"], [])
|
||||
|
||||
|
||||
func test_multiple_facts_collected():
|
||||
var r := TagExtractor.extract("Hi [FACT: a] mid [FACT: b] end")
|
||||
assert_eq(r["facts"], ["a", "b"])
|
||||
assert_eq(r["clean_text"], "Hi mid end")
|
||||
|
||||
|
||||
func test_no_tags_is_clean_passthrough():
|
||||
var r := TagExtractor.extract("no tags here")
|
||||
assert_eq(r["facts"], [])
|
||||
assert_eq(r["dispositions"], [])
|
||||
assert_eq(r["moves"], [])
|
||||
assert_eq(r["clean_text"], "no tags here")
|
||||
1
client/tests/unit/test_tag_extractor.gd.uid
Normal file
1
client/tests/unit/test_tag_extractor.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://piviwupch8rt
|
||||
Reference in New Issue
Block a user