Skip to content

Add parser for configurable workload#54

Open
erthalion wants to merge 3 commits intomainfrom
feature/parser
Open

Add parser for configurable workload#54
erthalion wants to merge 3 commits intomainfrom
feature/parser

Conversation

@erthalion
Copy link
Collaborator

Introduce a new configurable workload parser. The workload definition looks like this:

// Named work block
main (workers = 2, duration = 10) {
  // Anon work block with only one unit.
  // task(name) -- spawn a process with specified name
  // debug(text) -- log with DEBUG level
  // open(path) -- open file by path, create if needed and write something to it
  debug("run task stub");
  task(stub);
  debug("open file /tmp/test");
  open("/tmp/test");
} : exp {
  // If no distribution provided, do the unit only once.
  rate = 10.0;
}

Note that this commit only produces AST without JIT compiling it into anything that looks like an actual code.

Var { name: String },

/// Helper like random_path
Dynamic { name: String, args: Vec<Arg> },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh lord, self referential structs/enums, this is going to be fun.

Mandatory "if you get into trouble you should read https://rust-unofficial.github.io/too-many-lists/" PSA.

Copy link
Collaborator Author

@erthalion erthalion Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm? The page you've posted doesn't mention self referential structures at all, am I missing something?
And even if it's somehow bad in Rust, what is your suggestion? Parsing AST without self references is like trying to hammer a nail without an actual hammer -- you probably can do this, but it will likely be very awkward.

Copy link
Contributor

@Molter73 Molter73 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new script module is not actually included in the repo, so CI is not attempting to build or lint it. We should probably add mod script; somewhere in lib.rs

}

