///|
pub(all) struct TokenInfo {
kind : String
span : Span
control : String?
parameter : Int?
depth : Int
} derive(Eq, Debug, ToJson)
///|
pub(all) struct ScanReport {
tokens : Array[TokenInfo]
diagnostics : Array[Diagnostic]
truncated : Bool
consumed_bytes : Int
maximum_depth : Int
} derive(Eq, Debug, ToJson)
///|
/// Bounded syntax inspection for editor integrations and troubleshooting.
pub fn scan(
input : Bytes,
max_tokens? : Int = 10000,
max_input? : Int = 32 * 1024 * 1024,
) -> ScanReport {
let tokens : Array[TokenInfo] = []
let diagnostics : Array[Diagnostic] = []
if max_tokens <= 0 || max_input <= 0 || input.length() > max_input {
return {
tokens,
diagnostics: [
{
code: "scan_limit",
severity: Error,
message: "Invalid scan limit or input exceeds scan byte limit",
span: { start: 0, end: 0, },
},
],
truncated: true,
consumed_bytes: 0,
maximum_depth: 0,
}
}
let lexer = Lexer::new(input)
let mut depth = 0
let mut maximum_depth = 0
let mut truncated = false
while lexer.pos < input.length() || lexer.binary_remaining > 0 {
if tokens.length() >= max_tokens {
truncated = true
break
}
let token = lexer.next()
let mut control : String? = None
let mut parameter : Int? = None
let kind = match token.kind {
Open => {
depth += 1
maximum_depth = maximum_depth.max(depth)
"open"
}
Close => {
if depth == 0 {
diagnostics.push({
code: "unexpected_close",
severity: Error,
message: "Unmatched closing brace",
span: token.span,
})
} else {
depth -= 1
}
"close"
}
Word(name, value) => {
control = Some(name)
parameter = value
"control"
}
Symbol(value) => {
parameter = Some(value)
"symbol"
}
Hex(value) => {
parameter = Some(value)
"hex"
}
Text => "text"
Binary => "binary"
Bad(code) => {
diagnostics.push({
code,
severity: Error,
message: "Malformed lexical token: \{Repr(token.kind)}",
span: token.span,
})
"error"
}
End => "end"
}
tokens.push({ kind, span: token.span, control, parameter, depth, })
}
if !truncated && depth > 0 {
diagnostics.push({
code: "unclosed_group",
severity: Error,
message: "Unclosed groups at end of scan",
span: { start: lexer.pos, end: lexer.pos, },
})
}
{ tokens, diagnostics, truncated, consumed_bytes: lexer.pos, maximum_depth, }
}
///|
pub fn ScanReport::to_json_text(self : ScanReport) -> String {
let tokens = self.tokens.map(token => {
Json::object({
"kind": Json::string(token.kind),
"source": span_json(token.span),
"control": optional_string_json(token.control),
"parameter": optional_int_json(token.parameter),
"depth": token.depth.to_json(),
})
})
Json::object({
"schema": Json::string("moonrtf.tokens.v1"),
"tokens": Json::array(tokens),
"diagnostics": Json::array(self.diagnostics.map(diagnostic_json)),
"truncated": Json::boolean(self.truncated),
"consumed_bytes": self.consumed_bytes.to_json(),
"maximum_depth": self.maximum_depth.to_json(),
}).stringify()
}
///|
pub fn ScanReport::to_text(self : ScanReport) -> String {
let out = StringBuilder()
for token in self.tokens {
out.write_string(
"\{token.span.start}..\{token.span.end} depth=\{token.depth} \{token.kind}",
)
if token.control is Some(name) {
out.write_string(" \\\{name}")
}
if token.parameter is Some(value) {
out.write_string(" \{value}")
}
out.write_char('\n')
}
if self.truncated {
out.write_string("Scan truncated by token limit\n")
}
out.to_string()
}