///|
pub(all) suberror ArpaError {
Invalid(String)
Budget(String)
Missing(String)
} derive(Debug)
///|
/// Limits apply before allocations or bounded inner loops.
pub(all) struct Limits {
max_order : Int32
max_records : Int32
max_text : Int32
max_tokens : Int32
max_work : Int32
} derive(Debug, Eq)
///|
pub fn Limits::default() -> Limits {
{
max_order: 8,
max_records: 200000,
max_text: 16000000,
max_tokens: 1000000,
max_work: 2000000,
}
}
///|
fn Limits::validate(self : Limits) -> Unit raise ArpaError {
if self.max_order < 1 ||
self.max_order > 8 ||
self.max_records < 1 ||
self.max_records > 1000000 ||
self.max_text < 1 ||
self.max_text > 256000000 ||
self.max_tokens < 1 ||
self.max_tokens > 10000000 ||
self.max_work < 1 ||
self.max_work > 100000000 {
raise Invalid("limits outside supported integer-safe bounds")
}
}
///|
fn finite(value : Double) -> Bool {
value == value && value != @double.infinity && value != @double.neg_infinity
}
///|
fn valid_log(value : Double) -> Bool {
finite(value) || value == @double.neg_infinity
}
///|
fn token_valid(token : String) -> Bool {
if token.is_empty() {
return false
}
for ch in token {
if ch.is_whitespace() || ch.to_int() < 32 || ch.to_int() == 127 {
return false
}
}
true
}
///|
/// Splits on Unicode whitespace without linguistic tokenization or normalization.
pub fn words(text : String) -> Array[String] {
let output = []
let mut current : Array[Char] = []
for ch in text {
if ch.is_whitespace() {
if !current.is_empty() {
output.push(String::from_array(current))
current = []
}
} else {
current.push(ch)
}
}
if !current.is_empty() {
output.push(String::from_array(current))
}
output
}
///|
fn gram_key(tokens : Array[String]) -> String {
tokens.join("\u{001f}")
}
///|
fn parse_number(text : String, line : Int32) -> Double raise ArpaError {
let value = if text == "-inf" || text == "-Infinity" {
@double.neg_infinity
} else {
@string.parse_double(text) catch {
_ => raise Invalid("line \{line}: invalid number")
}
}
if !valid_log(value) {
raise Invalid("line \{line}: non-finite number")
}
value
}
///|
fn positive_count(text : String, line : Int32) -> Int32 raise ArpaError {
let value = @string.parse_int(text) catch {
_ => raise Invalid("line \{line}: invalid count")
}
if value < 0 {
raise Invalid("line \{line}: negative count")
}
value
}
///|
pub extend ArpaError with @debug.Debug::{to_repr}
///|
pub extend Limits with @debug.Debug::{to_repr}
///|
pub extend Limits with Eq::{equal, not_equal}