Table of Contents

Class NcPatchUtil

Namespace
Hi.NcParsers.NcWriteback
Assembly
HiMech.dll

Positional text-edit primitives for NcPatchWriter (the patch-mode INcPieceConverter): anchored token edits on a verbatim BlockText draft.

This is the SoftNc dual of HardNcUtil.SetTagNumber/AddBySeq with the two known defects fixed while keeping every other semantic:

Faithfully kept from HardNc: NaN deletes the token; an absent tag is inserted at the conventional word position (ConventionalTagOrder: X,Y,Z,I,J,K,A,B,C,F,S — X scans forward for the first present successor and lands before it, other tags scan backward for the nearest present predecessor and land after it, otherwise the token is appended at the end of the code region); formatted values trim trailing zeros but keep the decimal point (10.0000 → 10.), which also guards the value against no-decimal-point integer shrinking on replay.

Tag matching mirrors the parser's lexing exactly (GrabTagValue(ref string, IEnumerable<string>, string, bool): case-insensitive, RegexFlagPrefix guard, and the full value grammar number|variable|bracket-expression) so that patch and parse agree on what a token is. A tag whose value is a variable or bracket expression is refused, never rewritten.

public static class NcPatchUtil
Inheritance
NcPatchUtil
Inherited Members

Fields

ConventionalTagOrder

Conventional Fanuc-style word order used when inserting an absent tag (mirrors HardNcUtil.AddBySeq).

public static readonly string[] ConventionalTagOrder

Field Value

string[]

Properties

OmitLeadingZero

Process-wide leading-zero style for rewritten numbers below one: false (the default) writes 0.348, true writes .348. Posts that diff the optimized program against its source compare text, so a program authored in the bare-dot style comes back changed on every word below one even when the value is identical — set this to true to keep the source's spelling.

The default is the frozen HardNc writeback convention, which the Soft-vs-Hard byte parity gates assume; changing it changes the output bytes of BOTH optimization legs. Ask ScanOmitsLeadingZero(IEnumerable<string>, Func<string, IReadOnlyList<(int Start, int Length)>>) what the source program itself does rather than guessing.

Not a knob a caller sets by hand in production: both optimization legs assign it on every output pass from the project's NcOptOption.OmitLeadingZero, so the spelling travels with the .hincproj and the same project produces the same bytes on every machine. Direct assignment is for tests and one-off tooling.

public static bool OmitLeadingZero { get; set; }

Property Value

bool

Methods

FormatTagNumber(double, string)

Formats a tag number per the HardNc writeback convention: InvariantCulture, trailing zeros after the decimal point trimmed while the dot itself is kept (-50.2500 → -50.25, 10.0000 → 10.). Shared by the patch primitives and FanucNcSentenceComposer.

OmitLeadingZero additionally drops the single zero left of the point (0.348 → .348) for values below one. Zero itself keeps its digit: 0. stays, because a lone . does not read as a number.

public static string FormatTagNumber(double value, string format)

Parameters

value double
format string

Returns

string

GetTagNumber(string, string, string, IReadOnlyList<(int Start, int Length)>, IReadOnlyList<string>)

Reads the numeric literal of the first unprotected token of tag in blockText; NaN when the tag is absent or carries a variable/bracket/keyword value. Uses exactly the value grammar of SetTagNumber(ref string, string, double, string, string, IReadOnlyList<(int Start, int Length)>, IReadOnlyList<string>, bool, bool, IReadOnlyList<string>) (TagValuePattern), so a read and a subsequent edit agree on the token. The literal is parsed raw (InvariantCulture) — no dialect integer-shrink scaling is applied, unlike the parser's SoftNcUtil.GrabDouble(…, enableIntegerShrink); splition-plan callers read decimal-pointed arc words (I/J/K/R) where the raw literal equals the parsed value.

public static double GetTagNumber(string blockText, string tag, string varPrefix = "#", IReadOnlyList<(int Start, int Length)> protectedSpans = null, IReadOnlyList<string> keywordValues = null)

Parameters

blockText string

NC block text to read; null counts as empty.

tag string

Tag to look for, e.g. “I”.

varPrefix string

Variable index prefix of the dialect.

protectedSpans IReadOnlyList<(int Start, int Length)>

Comment spans to skip.

keywordValues IReadOnlyList<string>

Dialect keywords that can stand where the tag's number would be; null for dialects without keyword values.

Returns

double

IsCarriedSection(JsonObject, string)

True when the piece's section was injected by the pipeline (AddedByKey present: a ModalCarrySyntax clone or a backfill) rather than authored from the block's own text. A carried section's token is guaranteed absent from BlockText — a patch must not anchor on it, and a strip is a no-op.

One-way signal only: an AddedBy-less section can still lack a literal token (modal-lookback syntaxes such as FeedrateSyntax re-author sections without stamping). The final authority on literal presence is the anchored text match itself.

public static bool IsCarriedSection(JsonObject json, string sectionKey)

Parameters

json JsonObject
sectionKey string

Returns

bool

ProbeTagValue(string, string, string, IReadOnlyList<(int Start, int Length)>, IReadOnlyList<string>)

Probes what the first unprotected literal token of tag carries in blockText, using exactly the value grammar (TagValuePattern: number | variable/bracket expression | dialect keyword) that SetTagNumber(ref string, string, double, string, string, IReadOnlyList<(int Start, int Length)>, IReadOnlyList<string>, bool, bool, IReadOnlyList<string>) matches against — so a probe and a subsequent edit can never disagree on token presence or editability.

public static NcTagValueKind ProbeTagValue(string blockText, string tag, string varPrefix = "#", IReadOnlyList<(int Start, int Length)> protectedSpans = null, IReadOnlyList<string> keywordValues = null)

Parameters

blockText string

NC block text to probe; null counts as empty.

tag string

Tag to look for, e.g. “F”.

varPrefix string

Variable index prefix of the dialect (Fanuc #, Heidenhain Q).

protectedSpans IReadOnlyList<(int Start, int Length)>

Comment spans to skip (see GetFanucCommentSpans(string)).

keywordValues IReadOnlyList<string>

Dialect keywords that can stand where the tag's number would be; null for dialects without keyword values.

Returns

NcTagValueKind

RemoveFlag(ref string, string, IReadOnlyList<(int Start, int Length)>)

Removes the first whole-token occurrence of flagRegex from blockText by positional splice, absorbing one adjacent separator space. Matching is case-insensitive with the RegexFlagPrefix prefix guard and a (?![0-9]) tail guard — the numbered-flag lexing: the token ends at its digits, so glued words match (G01X1.) while a longer number does not (G1 never bites G17). Dotted variants are the caller's responsibility (strip G43.4 before G43).

This is the text half of modal-token stripping (one line split into many keeps the modal G word only on the first fragment); use IsCarriedSection(JsonObject, string) first — a carried section's token is not in the source text at all, so no removal should be attempted.

public static bool RemoveFlag(ref string blockText, string flagRegex, IReadOnlyList<(int Start, int Length)> protectedSpans = null)

Parameters

blockText string
flagRegex string
protectedSpans IReadOnlyList<(int Start, int Length)>

Returns

bool

True when a token was found and removed.

RemoveFlagTrimmingTail(ref string, string, IReadOnlyList<(int Start, int Length)>)

Removes the first whole-token occurrence of flagRegex with the HardNc splition byte shape: the token span is cut out and the REMAINDER after it is TrimStart()ed — the exact splice of the oracle's RebuildFlagSyntaxToNcLine (frozen baseline, NcOptProc), which differs from RemoveFlag(ref string, string, IReadOnlyList<(int Start, int Length)>) when the token sits at the end of the code region (the oracle keeps the preceding space; the absorbing removal would eat it). The tail guard here is (?![0-9.]) — one character stronger than the oracle's guard-less regex — so a plain name never bites its dotted variant (G54 vs G54.1); the oracle avoids those bites through its flag-state gate instead.

public static bool RemoveFlagTrimmingTail(ref string blockText, string flagRegex, IReadOnlyList<(int Start, int Length)> protectedSpans = null)

Parameters

blockText string

NC block text to edit.

flagRegex string

Escaped literal flag token, e.g. “G94”.

protectedSpans IReadOnlyList<(int Start, int Length)>

Comment spans to skip.

Returns

bool

True when a token was found and removed.

RemoveFlagTrimmingTail(ref string, string, out int, IReadOnlyList<(int Start, int Length)>)

RemoveFlagTrimmingTail(ref string, string, IReadOnlyList<(int Start, int Length)>) that also reports where the token was cut: the index the trimmed remainder now starts at, which is where a parameter scope opened by that token begins (see RemoveScopedParamWordTrimmingTail(ref string, int, string, string, IReadOnlyList<(int Start, int Length)>)).

public static bool RemoveFlagTrimmingTail(ref string blockText, string flagRegex, out int removedAt, IReadOnlyList<(int Start, int Length)> protectedSpans = null)

Parameters

blockText string

NC block text to edit.

flagRegex string

Escaped literal flag token, e.g. “G94”.

removedAt int

Index the token was cut at; -1 when nothing was removed.

protectedSpans IReadOnlyList<(int Start, int Length)>

Comment spans to skip.

Returns

bool

True when a token was found and removed.

RemoveScopedParamWordTrimmingTail(ref string, int, string, string, IReadOnlyList<(int Start, int Length)>)

Removes the first paramTag word inside the parameter scope that opens at scopeStart and closes at the next G or M word — the scope a parameterized flag such as G54.1 Pn (or its G54 Pn alias) reads its parameters from: ParameterizedFlagSyntax grabs the word anywhere after the code, up to its G/M terminate words, so G54 X0. P48 selects P48 as much as G54 P48 X0. does, while a dwell's G04 P100 or a call's M98 P1000 after the token is out of scope. The word is cut with the RemoveFlagTrimmingTail(ref string, string, IReadOnlyList<(int Start, int Length)>) splice (cut the span, TrimStart() the remainder). The value grammar is the patch grammar (number, variable, bracket expression), so a bare letter is not a word. Used by the splition modal strip to take the index word of an additional work coordinate selection out together with its G54-family token — a fragment that kept the P alone re-parses it as an unconsumed word.

public static bool RemoveScopedParamWordTrimmingTail(ref string blockText, int scopeStart, string paramTag, string varPrefix, IReadOnlyList<(int Start, int Length)> protectedSpans = null)

Parameters

blockText string

NC block text to edit.

scopeStart int

Index where the scope opens — where the stripped flag token stood.

paramTag string

Parameter tag, e.g. “P”.

varPrefix string

Variable index prefix of the dialect (Fanuc #, Heidenhain Q).

protectedSpans IReadOnlyList<(int Start, int Length)>

Comment spans to skip.

Returns

bool

True when a word was found and removed.

SetTagNumber(ref string, string, double, string, string, IReadOnlyList<(int Start, int Length)>, IReadOnlyList<string>, bool, bool, IReadOnlyList<string>)

Sets, replaces, or deletes a single tag value token in blockText by positional splice.

Dual of HardNcUtil.SetTagNumber: a found tag is rewritten in place (NaN removes the whole token together with one adjacent separator space); an absent tag with a non-NaN value is inserted at its conventional word position. The original tag letter casing is preserved on replace; only the addressed token's span changes, so an edit to F can never diff anything but F.

public static NcPatchTagResult SetTagNumber(ref string blockText, string tag, double tagNumber, string tagNumberFormat, string varPrefix = "#", IReadOnlyList<(int Start, int Length)> protectedSpans = null, IReadOnlyList<string> keywordValues = null, bool absorbSeparatorSpaceOnRemove = true, bool rewriteEqualLiteral = false, IReadOnlyList<string> preFeedWordPatterns = null)

Parameters

blockText string

NC block text to edit (verbatim draft).

tag string

Tag to address, e.g. “F”, “X”.

tagNumber double

New value; NaN deletes the token.

tagNumberFormat string

Numeric format, e.g. “F2”/“F4” (see FormatTagNumber(double, string)).

varPrefix string

Variable index prefix of the dialect (Fanuc #, Heidenhain Q) — used to recognize, and refuse, variable values.

protectedSpans IReadOnlyList<(int Start, int Length)>

Comment spans to skip; matches and insertions never land inside them (see GetFanucCommentSpans(string)).

keywordValues IReadOnlyList<string>

Dialect keywords that can stand where the tag's number would be (e.g. Heidenhain MAX/AUTO after F); a keyword-valued tag is refused, never treated as absent.

absorbSeparatorSpaceOnRemove bool

Whether a NaN-value removal also absorbs one adjacent separator space (the default). Pass false for the HardNc splition-fragment byte shape: the oracle's HardNcUtil.SetTagNumber deletes with string.Replace(match, ""), leaving both separators in place — e.g. deleting the trailing F of "… Z-1.9 F4000." keeps the space, so the embedded-log note lands as "… Z-1. (src(…))" with a doubled space, which the fragment rewrite must reproduce byte-for-byte.

rewriteEqualLiteral bool

Whether to rewrite a literal that already means the requested value, re-spelling it in canonical format instead of keeping the original bytes (the default keeps them). The HardNc oracle always rewrites (HardNcUtil.SetTagNumber), so the splition fragment path passes true for byte parity — e.g. a source I0.0 under an unchanged center re-spells as I0. in the reference output, which the byte-stability shortcut would otherwise keep as I0.0.

preFeedWordPatterns IReadOnlyList<string>

Regex patterns of the dialect's bare words that precede the F word (Heidenhain klartext DR±, RL/RR/R0; see PreFeedWordPatterns). When an absent F is inserted and one of them is present, the token lands after the rightmost of those words and of the conventional predecessor words instead of after the nearest conventional predecessor alone. Null (the default) keeps the conventional placement byte-for-byte.

Returns

NcPatchTagResult