Skip to content

Commit 4fa48a2

Browse files
committed
fix(isc): achieve 289/289 parse parity with escaped braces, primitive-aware alias_arg, and multi-line field handlers
Codemod fixes: - Remove broken "Generic field with multi-line quoted value" code that was leaking into the multi-line list handler (caused iso-ara/ua-ukr regressions) - Multi-line list handler now wraps in braces and uses convert_indented_block_until_dedent for continuation lines - Add multi-line unquoted text handler for fields like original_notes - Escape literal braces in heredoc/raw text bodies so braces in description blocks (e.g. Python code in moct-kor) don't prematurely close the block - Drop colon after separator keyword Grammar fixes: - raw_text rule handles escaped braces for description bodies - alias_arg excludes zero-width primitives so any(space+line_end) parses correctly instead of greedily consuming space as an alias name - separate_directive accepts optional separator argument DocumentBuilder: - Handle field_block in extract_metadata (braced generic fields) - unescape_braces reverses the codemod escaping for description/field_block - Extract separator from separate directive
1 parent 38711aa commit 4fa48a2

5 files changed

Lines changed: 44 additions & 40 deletions

File tree

exe/codemod-imp-to-isc

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ module Interscript
145145
elsif @scanner.scan(/,/)
146146
# Trailing comma — drop in compact rule contexts, leave elsewhere.
147147
@out << ""
148-
elsif @scanner.scan(/(before|after|not_before|not_after):/)
148+
elsif @scanner.scan(/(before|after|not_before|not_after|separator):/)
149149
# Drop the colon in modifier kwarg form.
150150
@out << "#{@scanner[1]} "
151151
elsif @scanner.scan(/[A-Za-z_][A-Za-z0-9_]*/)
@@ -301,32 +301,19 @@ module Interscript
301301
indent = @scanner[1]
302302
field = @scanner[2]
303303
item_indent = @scanner[3]
304-
@out << "\n#{indent}#{field} "
304+
@out << "\n#{indent}#{field} {"
305+
@out << "\n#{item_indent}- "
305306
text = @scanner.scan(/[^\n]+/).to_s
306-
@out << text
307-
while @scanner.check(/\n[ \t]{#{item_indent.length},}-[ \t]/)
308-
@scanner.scan(/\n[ \t]+-[ \t]+/)
309-
text = @scanner.scan(/[^\n]+/).to_s
310-
@out << " " + text
311-
end
312-
# Generic field with multi-line quoted value
313-
indent = @scanner[1]
307+
@out << escape_braces(text)
308+
convert_indented_block_until_dedent(indent)
309+
@out << "\n#{indent}}"
310+
elsif @scanner.scan(/(?:\A|\n)([ \t]+)([A-Za-z_][\w]*)[ \t]*:[ \t]*\n([ \t]+)(?![ \t]*(?:-|"|\[|\]|\|))(?![ \t]*$)/)
311+
# Multi-line unquoted text value: `field:\n text` (not list, quote, heredoc)
312+
indent = @scanner[1]
314313
field = @scanner[2]
315-
@out << "\n#{indent}#{field} \""
316-
until @scanner.eos?
317-
if @scanner.scan(/[^"\n]+/)
318-
@out << @scanner.matched
319-
elsif @scanner.scan(/"/)
320-
@out << @scanner.matched
321-
break
322-
elsif @scanner.scan(/\n[ \t]+/)
323-
@out << " "
324-
elsif @scanner.scan(/\n/)
325-
@out << " "
326-
else
327-
break
328-
end
329-
end
314+
@out << "\n#{indent}#{field} {"
315+
convert_indented_block_until_dedent(indent)
316+
@out << "\n#{indent}}"
330317
elsif @scanner.scan(/(?:\A|\n)([ \t]+)([A-Za-z_][\w]*)[ \t]*:[ \t]*\|[ \t]*\n/)
331318
# Generic field with heredoc: `field: |\n body`
332319
indent = @scanner[1]
@@ -365,13 +352,17 @@ module Interscript
365352
elsif @scanner.scan(/\n/)
366353
@out << "\n"
367354
elsif @scanner.scan(/[^\n]+/)
368-
@out << @scanner.matched
355+
@out << escape_braces(@scanner.matched)
369356
else
370357
@out << @scanner.getch
371358
end
372359
end
373360
end
374361

362+
def escape_braces(text)
363+
text.gsub("\\", "\\\\\\\\").gsub(/[{}]/) { |c| "\\#{c}" }
364+
end
365+
375366
# Notes list: each item begins with `- `. Convert each to `note "..."`.
376367
# A `- |` item is a multi-line YAML heredoc; consume subsequent indented lines.
377368
def convert_notes_list_until_dedent(indent)

lib/interscript/isc/document_builder.rb

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ def unquote(fragment)
7272
out.is_a?(Items::StringValue) ? out.value : out.to_s
7373
end
7474

75+
def unescape_braces(text)
76+
text.gsub(/\\([{}\\])/, '\1')
77+
end
78+
7579
# Apply Transform to an identifier fragment.
7680
def ident(fragment)
7781
return "" if fragment.nil?
@@ -104,18 +108,22 @@ def extract_metadata(arr)
104108
when field.key?(:relations)
105109
h[:relations] = extract_relations(field[:relations])
106110
when field.key?(:description)
107-
h[:description] = field[:description].to_s.strip
111+
h[:description] = unescape_braces(field[:description].to_s.strip)
108112
when field.key?(:field_name)
109113
# Generic field: identifier + raw value
110114
name = ident(field[:field_name]).to_sym
111-
raw = field[:field_value]
112-
val_str = case raw
113-
when Hash
114-
raw.key?(:string) ? unquote(raw) : (raw[:raw]&.to_s || "").strip
115-
when nil then ""
116-
else raw.to_s.strip
117-
end
118-
h[name] = val_str
115+
if field.key?(:field_block)
116+
h[name] = unescape_braces(field[:field_block].to_s.strip)
117+
else
118+
raw = field[:field_value]
119+
val_str = case raw
120+
when Hash
121+
raw.key?(:string) ? unquote(raw) : (raw[:raw]&.to_s || "").strip
122+
when nil then ""
123+
else raw.to_s.strip
124+
end
125+
h[name] = val_str
126+
end
119127
else
120128
# Specific named field (authority, name, system_status, etc.)
121129
field.each do |key, val|
@@ -171,7 +179,7 @@ def extract_stage_items(n)
171179
case
172180
when n[:sequence] then [{ kind: :sequence, rules: Array(n[:sequence]).map { |r| extract_rule(r) } }]
173181
when n[:parallel] then [{ kind: :parallel, rules: Array(n[:parallel]).map { |r| extract_rule(r) } }]
174-
when n[:separate] then [{ kind: :separate }]
182+
when n[:separate] then [{ kind: :separate, separator: n[:separator] ? materialize(n[:separator]) : nil }]
175183
when n[:compose] then [{ kind: :compose }]
176184
when n[:case] then [{ kind: :string_case, op: n[:case].to_s }]
177185
when n[:dep] then [{ kind: :run, dependency: ident(n[:dep]), stage: ident(n[:stage]) }]

lib/interscript/isc/grammar/concerns/items.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,10 @@ module Items
6161
end
6262

6363
# `any(identifier)` — accept a bare alias reference inside any().
64+
# Exclude zero-width primitives (space, boundary, etc.) which are
65+
# handled by `item` via `zero_width_primitive` in `item_atom`.
6466
rule(:alias_arg) do
65-
(keyword.absent? >> identifier).as(:alias_ref)
67+
(zero_width_primitive.absent? >> keyword.absent? >> identifier).as(:alias_ref)
6668
end
6769

6870
# capture(...) — wraps a sub-expression with a capture group.

lib/interscript/isc/grammar/concerns/metadata.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,10 @@ module Metadata
130130
end
131131

132132
# Raw text inside `{ ... }` — for description blocks. Consumes any
133-
# character that isn't an unescaped closing brace.
133+
# character that isn't an unescaped closing brace. Literal braces
134+
# inside the body are escaped as `\{` and `\}` by the codemod.
134135
rule(:raw_text) do
135-
(str("}").absent? >> any).repeat
136+
(str("\\{") | str("\\}") | (str("}").absent? >> any)).repeat
136137
end
137138
end
138139
end

lib/interscript/isc/grammar/concerns/stages.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ module Stages
4848
end
4949

5050
rule(:separate_directive) do
51-
str("separate").as(:separate)
51+
str("separate").as(:separate) >>
52+
(whitespace >> str("separator") >> whitespace >>
53+
item_atom.as(:separator)).maybe
5254
end
5355

5456
# `downcase`, `upcase`, `title_case` — string-case directives.

0 commit comments

Comments
 (0)