///|
priv struct ScanResult {
tokens : Array[RawToken]
issues : Array[Issue]
mut fatal : Bool
}
///|
fn empty_scan_result() -> ScanResult {
{ tokens: [], issues: [], fatal: false }
}
///|
fn advance_line(ch : Int, next : Int?, line : Ref[Int]) -> Bool {
if ch == 13 {
line.val = line.val + 1
match next {
Some(10) => true
_ => false
}
} else if ch == 10 {
line.val = line.val + 1
false
} else {
false
}
}
///|
fn is_newline(ch : Int) -> Bool {
ch == 10 || ch == 13
}
///|
fn scan_source(source : String) -> ScanResult {
let result = empty_scan_result()
if exceeds_utf8_byte_limit(source, MAX_INPUT_BYTES) {
result.issues.push({
code: "G001",
severity: Error,
message: "Input file exceeds 64 MiB limit.",
line: None,
})
result.fatal = true
return result
}
let line = Ref(1)
let mut pos = 0
let len = source.length()
while pos < len {
let ch = code_unit_at(source, pos)
let next = if pos + 1 < len {
Some(code_unit_at(source, pos + 1))
} else {
None
}
if is_newline(ch) {
let skip_next = advance_line(ch, next, line)
pos = pos + 1
if skip_next {
pos = pos + 1
}
continue
}
if ch == 37 {
let start_line = line.val
let start = pos
pos = pos + 1
let mut closed = false
while pos < len {
let c = code_unit_at(source, pos)
let n = if pos + 1 < len {
Some(code_unit_at(source, pos + 1))
} else {
None
}
if is_newline(c) {
let skip_next = advance_line(c, n, line)
pos = pos + 1
if skip_next {
pos = pos + 1
}
continue
}
if c == 37 {
pos = pos + 1
closed = true
break
}
pos = pos + 1
}
if !closed {
result.issues.push({
code: "G102",
severity: Error,
message: "Unterminated extended block.",
line: Some(start_line),
})
result.fatal = true
return result
}
let raw = slice_str(source, start, pos)
if exceeds_utf8_byte_limit(raw, MAX_EXTENDED_BLOCK_BYTES) {
result.issues.push({
code: "G103",
severity: Error,
message: "Command exceeds configured size limit.",
line: Some(start_line),
})
result.fatal = true
return result
}
result.tokens.push({ kind: ExtendedBlock, raw, line: start_line })
if result.tokens.length() > MAX_TOKEN_COUNT {
result.issues.push({
code: "G002",
severity: Error,
message: "Command count exceeds 1,000,000.",
line: Some(start_line),
})
result.fatal = true
return result
}
} else {
let start_line = line.val
let start = pos
let mut closed = false
while pos < len {
let c = code_unit_at(source, pos)
let n = if pos + 1 < len {
Some(code_unit_at(source, pos + 1))
} else {
None
}
if is_newline(c) {
let skip_next = advance_line(c, n, line)
pos = pos + 1
if skip_next {
pos = pos + 1
}
continue
}
if c == 42 {
pos = pos + 1
closed = true
break
}
pos = pos + 1
}
if !closed {
result.issues.push({
code: "G101",
severity: Error,
message: "Unterminated word command.",
line: Some(start_line),
})
result.fatal = true
return result
}
let raw = slice_str(source, start, pos)
if exceeds_utf8_byte_limit(raw, MAX_WORD_BYTES) {
result.issues.push({
code: "G103",
severity: Error,
message: "Command exceeds configured size limit.",
line: Some(start_line),
})
result.fatal = true
return result
}
result.tokens.push({ kind: Word, raw, line: start_line })
if result.tokens.length() > MAX_TOKEN_COUNT {
result.issues.push({
code: "G002",
severity: Error,
message: "Command count exceeds 1,000,000.",
line: Some(start_line),
})
result.fatal = true
return result
}
}
}
result
}
///|
fn strip_word_star(raw : String) -> String {
if raw.length() > 0 && code_unit_at(raw, raw.length() - 1) == 42 {
slice_str(raw, 0, raw.length() - 1)
} else {
raw
}
}
///|
fn strip_extended(raw : String) -> String {
let mut inner = if raw.length() >= 2 &&
code_unit_at(raw, 0) == 37 &&
code_unit_at(raw, raw.length() - 1) == 37 {
slice_str(raw, 1, raw.length() - 1)
} else {
raw
}
// Gerber extended commands terminate with '*' before the closing '%'.
if inner.length() > 0 && code_unit_at(inner, inner.length() - 1) == 42 {
inner = slice_str(inner, 0, inner.length() - 1)
}
inner
}