///|
/// Parse a complete Intel HEX document into normalized absolute-address memory.
pub fn parse_document(
text : String,
options? : @model.ParseOptions = @model.ParseOptions::default(),
) -> @model.FirmwareImage raise @model.FirmwareError {
let memory = @model.MemoryMap::new()
let warnings = []
let counts = Array::make(10, 0)
let mut base = 0L
let mut segmented = false
let mut entry : @model.EntryPoint? = None
let mut eof = false
let mut total = 0
let mut last_line = 0
@codec.for_each_line(text, IntelHex, options, warnings, source => {
last_line = source.location.line
total += 1
if total > options.max_records {
raise source.location.error(
ResourceLimit,
1,
"record count limit exceeded",
)
}
let record = parse_at(source.text, source.location)
let loc = { ..source.location, record_type: Some(record.kind), }
if eof {
if options.mode == Permissive && record.kind == 1 {
let @model.FirmwareError(d) = loc.error(
DuplicateRecord,
1,
"accepted duplicate EOF in permissive mode",
)
warnings.push(d)
counts[1] += 1
return
}
raise loc.error(AfterTerminator, 1, "record after EOF")
}
counts[record.kind] += 1
match record.kind {
0 => {
try {
if segmented && record.address + record.data.length() > 65536 {
// HEX86 offsets wrap within the segment; HEX386 linear addresses carry.
let first_length = 65536 - record.address
memory.insert(
base + record.address.to_int64(),
record.data[:first_length].to_owned(),
policy=options.overlap,
)
memory.insert(
base,
record.data[first_length:].to_owned(),
policy=options.overlap,
)
} else {
memory.insert(
base + record.address.to_int64(),
record.data,
policy=options.overlap,
)
}
} catch {
e => raise @codec.locate_error(e, loc)
}
if memory.payload_size() > options.max_payload {
raise loc.error(ResourceLimit, 2, "payload limit exceeded")
}
}
1 => eof = true
2 => {
base = @codec.read_address(record.data, 0, 2) * 16L
segmented = true
}
4 => {
base = @codec.read_address(record.data, 0, 2) * 65536L
segmented = false
}
3 | 5 => {
let next = if record.kind == 3 {
@model.EntryPoint::Segment(
@codec.read_address(record.data, 0, 2).to_int(),
@codec.read_address(record.data, 2, 2).to_int(),
)
} else {
@model.EntryPoint::Linear(@codec.read_address(record.data, 0, 4))
}
if entry != None {
if entry != Some(next) || options.mode == Strict {
raise loc.error(
EntryConflict,
10,
"duplicate or conflicting execution start record",
)
}
let @model.FirmwareError(d) = loc.error(
DuplicateRecord,
10,
"accepted identical execution start in permissive mode",
)
warnings.push(d)
}
entry = Some(next)
}
_ => () // Record construction has already rejected all unsupported types.
}
})
if !eof {
let loc : @codec.Location = {
format: IntelHex,
line: last_line + 1,
column_offset: 0,
record_type: None,
}
let error = loc.error(MissingTerminator, 1, "missing Intel HEX EOF record")
if options.mode == Strict || total == 0 {
raise error
}
let @model.FirmwareError(d) = error
if warnings.length() >= options.max_warnings {
raise loc.error(ResourceLimit, 1, "document exceeds warning limit")
}
warnings.push(d)
}
{
memory,
entry,
warnings,
metadata: {
source_format: IntelHex,
record_counts: counts,
header: None,
checksum_status: Verified,
},
}
}
///|
/// Verify syntax, checksums, ordering, bounds and overlaps without exposing state.
pub fn validate(
text : String,
options? : @model.ParseOptions = @model.ParseOptions::default(),
) -> Array[@model.Diagnostic] raise @model.FirmwareError {
parse_document(text, options~).warnings
}