///|
fn expression_grammar() -> @grammata.Grammar {
let builder = @grammata.GrammarBuilder::new()
let digit = builder.ascii_range('0', '9', name="digit")
let operator_rule = builder.dictionary(["+", "-", "*", "/"], name="operator")
let open = builder.literal("(")
let close = builder.literal(")")
let expression_ref = builder.reference("expression")
let binary = builder.sequence([
open, expression_ref, operator_rule, expression_ref, close,
])
let expression = builder.choice(
[
@grammata.alternative(digit, weight=5, label="digit"),
@grammata.alternative(binary, weight=3, label="binary"),
],
name="expression",
)
builder.set_start(expression).build().unwrap()
}
///|
fn contains_division_by_zero(text : String) -> Bool {
let data = @utf8.encode(text)
if data.length() < 2 {
return false
}
for index = 0; index + 1 < data.length(); index = index + 1 {
if data[index].to_int() == '/'.to_int() &&
data[index + 1].to_int() == '0'.to_int() {
return true
}
}
false
}
///|
fn expression_features(text : String) -> Array[@grammata.Feature] {
let data = @utf8.encode(text)
let mut operators = 0
let mut maximum_depth = 0
let mut depth = 0
for byte in data {
let value = byte.to_int()
if value == '('.to_int() {
depth += 1
maximum_depth = maximum_depth.max(depth)
} else if value == ')'.to_int() {
depth = (depth - 1).max(0)
} else if value == '+'.to_int() ||
value == '-'.to_int() ||
value == '*'.to_int() ||
value == '/'.to_int() {
operators += 1
}
}
[
@grammata.feature("parser-stage", "complete"),
@grammata.feature("operator-band", (operators / 2).to_string()),
@grammata.feature("depth", maximum_depth.to_string()),
]
}
///|
fn expression_target() -> @grammata.Target {
@grammata.Target::new("expression-demo", input => {
let text = @utf8.decode(input) catch {
_ => return @grammata.rejected("invalid-utf8")
}
let features = expression_features(text)
if contains_division_by_zero(text) {
@grammata.failed("arithmetic", "division-by-zero", features~)
} else {
@grammata.accepted(features~, result_digest=input.length().to_string())
}
})
}
///|
fn run_expression_demo(executions : Int) -> @grammata.CampaignResult {
let config : @grammata.CampaignConfig = {
..@grammata.CampaignConfig::default(seed=20260918L),
executions,
initial_inputs: executions.min(12),
}
@grammata.run_campaign(expression_grammar(), expression_target(), config).unwrap()
}
///|
fn main {
let result = run_expression_demo(1000)
println(result.markdown_report(title="Expression Grammar Fuzzing Demo"))
if !result.failures.is_empty() {
println("First replay artifact:")
println(result.failures[0].encode_json())
}
}