///|
pub(all) enum CommentRewriteMode {
PreserveComments
DropComments
} derive(Eq, Debug)
///|
pub(all) enum BlankLineRewriteMode {
PreserveBlankLines
DropBlankLines
} derive(Eq, Debug)
///|
pub(all) enum SequenceRewriteMode {
KeepSequences
RemoveSequences
RenumberSequences(Int, Int)
} derive(Eq, Debug)
///|
pub(all) enum ChecksumRewriteMode {
RemoveChecksums
PreserveChecksumValues
RecomputeExistingChecksums
AddChecksums
} derive(Eq, Debug)
///|
pub struct ToolMapping {
from_value : Int
to_value : Int
} derive(Eq, Debug)
///|
pub fn ToolMapping::from(self : ToolMapping) -> Int {
self.from_value
}
///|
pub fn ToolMapping::to(self : ToolMapping) -> Int {
self.to_value
}
///|
pub struct DocumentRewritePlan {
comment_mode_value : CommentRewriteMode
blank_mode_value : BlankLineRewriteMode
sequence_mode_value : SequenceRewriteMode
checksum_mode_value : ChecksumRewriteMode
feed_multiplier_value : Double
spindle_multiplier_value : Double
tool_mappings_value : Array[ToolMapping]
include_final_newline_value : Bool
} derive(Eq, Debug)
///|
pub fn DocumentRewritePlan::canonical() -> DocumentRewritePlan {
{
comment_mode_value: PreserveComments,
blank_mode_value: PreserveBlankLines,
sequence_mode_value: KeepSequences,
checksum_mode_value: RecomputeExistingChecksums,
feed_multiplier_value: 1.0,
spindle_multiplier_value: 1.0,
tool_mappings_value: [],
include_final_newline_value: false,
}
}
///|
pub fn DocumentRewritePlan::with_comments(
self : DocumentRewritePlan,
mode : CommentRewriteMode,
) -> DocumentRewritePlan {
{ ..self, comment_mode_value: mode }
}
///|
pub fn DocumentRewritePlan::with_blank_lines(
self : DocumentRewritePlan,
mode : BlankLineRewriteMode,
) -> DocumentRewritePlan {
{ ..self, blank_mode_value: mode }
}
///|
pub fn DocumentRewritePlan::with_sequences(
self : DocumentRewritePlan,
mode : SequenceRewriteMode,
) -> DocumentRewritePlan {
{ ..self, sequence_mode_value: mode }
}
///|
pub fn DocumentRewritePlan::with_checksums(
self : DocumentRewritePlan,
mode : ChecksumRewriteMode,
) -> DocumentRewritePlan {
{ ..self, checksum_mode_value: mode }
}
///|
pub fn DocumentRewritePlan::with_feed_multiplier(
self : DocumentRewritePlan,
multiplier : Double,
) -> Result[DocumentRewritePlan, GCodeError] {
if multiplier <= 0.0 {
return Err(make_error(InvalidNumber, "feed multiplier must be positive", 0))
}
Ok({ ..self, feed_multiplier_value: multiplier })
}
///|
pub fn DocumentRewritePlan::with_spindle_multiplier(
self : DocumentRewritePlan,
multiplier : Double,
) -> Result[DocumentRewritePlan, GCodeError] {
if multiplier <= 0.0 {
return Err(
make_error(InvalidNumber, "spindle multiplier must be positive", 0),
)
}
Ok({ ..self, spindle_multiplier_value: multiplier })
}
///|
pub fn DocumentRewritePlan::with_tool_mapping(
self : DocumentRewritePlan,
from : Int,
to : Int,
) -> Result[DocumentRewritePlan, GCodeError] {
if from < 0 || to < 0 {
return Err(
make_error(InvalidNumber, "tool mapping must be non-negative", 0),
)
}
let mappings = self.tool_mappings_value.copy()
for mapping in mappings {
if mapping.from() == from {
return Err(
make_error(InvalidWord, "tool mapping source is duplicated", 0),
)
}
}
mappings.push({ from_value: from, to_value: to })
Ok({ ..self, tool_mappings_value: mappings })
}
///|
pub fn DocumentRewritePlan::with_final_newline(
self : DocumentRewritePlan,
enabled : Bool,
) -> DocumentRewritePlan {
{ ..self, include_final_newline_value: enabled }
}
///|
pub struct RewriteLineRecord {
source_line_value : Int
output_line_value : Int?
original_value : String
rewritten_value : String?
changes_value : Array[String]
} derive(Eq, Debug)
///|
pub fn RewriteLineRecord::source_line(self : RewriteLineRecord) -> Int {
self.source_line_value
}
///|
pub fn RewriteLineRecord::output_line(self : RewriteLineRecord) -> Int? {
self.output_line_value
}
///|
pub fn RewriteLineRecord::original(self : RewriteLineRecord) -> String {
self.original_value
}
///|
pub fn RewriteLineRecord::rewritten(self : RewriteLineRecord) -> String? {
self.rewritten_value
}
///|
pub fn RewriteLineRecord::changes(self : RewriteLineRecord) -> Array[String] {
self.changes_value.copy()
}
///|
pub fn RewriteLineRecord::changed(self : RewriteLineRecord) -> Bool {
match self.rewritten_value {
Some(value) => value != self.original_value
None => true
}
}
///|
pub struct DocumentRewriteResult {
text_value : String
records_value : Array[RewriteLineRecord]
changed_lines_value : Int
omitted_lines_value : Int
diagnostics_value : Array[Diagnostic]
} derive(Eq, Debug)
///|
pub fn DocumentRewriteResult::text(self : DocumentRewriteResult) -> String {
self.text_value
}
///|
pub fn DocumentRewriteResult::records(
self : DocumentRewriteResult,
) -> Array[RewriteLineRecord] {
self.records_value.copy()
}
///|
pub fn DocumentRewriteResult::changed_lines(
self : DocumentRewriteResult,
) -> Int {
self.changed_lines_value
}
///|
pub fn DocumentRewriteResult::omitted_lines(
self : DocumentRewriteResult,
) -> Int {
self.omitted_lines_value
}
///|
pub fn DocumentRewriteResult::diagnostics(
self : DocumentRewriteResult,
) -> Array[Diagnostic] {
self.diagnostics_value.copy()
}
///|
fn mapped_tool(mappings : Array[ToolMapping], original : Double) -> Double {
match integer_code(original) {
Some(tool) => {
for mapping in mappings {
if mapping.from() == tool {
return mapping.to().to_double()
}
}
original
}
None => original
}
}
///|
fn rewritten_word_value(word : BlockWord, plan : DocumentRewritePlan) -> Double {
if word.letter() == 70 {
word.value() * plan.feed_multiplier_value
} else if word.letter() == 83 {
word.value() * plan.spindle_multiplier_value
} else if word.letter() == 84 {
mapped_tool(plan.tool_mappings_value, word.value())
} else {
word.value()
}
}
///|
fn canonical_number(value : Double) -> String {
if value == 0.0 {
"0"
} else {
value.to_string()
}
}
///|
fn canonical_word(word : BlockWord, value : Double) -> String {
word.letter_text() + canonical_number(value)
}
///|
fn rewritten_sequence(
block : GCodeBlock,
plan : DocumentRewritePlan,
executable_ordinal : Int,
) -> Int? {
match plan.sequence_mode_value {
KeepSequences => block.sequence()
RemoveSequences => None
RenumberSequences(start, step) =>
if block.word_count() > 0 {
Some(start + executable_ordinal * step)
} else {
None
}
}
}
///|
fn render_rewrite_body(
block : GCodeBlock,
plan : DocumentRewritePlan,
sequence : Int?,
changes : Array[String],
) -> String {
let mut output = ""
match sequence {
Some(value) => output = "N" + value.to_string()
None => ()
}
for word in block.words() {
if word.letter() == 78 {
continue
}
let value = rewritten_word_value(word, plan)
if output != "" {
output = output + " "
}
output = output + canonical_word(word, value)
if value != word.value() {
if word.letter() == 70 {
changes.push("feed scaled")
} else if word.letter() == 83 {
changes.push("spindle scaled")
} else if word.letter() == 84 {
changes.push("tool mapped")
}
}
}
if plan.comment_mode_value == PreserveComments {
for comment in block.comments() {
if output != "" {
output = output + " "
}
output = output + "(" + comment + ")"
}
} else if block.comments().length() > 0 {
changes.push("comments removed")
}
output
}
///|
fn append_checksum(
body : String,
block : GCodeBlock,
mode : ChecksumRewriteMode,
changes : Array[String],
diagnostics : Array[Diagnostic],
) -> String {
match mode {
RemoveChecksums => {
if block.checksum() is Some(_) {
changes.push("checksum removed")
}
body
}
PreserveChecksumValues =>
match block.checksum() {
Some(value) => {
if checksum_for_text(body) != value {
diagnostics.push({
severity_value: Warning,
code_value: "rewrite.checksum_stale",
message_value: "preserved checksum no longer matches normalized block",
line_value: block.source_line(),
})
}
body + "*" + value.to_string()
}
None => body
}
RecomputeExistingChecksums =>
match block.checksum() {
Some(value) => {
let computed = checksum_for_text(body)
if computed != value {
changes.push("checksum recomputed")
}
body + "*" + computed.to_string()
}
None => body
}
AddChecksums =>
if body == "" {
body
} else {
if block.checksum() is None {
changes.push("checksum added")
} else if block.checksum() != Some(checksum_for_text(body)) {
changes.push("checksum recomputed")
}
body + "*" + checksum_for_text(body).to_string()
}
}
}
///|
fn sequence_change_label(
block : GCodeBlock,
sequence : Int?,
changes : Array[String],
) -> Unit {
if sequence != block.sequence() {
match (block.sequence(), sequence) {
(None, Some(_)) => changes.push("sequence added")
(Some(_), None) => changes.push("sequence removed")
(Some(_), Some(_)) => changes.push("sequence changed")
_ => ()
}
}
}
///|
fn should_omit_block(block : GCodeBlock, plan : DocumentRewritePlan) -> Bool {
let blank = block.word_count() == 0 && block.comments().length() == 0
let comment_only = block.word_count() == 0 && block.comments().length() > 0
(blank && plan.blank_mode_value == DropBlankLines) ||
(comment_only && plan.comment_mode_value == DropComments)
}
///|
pub fn rewrite_document(
document : GCodeDocument,
plan? : DocumentRewritePlan = DocumentRewritePlan::canonical(),
) -> Result[DocumentRewriteResult, GCodeError] {
match plan.sequence_mode_value {
RenumberSequences(start, step) =>
if start < 0 || step <= 0 {
return Err(
make_error(
InvalidNumber,
"renumber start must be non-negative and step positive",
0,
),
)
}
_ => ()
}
let records : Array[RewriteLineRecord] = []
let output_lines : Array[String] = []
let diagnostics : Array[Diagnostic] = []
let mut executable_ordinal = 0
let mut changed = 0
let mut omitted = 0
for block in document.blocks() {
if should_omit_block(block, plan) {
records.push({
source_line_value: block.source_line(),
output_line_value: None,
original_value: block.source(),
rewritten_value: None,
changes_value: ["line omitted"],
})
changed += 1
omitted += 1
continue
}
let changes : Array[String] = []
let sequence = rewritten_sequence(block, plan, executable_ordinal)
if block.word_count() > 0 {
executable_ordinal += 1
}
sequence_change_label(block, sequence, changes)
let body = render_rewrite_body(block, plan, sequence, changes)
let line = append_checksum(
body,
block,
plan.checksum_mode_value,
changes,
diagnostics,
)
if line != block.source() {
if changes.length() == 0 {
changes.push("lexical normalization")
}
changed += 1
}
output_lines.push(line)
records.push({
source_line_value: block.source_line(),
output_line_value: Some(output_lines.length()),
original_value: block.source(),
rewritten_value: Some(line),
changes_value: changes,
})
}
let mut text = output_lines.join("\n")
if plan.include_final_newline_value {
text = text + "\n"
}
Ok({
text_value: text,
records_value: records,
changed_lines_value: changed,
omitted_lines_value: omitted,
diagnostics_value: diagnostics,
})
}
///|
pub fn render_rewrite_report(result : DocumentRewriteResult) -> String {
let mut output = "records=" + result.records().length().to_string()
output = output + ", changed=" + result.changed_lines().to_string()
output = output + ", omitted=" + result.omitted_lines().to_string()
output = output +
", diagnostics=" +
result.diagnostics().length().to_string() +
"\n"
for record in result.records() {
if record.changed() {
output = output + "source=" + record.source_line().to_string()
match record.output_line() {
Some(line) => output = output + " output=" + line.to_string()
None => output = output + " output=omitted"
}
output = output + " changes=" + record.changes().join(",") + "\n"
}
}
output
}