for i in pair.into_inner() {
if expr_rules.contains(&i.as_rule()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered using matches!() here? Think it would be a bit easier to read like this:

Suggested change
if expr_rules.contains(&i.as_rule()) {
if matches!(i.as_rule(), Rule::expr | Rule::machine) {

https://doc.rust-lang.org/std/macro.matches.html

) -> Vec<MachineInstruction> {
let mut instr = vec![] as Vec<MachineInstruction>;

for i in pair {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[ultra-nit] Maybe we change pair to pairs and i to pair? That might help readability a bit

Comment on lines 60 to 62
for i in pair {
let mut inner = i.into_inner();
let name = inner.next().expect("No instruction name");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] Since you don't seem to use i beyond turning it into its inner representation:

Suggested change
for i in pair {
let mut inner = i.into_inner();
let name = inner.next().expect("No instruction name");
for mut inner in pair.into_inner() {
let name = inner.next().expect("No instruction name");

Comment on lines 99 to 101
pairs: pest::iterators::Pairs<Rule>,
) -> (String, HashMap<String, String>) {
let mut work_parts = pairs.clone();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we cloning here just to make the pairs mutable? If so, consider:

Suggested change
pairs: pest::iterators::Pairs<Rule>,
) -> (String, HashMap<String, String>) {
let mut work_parts = pairs.clone();
mut work_parts: pest::iterators::Pairs<Rule>,
) -> (String, HashMap<String, String>) {

Comment on lines 107 to 118
let mut params_map: HashMap<String, String> = HashMap::new();

for param in params.into_inner() {
let mut kv = param.into_inner();
let key = kv.next().expect("No parameter name");
let value = kv.next().expect("No parameter value");

assert_eq!(key.as_rule(), Rule::ident);
assert_eq!(value.as_rule(), Rule::value);

params_map.insert(pair_to_string(key), pair_to_string(value));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider rewriting this part using iterators, this will save you the additional mutable map

Suggested change
let mut params_map: HashMap<String, String> = HashMap::new();
for param in params.into_inner() {
let mut kv = param.into_inner();
let key = kv.next().expect("No parameter name");
let value = kv.next().expect("No parameter value");
assert_eq!(key.as_rule(), Rule::ident);
assert_eq!(value.as_rule(), Rule::value);
params_map.insert(pair_to_string(key), pair_to_string(value));
}
let params_map = params
.into_inner()
.map(|param| {
let mut kv = param.into_inner();
let key = kv.next().expect("No parameter name");
let value = kv.next().expect("No parameter value");
assert_eq!(key.as_rule(), Rule::ident);
assert_eq!(value.as_rule(), Rule::value);
(pair_to_string(key), pair_to_string(value))
})
.collect::<HashMap<String, String>>();

Comment on lines 123 to 124
fn build_ast_from_function(pairs: pest::iterators::Pairs<Rule>) -> Node {
let mut func_parts = pairs.clone();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to my previous comment on cloning for mutability:

Suggested change
fn build_ast_from_function(pairs: pest::iterators::Pairs<Rule>) -> Node {
let mut func_parts = pairs.clone();
fn build_ast_from_function(mut func_parts: pest::iterators::Pairs<Rule>) -> Node {

Comment on lines 229 to 239
let mut opts: HashMap<String, String> = HashMap::new();

for p in pair.into_inner() {
if let Rule::opt = p.as_rule() {
let mut inner = p.into_inner();
let key = inner.next().expect("No dist argument key");
let value = inner.next().expect("No dist argument value");

opts.insert(pair_to_string(key), pair_to_string(value));
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider the functional approach with filter_map:

Suggested change
let mut opts: HashMap<String, String> = HashMap::new();
for p in pair.into_inner() {
if let Rule::opt = p.as_rule() {
let mut inner = p.into_inner();
let key = inner.next().expect("No dist argument key");
let value = inner.next().expect("No dist argument value");
opts.insert(pair_to_string(key), pair_to_string(value));
}
}
let opts = pair
.into_inner()
.filter_map(|p| {
if let Rule::opt = p.as_rule() {
let mut inner = p.into_inner();
let key = inner.next().expect("No dist argument key");
let value =
inner.next().expect("No dist argument value");
Some((pair_to_string(key), pair_to_string(value)))
} else {
None
}
})
.collect::<HashMap<String, String>>();

Comment on lines 229 to 248
let mut opts: HashMap<String, String> = HashMap::new();

for p in pair.into_inner() {
if let Rule::opt = p.as_rule() {
let mut inner = p.into_inner();
let key = inner.next().expect("No dist argument key");
let value = inner.next().expect("No dist argument value");

opts.insert(pair_to_string(key), pair_to_string(value));
}
}

Dist::Exp {
rate: opts
.get("rate")
.cloned()
.unwrap_or(String::from("0"))
.parse()
.unwrap(),
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can probably take my previous comment one step further and not event build the full hashmap, since it looks like you only care about the rate option, something like this should work:

            let rate = pair
                .into_inner()
                .filter_map(|p| {
                    if let Rule::opt = p.as_rule() {
                        let mut inner = p.into_inner();
                        let key = inner.next().expect("No dist argument key");
                        let key = pair_to_string(key);
                        if key == "rate" {
                            let value =
                                inner.next().expect("No dist argument value");
                            Some(pair_to_string(value).parse().unwrap())
                        } else {
                            None
                        }
                    } else {
                        None
                    }
                })
                .next()
                .unwrap_or(0.0);

            Dist::Exp { rate }

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to keep the map though, because there could be more distribution options than just rate in the future.

Comment on lines 254 to 266
fn string_from_constant(pair: pest::iterators::Pair<Rule>) -> String {
assert_eq!(pair.as_rule(), Rule::constant);

// Extract "value" rule and convert it to String
pair_to_string(first_nested_pair(pair))
}

fn string_from_ident(pair: pest::iterators::Pair<Rule>) -> String {
assert_eq!(pair.as_rule(), Rule::ident);

// Extract "name" rule and convert it to String
pair_to_string(first_nested_pair(pair))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two can probably be unified if you use the matches!() macro withing assert_eq!(), it'll look something like:

fn string_from_ident_or_const(pair: pest::iterators::Pair<Rule>) -> String {
    assert!(matches!(pair.as_rule(), Rule::ident | Rule::constant));

    pair_to_string(first_nested_pair(pair))
}

Not really sure about the function name though.

@erthalion
Copy link
Collaborator Author

The new script module is not actually included in the repo, so CI is not attempting to build or lint it. We should probably add mod script; somewhere in lib.rs

Yep, it's actually coming in the subsequent PR, but I agree -- it belongs here.

Introduce a new configurable workload parser. The workload definition
looks like this:

    // Named work block
    main (workers = 2, duration = 10) {
      // Anon work block with only one unit.
      // task(name) -- spawn a process with specified name
      // debug(text) -- log with DEBUG level
      // open(path) -- open file by path, create if needed and write something to it
      debug("run task stub");
      task(stub);
      debug("open file /tmp/test");
      open("/tmp/test");
    } : exp {
      // If no distribution provided, do the unit only once.
      rate = 10.0;
    }

Note that this commit only produces AST without JIT compiling it into anything
that looks like an actual code.
@erthalion erthalion force-pushed the feature/parser branch 2 times, most recently from 37739f9 to 5a01fb1 Compare February 27, 2026 14:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants