///|
/// An explicitly selected Unicode-scalar application domain, not an ABNF
/// mandated external encoding. Byte Program and ScalarProgram cannot be mixed.
pub struct ScalarProgram {
priv program : Program
}
///|
pub fn compile_scalar(
source : Bytes,
bindings~ : Array[ProseBinding] = [],
limits~ : ParseLimits = default_parse_limits(),
max_work~ : Int32 = 1000000,
) -> Result[ScalarProgram, Diagnostic] {
match compile_domain(source, limits, max_work, true, bindings, true) {
Ok(program) => Ok({ program, })
Err(d) => Err(d)
}
}
///|
pub fn ScalarProgram::rule_names(self : ScalarProgram) -> Array[String] {
self.program.rule_names()
}
///|
/// No UTF-8/UTF-16 decoding is performed: one Int is one scalar value.
pub(all) enum ScalarInputError {
InvalidScalar(Int32, Int32) // zero-based input index, invalid value
} derive(Eq, Debug)
///|
pub(all) struct ScalarMatchLimits {
max_input_scalars : Int32
max_work : Int32
max_states : Int32
max_depth : Int32
} derive(Eq, Debug)
///|
pub fn default_scalar_match_limits() -> ScalarMatchLimits {
{
max_input_scalars: 65536,
max_work: 500000,
max_states: 100000,
max_depth: 128,
}
}
///|
fn ScalarMatchLimits::matching(self : ScalarMatchLimits) -> MatchLimits {
{
max_input_bytes: self.max_input_scalars,
max_work: self.max_work,
max_states: self.max_states,
max_depth: self.max_depth,
}
}
///|
/// All report offsets are scalar indices. Validation costs one work unit per
/// scalar, before matching. This synchronous method neither copies nor retains
/// the input array and never invokes user callbacks. Exhausted validation is
/// indeterminate, not a claim that the unchecked suffix is valid.
pub fn ScalarProgram::recognize(
self : ScalarProgram,
start : String,
input : Array[Int32],
limits~ : ScalarMatchLimits = default_scalar_match_limits(),
) -> Result[MatchReport, ScalarInputError] {
let matching = limits.matching()
let empty : MatchReport = {
status: InvalidLimits,
offset: 0,
work: 0,
states: 0,
}
if !valid_match_limits(matching) {
return Ok(empty)
}
if input.length() > limits.max_input_scalars {
return Ok({ ..empty, status: BudgetExceeded("input-scalars"), })
}
for i, value in input {
if i >= limits.max_work {
return Ok({ ..empty, status: BudgetExceeded("work"), work: i, })
}
if !is_scalar(value) {
return Err(InvalidScalar(i, value))
}
}
let report = self.program.recognize_symbols(
start,
ScalarSymbols(input),
matching,
input.length(),
)
// Empty/unknown start shortcuts still expose the validation work consumed.
Ok({
..report,
work: if report.work < input.length() {
input.length()
} else {
report.work
},
})
}
///|
/// A distinct tree type prevents accidental use of byte extract() with scalar
/// indices. start/end are scalar positions; source_offset stays a grammar byte
/// position. Prose bindings are represented by lowered Choice/Sequence/Range.
pub(all) struct ScalarInputTree {
nodes : Array[InputNode]
root : Int32
} derive(Eq, Debug)
///|
fn ScalarInputTree::inner(self : ScalarInputTree) -> InputTree {
{ nodes: self.nodes, root: self.root, }
}
///|
pub fn ScalarInputTree::span(
self : ScalarInputTree,
node : Int32,
) -> (Int32, Int32)? {
self.inner().span(node)
}
///|
pub fn ScalarInputTree::rule_nodes(
self : ScalarInputTree,
name : String,
) -> Array[Int32] {
self.inner().rule_nodes(name)
}
///|
pub fn ScalarInputTree::rule_nodes_in(
self : ScalarInputTree,
name : String,
root : Int32,
) -> Array[Int32]? {
self.inner().rule_nodes_in(name, root)
}
///|
/// Copies the selected scalar span, returning None for invalid spans.
pub fn ScalarInputTree::extract(
self : ScalarInputTree,
input : Array[Int32],
node : Int32,
) -> Array[Int32]? {
match self.span(node) {
Some((start, end)) if end <= input.length() => {
let out : Array[Int32] = []
for i = start; i < end; i = i + 1 {
out.push(input[i])
}
Some(out)
}
_ => None
}
}
///|
pub(all) struct ScalarInputTreeLimits {
matching : ScalarMatchLimits
max_nodes : Int32
max_edges : Int32
} derive(Eq, Debug)
///|
pub fn default_scalar_input_tree_limits() -> ScalarInputTreeLimits {
{
matching: default_scalar_match_limits(),
max_nodes: 20000,
max_edges: 40000,
}
}
///|
pub(all) struct ScalarInputTreeReport {
status : MatchStatus
tree : ScalarInputTree?
offset : Int32
work : Int32
states : Int32
nodes : Int32
edges : Int32
} derive(Eq, Debug)
///|
pub fn ScalarProgram::parse_input(
self : ScalarProgram,
start : String,
input : Array[Int32],
limits~ : ScalarInputTreeLimits = default_scalar_input_tree_limits(),
) -> Result[ScalarInputTreeReport, ScalarInputError] {
let matching = limits.matching.matching()
let empty : ScalarInputTreeReport = {
status: InvalidLimits,
tree: None,
offset: 0,
work: 0,
states: 0,
nodes: 0,
edges: 0,
}
if !valid_match_limits(matching) ||
limits.max_nodes < 1 ||
limits.max_nodes > 100000 ||
limits.max_edges < 1 ||
limits.max_edges > 200000 {
return Ok(empty)
}
if input.length() > limits.matching.max_input_scalars {
return Ok({ ..empty, status: BudgetExceeded("input-scalars"), })
}
for i, value in input {
if i >= matching.max_work {
return Ok({ ..empty, status: BudgetExceeded("work"), work: i, })
}
if !is_scalar(value) {
return Err(InvalidScalar(i, value))
}
}
let report = self.program.parse_symbols(
start,
ScalarSymbols(input),
{ matching, max_nodes: limits.max_nodes, max_edges: limits.max_edges, },
input.length(),
)
Ok({
status: report.status,
tree: match report.tree {
None => None
Some(tree) => Some({ nodes: tree.nodes, root: tree.root, })
},
offset: report.offset,
work: if report.work < input.length() {
input.length()
} else {
report.work
},
states: report.states,
nodes: report.nodes,
edges: report.edges,
})
}
///|
pub extend ScalarInputError with Eq::{equal, not_equal}
///|
pub extend ScalarInputError with @debug.Debug::{to_repr}
///|
pub extend ScalarMatchLimits with Eq::{equal, not_equal}
///|
pub extend ScalarMatchLimits with @debug.Debug::{to_repr}
///|
pub extend ScalarInputTree with Eq::{equal, not_equal}
///|
pub extend ScalarInputTree with @debug.Debug::{to_repr}
///|
pub extend ScalarInputTreeLimits with Eq::{equal, not_equal}
///|
pub extend ScalarInputTreeLimits with @debug.Debug::{to_repr}
///|
pub extend ScalarInputTreeReport with Eq::{equal, not_equal}
///|
pub extend ScalarInputTreeReport with @debug.Debug::{to_repr}