~stacyharper/bonsai

680c389785b7a1f63043d0497fcd65fec8a15598 — Stacy Harper 4 months ago b24ad85
Use FSM terminology
15 files changed, 678 insertions(+), 681 deletions(-)

M README.md
R bonsai/{tree/json_errors.ha => json_errors.ha}
R bonsai/{tree/json_load.ha => json_load.ha}
R bonsai/{tree/json_test.ha => json_test.ha}
R bonsai/{tree/json_write.ha => json_write.ha}
A bonsai/state.ha
R bonsai/{tree/voyager_context.ha => state_context.ha}
R bonsai/{tree/test_voyager.ha => test_voyager.ha}
D bonsai/tree/voyager.ha
R bonsai/{tree/type.ha => type.ha}
M cmd/bonsaictl/main.ha
M cmd/bonsaid/cmd.ha
M cmd/bonsaid/main.ha
M cmd/bonsaid/socket.ha
R fixtures/{basic_tree.json => basic_transitions.json}
M README.md => README.md +37 -40
@@ 2,22 2,19 @@

### Definitions

Bonsai allow you to define "branches" that a "voyager" will travel. This is a
client / server software and you interract with the voyager using a command
line client. Every branch got child branches attached to it. There is 4 kind
of branches and the voyager will go to the branch childs if they match the
branch condition :

- event branch: The received event name match the branch event name
- context branch: The voyager context match the branch context
- exec branch: The branch command is run and succeed
- delay branch: The voyager wait for the duration. No relevant event is
received while waiting.

After you sent to the voyager an event or changed their context, the voyager will
follow every available branch they can, respecting the order, and recursively. If
there is no more available branch in front of the voyager, they returns to the
initial position.
Bonsai is a Finite State Machine structured as a tree. It has been designed to trigger
commands when successive events and/or a precise context is accepted.

There is 4 kind of transition with specific acceptation rules :

- event transition: The received event name match the transition one
- context transition: The state context match the transition one
- exec transition: The transition command is run and succeed
- delay transition: The state wait for the delay transition duration. No other
accepted event is received while waiting

The state will transition following every accepted transition. If there is no more
available transition, the state go back to the initial position.

### Reference



@@ 48,7 45,7 @@ initial position.
  {
    "type": "…",
    …,
    "branches": [
    "transitions": [
      {
      },


@@ 60,27 57,28 @@ initial position.

#### Events arent recursives

The voyager will not follow recursively every event branch that match. Only
the first one is triggered then only execs, contexts and delays are recursive.
Event input can trigger one or zero event transition. The state will then follow
every accepted execs, contexts or delays transitions.

#### Soft locks

It is easy to soft lock you voyager if you dont handle every situation. The
It is easy to soft lock the state machine if you dont plan every situation. The
following example show two trees. The first one can cause soft locks :


```json
[
  {
    "type": "event",
    "event_name": "foo",
    "branches": [
    "transitions": [
      {
        "type": "event",
        "event_name": "bar",
        "branches": [
        "transitions": [
          {
            "type": "exec",
            "mmmmmnd": [
            "command": [
              "yataaa"
            ]
          }


@@ 91,20 89,19 @@ following example show two trees. The first one can cause soft locks :
]
```

If you receive "foo" but never "bar", your voyager is soft locked. The voyager
will ignore irrelevant events so you cant go to a brother event branch of
"foo". Maybe this is intended, maybe not. Here how to handle this :
If the state machine receive "foo" but no "bar" it stay locked at this position.
Maybe this is intended, maybe not. Here how to handle this :

```json
[
  {
    "type": "event",
    "event_name": "foo",
    "branches": [
    "transitions": [
      {
        "type": "event",
        "event_name": "bar",
        "branches": [
        "transitions": [
          {
            "type": "exec",
            "command": [


@@ 122,24 119,24 @@ will ignore irrelevant events so you cant go to a brother event branch of
]
```

Now if the voyager receive "foo" but no "bar" in less than 0.5 seconds, it
Now if the state machine receive "foo" but no "bar" in less than 0.5 seconds, it
return to initial position. This is how [sxmo] use bonsai to support complex
keybinds.

[sxmo]: https://sxmo.org

#### Exec branches
#### Exec transitions

Exec branch commands are run synchronously. If you dont care about the command
Exec transition commands are run synchronously. If you dont care about the command
status or use the command as final trigger and dont want to hold the bonsai daemon,
you should use `["setsid", "-f", …]`.

#### Hole branches
#### Hole transitions

Context branches can be used as "holes" if you dont specify context values
cause it will always match. This is particulary usefull just after exec branch
that can fail. In that example, if the command fails the voyager go back to
initial position. You can also add childs to the context branch to exec another
Context transitions can be used as "holes" if you dont specify context values
cause it will always match. This is particulary usefull just after exec transition
that can fail. In that example, if the command fails the state machine go back to
initial position. You can also add childs to the context transition to exec another
command :

```json


@@ 147,7 144,7 @@ command :
  {
    "type": "event",
    "event_name": "foo",
    "branches": [
    "transitions": [
      {
        "type": "exec",
        "command": [


@@ 167,9 164,9 @@ command :

#### How delay are cancelled

If the voyager is waiting while receiving an event or a context update, it
check if there is a matching event or context branch available in front of
them. Otherwise it continue to wait.
If the state machine is waiting while receiving an event or a context update, it
check if there is a accepting event or context transition available.
Otherwise it continue to wait.

## Usage


R bonsai/tree/json_errors.ha => bonsai/json_errors.ha +17 -17
@@ 1,8 1,8 @@

export type first_isnt_array = !void;
export type unsupported_type = !void;
export type branch_isnt_object = !void;
export type missing_branch_type = !void;
export type transition_isnt_object = !void;
export type missing_transition_type = !void;
export type missing_contexts = !void;
export type missing_event_name = !void;
export type missing_delay_duration = !void;


@@ 10,7 10,7 @@ export type missing_command = !void;
export type type_isnt_str = !void;
export type command_isnt_array = !void;
export type contexts_isnt_object = !void;
export type branches_isnt_array = !void;
export type transitions_isnt_array = !void;
export type delay_duration_isnt_int = !void;
export type context_value_isnt_str = !void;
export type event_name_isnt_str = !void;


@@ 19,8 19,8 @@ export type command_argument_isnt_str = !void;
export type bad_syntax = !(
	unsupported_type |
	first_isnt_array |
	branch_isnt_object |
	missing_branch_type |
	transition_isnt_object |
	missing_transition_type |
	missing_contexts |
	missing_event_name |
	missing_delay_duration |


@@ 29,7 29,7 @@ export type bad_syntax = !(
	command_isnt_array |
	contexts_isnt_object |
	context_value_isnt_str |
	branches_isnt_array |
	transitions_isnt_array |
	delay_duration_isnt_int |
	event_name_isnt_str |
	command_argument_isnt_str |


@@ 42,29 42,29 @@ export fn strerror(err: error) str = {
	case first_isnt_array =>
		return "first node must be an array";
	case unsupported_type =>
		return "unsupported branch type";
	case branch_isnt_object =>
		return "a branch must be a json object";
	case missing_branch_type =>
		return "a branch specify its type";
		return "unsupported transition type";
	case transition_isnt_object =>
		return "a transition must be a json object";
	case missing_transition_type =>
		return "a transition specify its type";
	case type_isnt_str =>
		return "a type must be a string";
	case command_isnt_array =>
		return "command must be an array";
	case missing_contexts =>
		return "a context branch must have contexts";
		return "a context transition must have contexts";
	case missing_event_name =>
		return "a event branch must have event_name";
		return "a event transition must have event_name";
	case missing_delay_duration =>
		return "a delay branch must have delay_duration";
		return "a delay transition must have delay_duration";
	case missing_command =>
		return "a exec branch must have command";
		return "a exec transition must have command";
	case contexts_isnt_object =>
		return "contexts must be an object";
	case context_value_isnt_str =>
		return "context value must be a string";
	case branches_isnt_array =>
		return "branches must be an array";
	case transitions_isnt_array =>
		return "transitions must be an array";
	case delay_duration_isnt_int =>
		return "delay_duration must be a integer";
	case event_name_isnt_str =>

R bonsai/tree/json_load.ha => bonsai/json_load.ha +51 -51
@@ 5,30 5,30 @@ use log;
use math;
use strings;

fn load_branch_childs(object: json::object) ([]branch | error) = {
	let branches = match(json::get(&object, "branches")) {
fn load_object_transitions(object: json::object) ([]transition | error) = {
	let transitions = match(json::get(&object, "transitions")) {
	case let value: *json::value =>
		yield value;
	case void =>
		return [];
	};

	let branches = match(*branches) {
	let transitions = match(*transitions) {
	case let value: []json::value =>
		yield value;
	case let value: json::value =>
		return branches_isnt_array;
		return transitions_isnt_array;
	};

	match (load_tree_branches(branches)) {
	case let branches: []branch =>
		return branches;
	match (load_objects(transitions)) {
	case let transitions: []transition =>
		return transitions;
	case let err: error =>
		return err;
	};
};

fn load_tree_branch_event(object: json::object) (branch | error) = {
fn load_object_event(object: json::object) (transition | error) = {
	let event_name = match(json::get(&object, "event_name")) {
	case let value: *json::value =>
		yield value;


@@ 43,20 43,20 @@ fn load_tree_branch_event(object: json::object) (branch | error) = {
		return event_name_isnt_str;
	};

	let branches = match (load_branch_childs(object)) {
	case let branches: []branch =>
		yield branches;
	let transitions = match (load_object_transitions(object)) {
	case let transitions: []transition =>
		yield transitions;
	case let err: error =>
		return err;
	};

	return branch_event {
	return transition_event {
		event_name = event_name,
		branches = branches,
		transitions = transitions,
	};
};

fn load_tree_branch_delay(object: json::object) (branch | error) = {
fn load_object_delay(object: json::object) (transition | error) = {
	let delay_duration = match(json::get(&object, "delay_duration")) {
	case let value: *json::value =>
		yield value;


@@ 71,23 71,23 @@ fn load_tree_branch_delay(object: json::object) (branch | error) = {
		return delay_duration_isnt_int;
	};

	let branches = match (load_branch_childs(object)) {
	case let branches: []branch =>
		yield branches;
	let transitions = match (load_object_transitions(object)) {
	case let transitions: []transition =>
		yield transitions;
	case let err: error =>
		return err;
	};

	return branch_delay {
	return transition_delay {
		delay_duration = delay_duration,
		branches = branches,
		transitions = transitions,
	};
};

fn load_tree_branch_exec(object: json::object) (branch | error) = {
	let branches = match (load_branch_childs(object)) {
	case let branches: []branch =>
		yield branches;
fn load_object_exec(object: json::object) (transition | error) = {
	let transitions = match (load_object_transitions(object)) {
	case let transitions: []transition =>
		yield transitions;
	case let err: error =>
		return err;
	};


@@ 116,16 116,16 @@ fn load_tree_branch_exec(object: json::object) (branch | error) = {
		};
	};

	return branch_exec {
		branches = branches,
	return transition_exec {
		transitions = transitions,
		command = command,
	};
};

fn load_tree_branch_context(object: json::object) (branch | error) = {
	let branches = match (load_branch_childs(object)) {
	case let branches: []branch =>
		yield branches;
fn load_object_context(object: json::object) (transition | error) = {
	let transitions = match (load_object_transitions(object)) {
	case let transitions: []transition =>
		yield transitions;
	case let err: error =>
		return err;
	};


@@ 134,8 134,8 @@ fn load_tree_branch_context(object: json::object) (branch | error) = {
	case let value: *json::value =>
		yield value;
	case void =>
		return branch_context {
			branches = branches,
		return transition_context {
			transitions = transitions,
			...
		};
	};


@@ 169,65 169,65 @@ fn load_tree_branch_context(object: json::object) (branch | error) = {
		};
	};

	return branch_context {
		branches = branches,
	return transition_context {
		transitions = transitions,
		context_elements = context_elements,
		...
	};
};

fn load_tree_branch(object: json::object) (branch | error) = {
	let branch_type = match(json::get(&object, "type")) {
fn load_object(object: json::object) (transition | error) = {
	let transition_type = match(json::get(&object, "type")) {
	case let value: *json::value =>
		yield value;
	case void =>
		return missing_branch_type;
		return missing_transition_type;
	};

	let branch_type = match(*branch_type) {
	let transition_type = match(*transition_type) {
	case let value: str =>
		yield value;
	case let value: json::value =>
		return type_isnt_str;
	};

	switch (branch_type) {
	switch (transition_type) {
	case "context" =>
		return load_tree_branch_context(object);
		return load_object_context(object);
	case "event" =>
		return load_tree_branch_event(object);
		return load_object_event(object);
	case "delay" =>
		return load_tree_branch_delay(object);
		return load_object_delay(object);
	case "exec" =>
		return load_tree_branch_exec(object);
		return load_object_exec(object);
	case =>
		return unsupported_type;
	};
};

fn load_tree_branches(values: []json::value) ([]branch | error) = {
	let branches: []branch = [];
fn load_objects(values: []json::value) ([]transition | error) = {
	let transitions: []transition = [];

	for (let i = 0z; i < len(values); i += 1) {
		let object = match (values[i]) {
		case let object: json::object =>
			yield object;
		case let value: json::value =>
			return branch_isnt_object;
			return transition_isnt_object;
		};

		match (load_tree_branch(object)) {
		case let branch: branch =>
			append(branches, branch);
		match (load_object(object)) {
		case let transition: transition =>
			append(transitions, transition);
		case let err: error =>
			return err;
		};
	};

	return branches;
	return transitions;
};

export fn load_tree(source: io::handle) ([]branch | json::error | error) = {
export fn load_transitions(source: io::handle) ([]transition | json::error | error) = {
	let value: json::value = match(json::load(source)) {
	case let value: json::value =>
		yield value;


@@ 243,5 243,5 @@ export fn load_tree(source: io::handle) ([]branch | json::error | error) = {
		return first_isnt_array;
	};

	return load_tree_branches(value);
	return load_objects(value);
};

R bonsai/tree/json_test.ha => bonsai/json_test.ha +38 -38
@@ 7,87 7,87 @@ use io;
use bufio;
use strings;

@test fn load_basic_tree_nodes() void = {
	const branches = match(load_tree(os::open("fixtures/basic_tree.json")!)) {
	case let branches: []branch =>
		yield branches;
@test fn load_basic_transitions() void = {
	const transitions = match(load_transitions(os::open("fixtures/basic_transitions.json")!)) {
	case let transitions: []transition =>
		yield transitions;
	case let err: json::error =>
		fmt::fatal("Failed to parse the json:", json::strerror(err));
	case let err: error =>
		fmt::fatal("Failed to parse the branches:", strerror(err));
		fmt::fatal("Failed to parse the transitions:", strerror(err));
	};

	assert(len(branches) == 2);
	assert(len(transitions) == 2);

	assert(branches[0] is branch_context);
	let subject = branches[0] as branch_context;
	assert(len(subject.branches) == 2);
	assert(transitions[0] is transition_context);
	let subject = transitions[0] as transition_context;
	assert(len(subject.transitions) == 2);
	assert(len(subject.context_elements) == 2);
	assert(subject.context_elements[0].0 == "my_env");
	assert(subject.context_elements[0].1 == "true");
	assert(subject.context_elements[1].1 == "also present");
	assert(subject.context_elements[1].0 == "another_env");

	assert(subject.branches[0] is branch_event);
	let subject = subject.branches[0] as branch_event;
	assert(len(subject.branches) == 3);
	assert(subject.transitions[0] is transition_event);
	let subject = subject.transitions[0] as transition_event;
	assert(len(subject.transitions) == 3);
	assert(subject.event_name == "typical_event");

	assert(subject.branches[1] is branch_context);
	let subject2 = subject.branches[1] as branch_context;
	assert(len(subject2.branches) == 0);
	assert(subject.transitions[1] is transition_context);
	let subject2 = subject.transitions[1] as transition_context;
	assert(len(subject2.transitions) == 0);

	assert(subject.branches[0] is branch_delay);
	let subject = subject.branches[0] as branch_delay;
	assert(len(subject.branches) == 1);
	assert(subject.transitions[0] is transition_delay);
	let subject = subject.transitions[0] as transition_delay;
	assert(len(subject.transitions) == 1);
	assert(subject.delay_duration == 2000);

	assert(subject.branches[0] is branch_exec);
	let subject = subject.branches[0] as branch_exec;
	assert(len(subject.branches) == 0);
	assert(subject.transitions[0] is transition_exec);
	let subject = subject.transitions[0] as transition_exec;
	assert(len(subject.transitions) == 0);
	assert(len(subject.command) == 2z);
	assert(subject.command[0] == "echo");
	assert(subject.command[1] == "boooh");

	let subject = branches[0] as branch_context;
	assert(subject.branches[1] is branch_event);
	let subject = subject.branches[1] as branch_event;
	assert(len(subject.branches) == 0);
	let subject = transitions[0] as transition_context;
	assert(subject.transitions[1] is transition_event);
	let subject = subject.transitions[1] as transition_event;
	assert(len(subject.transitions) == 0);
	assert(subject.event_name == "typical_other_event");

	assert(branches[1] is branch_context);
	let subject = branches[1] as branch_context;
	assert(len(subject.branches) == 1);
	assert(transitions[1] is transition_context);
	let subject = transitions[1] as transition_context;
	assert(len(subject.transitions) == 1);
	assert(len(subject.context_elements) == 1);
	assert(subject.context_elements[0].0 == "my_env");
	assert(subject.context_elements[0].1 == "false");

	assert(subject.branches[0] is branch_event);
	let subject = subject.branches[0] as branch_event;
	assert(len(subject.branches) == 1);
	assert(subject.transitions[0] is transition_event);
	let subject = subject.transitions[0] as transition_event;
	assert(len(subject.transitions) == 1);
	assert(subject.event_name == "typical_event");

	assert(subject.branches[0] is branch_exec);
	let subject = subject.branches[0] as branch_exec;
	assert(len(subject.branches) == 0);
	assert(subject.transitions[0] is transition_exec);
	let subject = subject.transitions[0] as transition_exec;
	assert(len(subject.transitions) == 0);
	assert(len(subject.command) == 2z);
	assert(subject.command[0] == "echo");
	assert(subject.command[1] == "baaar");
};

@test fn load_write_tree_nodes() void = {
	const branches = load_tree(os::open("fixtures/basic_tree.json")!)!;
@test fn write_basic_transitions() void = {
	const transitions = load_transitions(os::open("fixtures/basic_transitions.json")!)!;

	let pipes = unix::pipe()!;

	write_tree(pipes.1, branches);
	write_transitions(pipes.1, transitions);
	io::close(pipes.1)!;

	let generated_value: json::value = json::load(pipes.0)!;
	defer json::finish(generated_value);

	let from_file_value: json::value = json::load(
		os::open("fixtures/basic_tree.json")!
		os::open("fixtures/basic_transitions.json")!
	)!;
	defer json::finish(from_file_value);


R bonsai/tree/json_write.ha => bonsai/json_write.ha +31 -31
@@ 2,78 2,78 @@
use fmt;
use io;

export fn write_tree(
export fn write_transitions(
	handle: io::handle,
	branches: []branch
	transitions: []transition
) void = {
	fmt::fprint(handle, "[")!;
	for (let i = 0z; i < len(branches); i += 1) {
	for (let i = 0z; i < len(transitions); i += 1) {
		fmt::fprint(handle, "{")!;
		match(branches[i]) {
		case let branch: branch_context =>
		match(transitions[i]) {
		case let transition: transition_context =>
			fmt::fprint(handle, "\"type\":\"context\"")!;
			if (0 < len(branch.context_elements)) {
			if (0 < len(transition.context_elements)) {
				fmt::fprint(handle, ",\"contexts\":{")!;
				for (let y = 0z; y < len(branch.context_elements); y += 1) {
				for (let y = 0z; y < len(transition.context_elements); y += 1) {
					fmt::fprintf(
						handle,
						"\"{}\":\"{}\"",
						branch.context_elements[y].0,
						branch.context_elements[y].1
						transition.context_elements[y].0,
						transition.context_elements[y].1
					)!;
					if (y < len(branch.context_elements) - 1) {
					if (y < len(transition.context_elements) - 1) {
						fmt::fprint(handle, ",")!;
					};
				};
				fmt::fprint(handle, "}")!;
			};
			if (0 < len(branch.branches)) {
				fmt::fprint(handle, ",\"branches\":")!;
				write_tree(handle, branch.branches);
			if (0 < len(transition.transitions)) {
				fmt::fprint(handle, ",\"transitions\":")!;
				write_transitions(handle, transition.transitions);
			};
		case let branch: branch_event =>
		case let transition: transition_event =>
			fmt::fprintf(
				handle,
				"\"type\":\"event\",\"event_name\":\"{}\"",
				branch.event_name
				transition.event_name
			)!;
			if (0 < len(branch.branches)) {
				fmt::fprint(handle, ",\"branches\":")!;
				write_tree(handle, branch.branches);
			if (0 < len(transition.transitions)) {
				fmt::fprint(handle, ",\"transitions\":")!;
				write_transitions(handle, transition.transitions);
			};
		case let branch: branch_delay =>
		case let transition: transition_delay =>
			fmt::fprintf(
				handle,
				"\"type\":\"delay\",\"delay_duration\":{}",
				branch.delay_duration
				transition.delay_duration
			)!;
			if (0 < len(branch.branches)) {
				fmt::fprint(handle, ",\"branches\":")!;
				write_tree(handle, branch.branches);
			if (0 < len(transition.transitions)) {
				fmt::fprint(handle, ",\"transitions\":")!;
				write_transitions(handle, transition.transitions);
			};
		case let branch: branch_exec =>
		case let transition: transition_exec =>
			fmt::fprintf(
				handle,
				"\"type\":\"exec\",\"command\":[",
			)!;
			for (let y = 0z; y < len(branch.command); y += 1) {
			for (let y = 0z; y < len(transition.command); y += 1) {
				fmt::fprintf(
					handle,
					"\"{}\"",
					branch.command[y],
					transition.command[y],
				)!;
				if (y < len(branch.command) - 1) {
				if (y < len(transition.command) - 1) {
					fmt::fprint(handle, ",")!;
				};
			};
			fmt::fprint(handle, "]")!;
			if (0 < len(branch.branches)) {
				fmt::fprint(handle, ",\"branches\":")!;
				write_tree(handle, branch.branches);
			if (0 < len(transition.transitions)) {
				fmt::fprint(handle, ",\"transitions\":")!;
				write_transitions(handle, transition.transitions);
			};
		};
		fmt::fprint(handle, "}")!;
		if (i < len(branches) - 1) {
		if (i < len(transitions) - 1) {
			fmt::fprint(handle, ",")!;
		};
	};

A bonsai/state.ha => bonsai/state.ha +239 -0
@@ 0,0 1,239 @@
use fmt;
use log;
use strings;
use os::exec;
use os;
use time;
use io;
use unix::signal;
use unix;
use fmt;
use bufio;
use encoding::json;
use errors;

export fn state_forward(
	state: *state_machine,
	act: action
) void = {
	sync_back_wait_child(state, false);

	match (act) {
	case let act: action_context_changed =>
		return state_forward_context_changed(state, act);
	case let act: action_event_received =>
		state_forward_event_received(state, act);
	case let act: action =>
		fmt::fatal("Not supported yet");
	};
};

fn state_forward_event_received(
	state: *state_machine,
	act: action_event_received
) void = {
	let target_event = act.event_name;
	let matched_event = false;

	for (let i = 0z; i < len(state.available_transitions); i += 1) {
		let event_transition = match(state.available_transitions[i]) {
		case let event_transition: transition_event =>
			yield event_transition;
		case transition =>
			continue;
		};

		if (event_transition.event_name == target_event) {
			if (sync_back_wait_child(state, true)) {
				return state_forward_event_received(state, act);
			};
			matched_event = true;
			state.available_transitions = event_transition.transitions;
			break;
		};
	};

	if (!matched_event) {
		return;
	};

	state_forward_available_transitions(state);
	trigger_delay_transition(state);
};

fn state_forward_context_changed(
	state: *state_machine,
	act: action_context_changed
) void = {
	state_update_context(state, act.context_elements);
	state_forward_available_transitions(state);
	trigger_delay_transition(state);
};

export fn state_forward_available_transitions(state: *state_machine) void = {
	for (let i = 0z; i < len(state.available_transitions); i += 1) {
		match(state.available_transitions[i]) {
		case let context_transition: transition_context =>
			if (context_elements_match_all(
				state.context_elements,
				context_transition.context_elements
			)) {
				if (sync_back_wait_child(state, true)) {
					return state_forward_available_transitions(state);
				};
				state.available_transitions = context_transition.transitions;

				return state_forward_available_transitions(state);
			};
		case let exec_transition: transition_exec =>
			if (run_transition_exec(exec_transition)) {
				state.available_transitions = exec_transition.transitions;

				return state_forward_available_transitions(state);
			};
		case transition =>
			continue;
		};

	};

	if (state_restart(state)) {
		return state_forward_available_transitions(state);
	};
};

export fn trigger_delay_transition(state: *state_machine) void = {
	match (find_to_wait_transition_delay(state.available_transitions)) {
	case let delay_transition: transition_delay =>
		run_delay_transition(state, delay_transition);
	case void =>
		return;
	};
};

fn find_to_wait_transition_delay(transitions: []transition) (void | transition_delay) = {
	for (let i = 0z; i < len(transitions); i += 1) {
		match(transitions[i]) {
		case let delay_transition: transition_delay =>
			return delay_transition;
		case transition =>
			continue;
		};
	};
};

fn run_delay_transition(state: *state_machine, transition: transition_delay) void = {
	if (state.wait_pipes is void) {
		state.wait_pipes = unix::pipe(unix::pipe_flag::NONBLOCK)!;
	};

	let wait_pipes = state.wait_pipes as (io::file, io::file);

	match (exec::fork()) {
	case let err: exec::error =>
		fmt::fatal("delay scheduling failed", exec::strerror(err));
	case let child_pid: int =>
		state.wait_pid = child_pid;
	case void =>
		signal::unblock(signal::SIGINT, signal::SIGTERM);

		time::sleep(transition.delay_duration);

		fmt::fprintln(wait_pipes.1, "WOKEUP")!;

		state.available_transitions = transition.transitions;
		state_forward_available_transitions(state);

		write_transitions(wait_pipes.1, state.available_transitions);

		fmt::fprintln(wait_pipes.1)!;

		os::exit(0);
	};
};

fn state_restart(state: *state_machine) bool = {
	if (len(state.available_transitions) == 0
		&& len(state.all_transitions) != 0
	) {
		state.available_transitions = state.all_transitions;
		return true;
	};
	return false;
};

// kill wait forked process and get back it results
export fn sync_back_wait_child(state: *state_machine, abort_it: bool) bool = {
	if (state.wait_pid == 0) {
		return false;
	};

	let wait_pipes = state.wait_pipes as (io::file, io::file);

	for (true) {
		match (bufio::scanline(wait_pipes.0)) {
		case let buf: []u8 =>
			defer free(buf);
			const string = strings::fromutf8(buf);
			if (string == "WOKEUP") {
				break;
			};
		case errors::again =>
			// it never woke up
			if (abort_it) {
				exec::kill(state.wait_pid)!;
				state.wait_pid = 0;
				state.wait_pipes = void;
			};
			return false;
		case let err: io::error =>
			fmt::fatal(io::strerror(err));
		};
	};

	for (true) {
		match(load_transitions(wait_pipes.0)) {
		case let transitions: []transition =>
			state.available_transitions = transitions;
			break;
		case errors::again =>
			time::sleep(10000000);
		case let err: json::error =>
			fmt::fatal("Failed to parse the json:", json::strerror(err));
		case let err: error =>
			fmt::fatal("Failed to parse the json:", strerror(err));
		};
	};

	state.wait_pid = 0;

	return true;
};

fn run_transition_exec(transition: transition_exec) bool = {
	let program = transition.command[0];
	let args = transition.command[1..];

	let cmd = match (exec::cmd(program, args...)) {
	case let cmd: exec::command =>
		yield cmd;
	case let err: exec::error =>
		fmt::fatal(exec::strerror(err));
	};

	let proc = match(exec::start(&cmd)) {
	case let proc: exec::process =>
		yield proc;
	case let err: exec::error =>
		fmt::fatal(exec::strerror(err));
	};

	let status = match(exec::wait(&proc)) {
	case let status: exec::status =>
		yield status;
	case let err: exec::error =>
		fmt::fatal(exec::strerror(err));
	};

	return status.status == 0;
};

R bonsai/tree/voyager_context.ha => bonsai/state_context.ha +4 -4
@@ 39,20 39,20 @@ export fn context_elements_match_all(
	return match_count == len(to_match_context_elements);
};

fn voyager_update_context(
	voyager: *voyager,
fn state_update_context(
	state: *state_machine,
	context_elements: []context_element
) void = {
	for (let i = 0z; i < len(context_elements); i += 1) {
		if (context_elements_set_key_if_present(
			voyager.context_elements,
			state.context_elements,
			context_elements[i].0,
			context_elements[i].1,
		)) {
			continue;
		};
		append(
			voyager.context_elements,
			state.context_elements,
			(
				strings::dup(context_elements[i].0),
				strings::dup(context_elements[i].1)

R bonsai/tree/test_voyager.ha => bonsai/test_voyager.ha +193 -193
@@ 6,25 6,25 @@ use os;
use io;
use strings;

let tree1: []branch = [
	branch_context {
let transitions1: []transition = [
	transition_context {
		context_elements = [
			("foo", "asdf"),
			("titi", "tata"),
		],
		branches = [
			branch_event {
		transitions = [
			transition_event {
				event_name = "target_2",
				...
			}
		]
	},
	branch_context {
	transition_context {
		context_elements = [
			("foo", "bar"),
		],
		branches = [
			branch_event {
		transitions = [
			transition_event {
				event_name = "target_1",
				...
			}


@@ 32,9 32,9 @@ let tree1: []branch = [
	},
];

@test fn voyager_forward_context_changed_test() void = {
	let voyager = voyager {
		available_branches = tree1,
@test fn state_forward_context_changed_test() void = {
	let state = state_machine {
		available_transitions = transitions1,
		context_elements = alloc([
			(
				"foo",


@@ 44,9 44,9 @@ let tree1: []branch = [
		...
	};

	voyager_forward(
		&voyager,
		voyager_action_context_changed {
	state_forward(
		&state,
		action_context_changed {
			context_elements = [
				(
					"foo",


@@ 60,21 60,21 @@ let tree1: []branch = [
		}
	);

	assert(len(voyager.context_elements) == 2);
	assert(voyager.context_elements[0].0 == "foo");
	assert(voyager.context_elements[0].1 == "bar");
	assert(voyager.context_elements[1].0 == "titi");
	assert(voyager.context_elements[1].1 == "tata");
	assert(len(state.context_elements) == 2);
	assert(state.context_elements[0].0 == "foo");
	assert(state.context_elements[0].1 == "bar");
	assert(state.context_elements[1].0 == "titi");
	assert(state.context_elements[1].1 == "tata");

	assert(voyager.available_branches[0] is branch_event);
	let subject = voyager.available_branches[0] as branch_event;
	assert(state.available_transitions[0] is transition_event);
	let subject = state.available_transitions[0] as transition_event;
	assert(subject.event_name == "target_1");

	voyager.available_branches = tree1;
	state.available_transitions = transitions1;

	voyager_forward(
		&voyager,
		voyager_action_context_changed {
	state_forward(
		&state,
		action_context_changed {
			context_elements = [
				(
					"foo",


@@ 84,46 84,46 @@ let tree1: []branch = [
		}
	);

	assert(voyager.available_branches[0] is branch_event);
	let subject = voyager.available_branches[0] as branch_event;
	assert(state.available_transitions[0] is transition_event);
	let subject = state.available_transitions[0] as transition_event;
	assert(subject.event_name == "target_2");
};

let tree2: []branch = [
	branch_event {
let transitions2: []transition = [
	transition_event {
		event_name = "event1",
		branches = [
			branch_event {
		transitions = [
			transition_event {
				event_name = "target_1",
				...
			}
		]
	},
	branch_event {
	transition_event {
		event_name = "event2",
		branches = [
			branch_event {
		transitions = [
			transition_event {
				event_name = "target_2",
				...
			}
		]
	},
	branch_event {
	transition_event {
		event_name = "event3",
		branches = [
			branch_context {
		transitions = [
			transition_context {
				context_elements = [
					("foo", "bar"),
					("toto", "tata"),
				],
				branches = [
					branch_event {
				transitions = [
					transition_event {
						event_name = "target_3",
						...
					}
				]
			},
			branch_event {
			transition_event {
				event_name = "target_4",
				...
			}


@@ 132,9 132,9 @@ let tree2: []branch = [
];


@test fn voyager_forward_event_received_test() void = {
	let voyager = voyager {
		available_branches = tree2,
@test fn state_forward_event_received_test() void = {
	let state = state_machine {
		available_transitions = transitions2,
		context_elements = alloc([
			("foo", "bar"),
			("toto", "tata"),


@@ 142,89 142,89 @@ let tree2: []branch = [
		...
	};

	voyager_forward(
		&voyager,
		voyager_action_event_received {
	state_forward(
		&state,
		action_event_received {
			event_name = "event1"
		}
	);

	assert(voyager.available_branches[0] is branch_event);
	let subject = voyager.available_branches[0] as branch_event;
	assert(state.available_transitions[0] is transition_event);
	let subject = state.available_transitions[0] as transition_event;
	assert(subject.event_name == "target_1");

	voyager.available_branches = tree2;
	state.available_transitions = transitions2;

	voyager_forward(
		&voyager,
		voyager_action_event_received {
	state_forward(
		&state,
		action_event_received {
			event_name = "event2"
		}
	);

	assert(voyager.available_branches[0] is branch_event);
	let subject = voyager.available_branches[0] as branch_event;
	assert(state.available_transitions[0] is transition_event);
	let subject = state.available_transitions[0] as transition_event;
	assert(subject.event_name == "target_2");

	voyager.available_branches = tree2;
	state.available_transitions = transitions2;

	voyager_forward(
		&voyager,
		voyager_action_event_received {
	state_forward(
		&state,
		action_event_received {
			event_name = "event3"
		}
	);

	assert(voyager.available_branches[0] is branch_event);
	let subject = voyager.available_branches[0] as branch_event;
	assert(state.available_transitions[0] is transition_event);
	let subject = state.available_transitions[0] as transition_event;
	assert(subject.event_name == "target_3");

	voyager.available_branches = tree2;
	state.available_transitions = transitions2;

	voyager_forward(
		&voyager,
		voyager_action_context_changed {
	state_forward(
		&state,
		action_context_changed {
			context_elements = [("foo", "notbar")]
		}
	);

	voyager_forward(
		&voyager,
		voyager_action_event_received {
	state_forward(
		&state,
		action_event_received {
			event_name = "event3"
		}
	);

	assert(voyager.available_branches[1] is branch_event);
	let subject = voyager.available_branches[1] as branch_event;
	assert(state.available_transitions[1] is transition_event);
	let subject = state.available_transitions[1] as transition_event;
	assert(subject.event_name == "target_4");
};

let tree3: []branch = [
	branch_event {
let transitions3: []transition = [
	transition_event {
		event_name = "event1",
		branches = [
			branch_exec {
		transitions = [
			transition_exec {
				command = [
					"sh",
					"-c",
					"exit 1"
				],
				branches = [
					branch_event {
				transitions = [
					transition_event {
						event_name = "impossible_target",
						...
					}
				]
			},
			branch_exec {
			transition_exec {
				command = [
					"sh",
					"-c",
					"exit 0"
				],
				branches = [
					branch_event {
				transitions = [
					transition_event {
						event_name = "target_1",
						...
					}


@@ 234,25 234,25 @@ let tree3: []branch = [
	},
];

let tree4: []branch = [
	branch_event {
let transitions4: []transition = [
	transition_event {
		event_name = "event1",
		branches = [
			branch_exec {
		transitions = [
			transition_exec {
				command = [
					"sh",
					"-c",
					"exit 0"
				],
				branches = [
					branch_exec {
				transitions = [
					transition_exec {
						command = [
							"sh",
							"-c",
							"exit 0"
						],
						branches = [
							branch_event {
						transitions = [
							transition_event {
								event_name = "target_1",
								...
							}


@@ 264,57 264,57 @@ let tree4: []branch = [
	},
];

@test fn voyager_forward_check_exec_test() void = {
	let voyager = voyager {
		available_branches = tree3,
@test fn state_forward_check_exec_test() void = {
	let state = state_machine {
		available_transitions = transitions3,
		...
	};

	voyager_forward(
		&voyager,
		voyager_action_event_received {
	state_forward(
		&state,
		action_event_received {
			event_name = "event1"
		}
	);

	assert(voyager.available_branches[0] is branch_event);
	let subject = voyager.available_branches[0] as branch_event;
	assert(state.available_transitions[0] is transition_event);
	let subject = state.available_transitions[0] as transition_event;
	assert(subject.event_name == "target_1");

	voyager.available_branches = tree4;
	state.available_transitions = transitions4;

	voyager_forward(
		&voyager,
		voyager_action_event_received {
	state_forward(
		&state,
		action_event_received {
			event_name = "event1"
		}
	);

	assert(voyager.available_branches[0] is branch_event);
	let subject = voyager.available_branches[0] as branch_event;
	assert(state.available_transitions[0] is transition_event);
	let subject = state.available_transitions[0] as transition_event;
	assert(subject.event_name == "target_1");
};

let tree5: []branch = [
	branch_event {
let transitions5: []transition = [
	transition_event {
		event_name = "event1",
		branches = [
			branch_event {
		transitions = [
			transition_event {
				event_name = "event2",
				branches = [
					branch_event {
				transitions = [
					transition_event {
						event_name = "target_2",
						...
					}
				]
			},
			branch_delay {
			transition_delay {
				delay_duration = 500000000,
				branches = [
					branch_event {
				transitions = [
					transition_event {
						event_name = "target_1",
						branches = [
							branch_event {
						transitions = [
							transition_event {
								event_name = "target_3",
								...
							}


@@ 326,105 326,105 @@ let tree5: []branch = [
	},
];

@test fn voyager_forward_check_delay_triggered_test() void = {
	let voyager = voyager {
		available_branches = tree5,
@test fn state_forward_check_delay_triggered_test() void = {
	let state = state_machine {
		available_transitions = transitions5,
		wait_pipes = void,
		...
	};

	voyager_forward(
		&voyager,
		voyager_action_event_received {
	state_forward(
		&state,
		action_event_received {
			event_name = "event1"
		}
	);

	time::sleep(700000000);

	voyager_forward(
		&voyager,
		voyager_action_event_received {
	state_forward(
		&state,
		action_event_received {
			event_name = "target_1"
		}
	);

	assert(voyager.available_branches[0] is branch_event);
	let subject = voyager.available_branches[0] as branch_event;
	assert(state.available_transitions[0] is transition_event);
	let subject = state.available_transitions[0] as transition_event;
	assert(subject.event_name == "target_3");
};

@test fn voyager_forward_check_delay_cancelled_test() void = {
	let voyager = voyager {
		available_branches = tree5,
@test fn state_forward_check_delay_cancelled_test() void = {
	let state = state_machine {
		available_transitions = transitions5,
		wait_pipes = void,
		...
	};

	voyager_forward(
		&voyager,
		voyager_action_event_received {
	state_forward(
		&state,
		action_event_received {
			event_name = "event1"
		}
	);

	time::sleep(250000000);

	voyager_forward(
		&voyager,
		voyager_action_event_received {
	state_forward(
		&state,
		action_event_received {
			event_name = "event2"
		}
	);

	time::sleep(500000000);
	sync_back_wait_child(&voyager, true);
	sync_back_wait_child(&state, true);

	assert(voyager.available_branches[0] is branch_event);
	let subject = voyager.available_branches[0] as branch_event;
	assert(state.available_transitions[0] is transition_event);
	let subject = state.available_transitions[0] as transition_event;
	assert(subject.event_name == "target_2");
};

@test fn voyager_forward_check_delay_cancelled_ignored_test() void = {
	let voyager = voyager {
		available_branches = tree5,
@test fn state_forward_check_delay_cancelled_ignored_test() void = {
	let state = state_machine {
		available_transitions = transitions5,
		wait_pipes = void,
		...
	};

	voyager_forward(
		&voyager,
		voyager_action_event_received {
	state_forward(
		&state,
		action_event_received {
			event_name = "event1"
		}
	);

	time::sleep(500000000);

	voyager_forward(
		&voyager,
		voyager_action_event_received {
	state_forward(
		&state,
		action_event_received {
			event_name = "event333"
		}
	);

	time::sleep(1000000000);
	sync_back_wait_child(&voyager, true);
	sync_back_wait_child(&state, true);

	assert(voyager.available_branches[0] is branch_event);
	let subject = voyager.available_branches[0] as branch_event;
	assert(state.available_transitions[0] is transition_event);
	let subject = state.available_transitions[0] as transition_event;
	assert(subject.event_name == "target_1");
};

let tree6: []branch = [
	branch_event {
let transitions6: []transition = [
	transition_event {
		event_name = "event1",
		...
	},
	branch_event {
	transition_event {
		event_name = "event2",
		branches = [
			branch_exec {
		transitions = [
			transition_exec {
				command = [
					"sh",
					"-c",


@@ 434,10 434,10 @@ let tree6: []branch = [
			},
		]
	},
	branch_event {
	transition_event {
		event_name = "event3",
		branches = [
			branch_delay {
		transitions = [
			transition_delay {
				delay_duration = 500000000,
				...
			},


@@ 445,62 445,62 @@ let tree6: []branch = [
	},
];

@test fn voyager_forward_check_returned_init_test() void = {
	let voyager = voyager {
		tree = tree6,
		available_branches = tree6,
@test fn state_forward_check_returned_init_test() void = {
	let state = state_machine {
		all_transitions = transitions6,
		available_transitions = transitions6,
		wait_pipes = void,
		...
	};

	voyager_forward(
		&voyager,
		voyager_action_event_received {
	state_forward(
		&state,
		action_event_received {
			event_name = "event1"
		}
	);

	assert(voyager.available_branches[0] is branch_event);
	let subject = voyager.available_branches[0] as branch_event;
	assert(state.available_transitions[0] is transition_event);
	let subject = state.available_transitions[0] as transition_event;
	assert(subject.event_name == "event1");

	voyager_forward(
		&voyager,
		voyager_action_event_received {
	state_forward(
		&state,
		action_event_received {
			event_name = "event2"
		}
	);

	assert(voyager.available_branches[0] is branch_event);
	let subject = voyager.available_branches[0] as branch_event;
	assert(state.available_transitions[0] is transition_event);
	let subject = state.available_transitions[0] as transition_event;
	assert(subject.event_name == "event1");

	voyager_forward(
		&voyager,
		voyager_action_event_received {
	state_forward(
		&state,
		action_event_received {
			event_name = "event3"
		}
	);

	time::sleep(700000000);

	voyager_forward(
		&voyager,
		voyager_action_event_received {
	state_forward(
		&state,
		action_event_received {
			event_name = "event1"
		}
	);

	assert(voyager.available_branches[0] is branch_event);
	let subject = voyager.available_branches[0] as branch_event;
	assert(state.available_transitions[0] is transition_event);
	let subject = state.available_transitions[0] as transition_event;
	assert(subject.event_name == "event1");
};

let tree7: []branch = [
	branch_event {
let transitions7: []transition = [
	transition_event {
		event_name = "event1",
		branches = [
			branch_exec {
		transitions = [
			transition_exec {
				command = [
					"sh",
					"-c",


@@ 508,9 508,9 @@ let tree7: []branch = [
				],
				...
			},
			branch_context {
				branches = [
					branch_event {
			transition_context {
				transitions = [
					transition_event {
						event_name = "target_1",
						...
					},


@@ 519,9 519,9 @@ let tree7: []branch = [
			}
		]
	},
	branch_context {
		branches = [
			branch_event {
	transition_context {
		transitions = [
			transition_event {
				event_name = "target_unwanted",
				...
			},


@@ 530,21 530,21 @@ let tree7: []branch = [
	},
];

@test fn voyager_forward_check_returned_init_test() void = {
	let voyager = voyager {
		available_branches = tree7,
@test fn state_forward_check_returned_init_test() void = {
	let state = state_machine {
		available_transitions = transitions7,
		wait_pipes = void,
		...
	};

	voyager_forward(
		&voyager,
		voyager_action_event_received {
	state_forward(
		&state,
		action_event_received {
			event_name = "event1"
		}
	);

	assert(voyager.available_branches[0] is branch_event);
	let subject = voyager.available_branches[0] as branch_event;
	assert(state.available_transitions[0] is transition_event);
	let subject = state.available_transitions[0] as transition_event;
	assert(subject.event_name == "target_1");
};

D bonsai/tree/voyager.ha => bonsai/tree/voyager.ha +0 -239
@@ 1,239 0,0 @@
use fmt;
use log;
use strings;
use os::exec;
use os;
use time;
use io;
use unix::signal;
use unix;
use fmt;
use bufio;
use encoding::json;
use errors;

export fn voyager_forward(
	voyager: *voyager,
	action: voyager_action
) void = {
	sync_back_wait_child(voyager, false);

	match (action) {
	case let action: voyager_action_context_changed =>
		return voyager_forward_context_changed(voyager, action);
	case let action: voyager_action_event_received =>
		voyager_forward_event_received(voyager, action);
	case let action: voyager_action =>
		fmt::fatal("Not supported yet");
	};
};

fn voyager_forward_event_received(
	voyager: *voyager,
	action: voyager_action_event_received
) void = {
	let target_event = action.event_name;
	let matched_event = false;

	for (let i = 0z; i < len(voyager.available_branches); i += 1) {
		let event_branch = match(voyager.available_branches[i]) {
		case let event_branch: branch_event =>
			yield event_branch;
		case branch =>
			continue;
		};

		if (event_branch.event_name == target_event) {
			if (sync_back_wait_child(voyager, true)) {
				return voyager_forward_event_received(voyager, action);
			};
			matched_event = true;
			voyager.available_branches = event_branch.branches;
			break;
		};
	};

	if (!matched_event) {
		return;
	};

	voyager_forward_available_branches(voyager);
	trigger_delay_branch(voyager);
};

fn voyager_forward_context_changed(
	voyager: *voyager,
	action: voyager_action_context_changed
) void = {
	voyager_update_context(voyager, action.context_elements);
	voyager_forward_available_branches(voyager);
	trigger_delay_branch(voyager);
};

export fn voyager_forward_available_branches(voyager: *voyager) void = {
	for (let i = 0z; i < len(voyager.available_branches); i += 1) {
		match(voyager.available_branches[i]) {
		case let context_branch: branch_context =>
			if (context_elements_match_all(
				voyager.context_elements,
				context_branch.context_elements
			)) {
				if (sync_back_wait_child(voyager, true)) {
					return voyager_forward_available_branches(voyager);
				};
				voyager.available_branches = context_branch.branches;

				return voyager_forward_available_branches(voyager);
			};
		case let exec_branch: branch_exec =>
			if (run_branch_exec(exec_branch)) {
				voyager.available_branches = exec_branch.branches;

				return voyager_forward_available_branches(voyager);
			};
		case branch =>
			continue;
		};

	};

	if (voyager_restart(voyager)) {
		return voyager_forward_available_branches(voyager);
	};
};

export fn trigger_delay_branch(voyager: *voyager) void = {
	match (find_to_wait_branch_delay(voyager.available_branches)) {
	case let delay_branch: branch_delay =>
		run_branch_delay(voyager, delay_branch);
	case void =>
		return;
	};
};

fn find_to_wait_branch_delay(branches: []branch) (void | branch_delay) = {
	for (let i = 0z; i < len(branches); i += 1) {
		match(branches[i]) {
		case let delay_branch: branch_delay =>
			return delay_branch;
		case branch =>
			continue;
		};
	};
};

fn run_branch_delay(voyager: *voyager, branch: branch_delay) void = {
	if (voyager.wait_pipes is void) {
		voyager.wait_pipes = unix::pipe(unix::pipe_flag::NONBLOCK)!;
	};

	let wait_pipes = voyager.wait_pipes as (io::file, io::file);

	match (exec::fork()) {
	case let err: exec::error =>
		fmt::fatal("delay scheduling failed", exec::strerror(err));
	case let child_pid: int =>
		voyager.wait_pid = child_pid;
	case void =>
		signal::unblock(signal::SIGINT, signal::SIGTERM);

		time::sleep(branch.delay_duration);

		fmt::fprintln(wait_pipes.1, "WOKEUP")!;

		voyager.available_branches = branch.branches;
		voyager_forward_available_branches(voyager);

		write_tree(wait_pipes.1, voyager.available_branches);

		fmt::fprintln(wait_pipes.1)!;

		os::exit(0);
	};
};

fn voyager_restart(voyager: *voyager) bool = {
	if (len(voyager.available_branches) == 0
		&& len(voyager.tree) != 0
	) {
		voyager.available_branches = voyager.tree;
		return true;
	};
	return false;
};

// kill wait forked process and get back it results
export fn sync_back_wait_child(voyager: *voyager, abort_it: bool) bool = {
	if (voyager.wait_pid == 0) {
		return false;
	};

	let wait_pipes = voyager.wait_pipes as (io::file, io::file);

	for (true) {
		match (bufio::scanline(wait_pipes.0)) {
		case let buf: []u8 =>
			defer free(buf);
			const string = strings::fromutf8(buf);
			if (string == "WOKEUP") {
				break;
			};
		case errors::again =>
			// it never woke up
			if (abort_it) {
				exec::kill(voyager.wait_pid)!;
				voyager.wait_pid = 0;
				voyager.wait_pipes = void;
			};
			return false;
		case let err: io::error =>
			fmt::fatal(io::strerror(err));
		};
	};

	for (true) {
		match(load_tree(wait_pipes.0)) {
		case let branches: []branch =>
			voyager.available_branches = branches;
			break;
		case errors::again =>
			time::sleep(10000000);
		case let err: json::error =>
			fmt::fatal("Failed to parse the json:", json::strerror(err));
		case let err: error =>
			fmt::fatal("Failed to parse the json:", strerror(err));
		};
	};

	voyager.wait_pid = 0;

	return true;
};

fn run_branch_exec(branch: branch_exec) bool = {
	let program = branch.command[0];
	let args = branch.command[1..];

	let cmd = match (exec::cmd(program, args...)) {
	case let cmd: exec::command =>
		yield cmd;
	case let err: exec::error =>
		fmt::fatal(exec::strerror(err));
	};

	let proc = match(exec::start(&cmd)) {
	case let proc: exec::process =>
		yield proc;
	case let err: exec::error =>
		fmt::fatal(exec::strerror(err));
	};

	let status = match(exec::wait(&proc)) {
	case let status: exec::status =>
		yield status;
	case let err: exec::error =>
		fmt::fatal(exec::strerror(err));
	};

	return status.status == 0;
};

R bonsai/tree/type.ha => bonsai/type.ha +21 -21
@@ 3,49 3,49 @@ use io;

export type event_name = str;

export type branch_event = struct {
	branches: []branch,
export type transition_event = struct {
	transitions: []transition,
	event_name: event_name,
};

export type branch_context = struct {
	branches: []branch,
export type transition_context = struct {
	transitions: []transition,
	context_elements: []context_element,
};

export type branch_exec = struct {
	branches: []branch,
export type transition_exec = struct {
	transitions: []transition,
	command: []str,
};

export type branch_delay = struct {
	branches: []branch,
export type transition_delay = struct {
	transitions: []transition,
	delay_duration: int,
};

export type branch = (
	branch_event |
	branch_context |
	branch_exec |
	branch_delay |
export type transition = (
	transition_event |
	transition_context |
	transition_exec |
	transition_delay |
);

export type voyager_action_event_received = struct {
export type action_event_received = struct {
	event_name: event_name,
};

export type voyager_action_context_changed = struct {
export type action_context_changed = struct {
	context_elements: []context_element,
};

export type voyager_action = (
	voyager_action_event_received |
	voyager_action_context_changed
export type action = (
	action_event_received |
	action_context_changed
);

export type voyager = struct {
	tree: []branch,
	available_branches: []branch,
export type state_machine = struct {
	all_transitions: []transition,
	available_transitions: []transition,
	context_elements: []context_element,
	wait_pid: int,
	wait_pipes: (void | (io::file, io::file)),

M cmd/bonsaictl/main.ha => cmd/bonsaictl/main.ha +6 -6
@@ 13,7 13,7 @@ use shlex;
use strings;
use strio;
use unix::tty;
use bonsai::tree;
use bonsai;

type operation = enum uint {
	PING,


@@ 34,7 34,7 @@ export fn main() void = {
	defer getopt::finish(&cmd);

	let event_name = "";
	let context_elements: []tree::context_element = [];
	let context_elements: []bonsai::context_element = [];

	let op = operation::PING;
	for (let i = 0z; i < len(cmd.opts); i += 1) {


@@ 47,7 47,7 @@ export fn main() void = {
			for (let i = 0z; i < len(cmd.args); i += 1) {
				append(
					context_elements,
					tree::context_element_str(cmd.args[i])
					bonsai::context_element_str(cmd.args[i])
				);
			};
		case 'Q' =>


@@ 74,8 74,8 @@ export fn main() void = {
fn send(
	conn: io::file,
	op: operation,
	event_name: tree::event_name,
	context_elements: []tree::context_element,
	event_name: bonsai::event_name,
	context_elements: []bonsai::context_element,
) void = {
	fmt::fprint(conn, switch (op) {
	case operation::EVENT =>


@@ 93,7 93,7 @@ fn send(

	for (let i = 0z; i < len(context_elements); i += 1) {
		fmt::fprint(conn, " ")!;
		const value = tree::str_context_element(context_elements[i]);
		const value = bonsai::str_context_element(context_elements[i]);
		defer free(value);
		fmt::fprint(conn, value)!;
	};

M cmd/bonsaid/cmd.ha => cmd/bonsaid/cmd.ha +9 -9
@@ 9,7 9,7 @@ use os::exec;
use shlex;
use strings;
use unix::poll::{event};
use bonsai::tree;
use bonsai;

type servererror = !(io::error | fs::error | exec::error);
type cmderror = servererror;


@@ 61,9 61,9 @@ fn exec(serv: *server, client: *client, cmd: str) (void | servererror) = {
};

fn exec_event(serv: *server, client: *client, args: []str) (void | cmderror) = {
	tree::voyager_forward(
		&serv.voyager,
		tree::voyager_action_event_received {
	bonsai::state_forward(
		&serv.state,
		bonsai::action_event_received {
			event_name = args[0]
		}
	);


@@ 75,17 75,17 @@ fn exec_event(serv: *server, client: *client, args: []str) (void | cmderror) = {
};

fn exec_context(serv: *server, client: *client, args: []str) (void | cmderror) = {
	let context_elements: []tree::context_element = [];
	let context_elements: []bonsai::context_element = [];
	for (let i = 0z; i < len(args); i += 1) {
		append(
			context_elements,
			tree::context_element_str(args[i])
			bonsai::context_element_str(args[i])
		);
	};

	tree::voyager_forward(
		&serv.voyager,
		tree::voyager_action_context_changed {
	bonsai::state_forward(
		&serv.state,
		bonsai::action_context_changed {
			context_elements = context_elements
		}
	);

M cmd/bonsaid/main.ha => cmd/bonsaid/main.ha +18 -18
@@ 7,20 7,20 @@ use os;
use rt;
use unix::signal;
use unix;
use bonsai::tree;
use bonsai;
use encoding::json;
use fs;

export fn main() void = {
	const cmd = getopt::parse(
		os::args,
		('t', "file", "json tree file source"),
		('t', "file", "json transition file source"),
		('D', "daemonize (fork) after startup")
	);
	defer getopt::finish(&cmd);

	let daemonize = false;
	let tree_path: (void | str) = void;
	let json_file_path: (void | str) = void;

	for (let i = 0z; i < len(cmd.opts); i += 1) {
		const opt = &cmd.opts[i];


@@ 28,44 28,44 @@ export fn main() void = {
		case 'D' =>
			daemonize = true;
		case 't' =>
			tree_path = opt.1;
			json_file_path = opt.1;
		case => abort();
		};
	};

	if (tree_path is void) {
	if (json_file_path is void) {
		fmt::fatal("You have to provide a json tree");
	};

	let tree_file = match (os::open(tree_path as str)) {
	case let tree_file: io::file =>
		yield tree_file;
	let json_file = match (os::open(json_file_path as str)) {
	case let json_file: io::file =>
		yield json_file;
	case let err: fs::error =>
		fmt::fatal("Failed to read tree file:", fs::strerror(err));
		fmt::fatal("Failed to read transitions file:", fs::strerror(err));
	};

	let branches = match(tree::load_tree(tree_file)) {
	case let branches: []tree::branch =>
		yield branches;
	let transitions = match(bonsai::load_transitions(json_file)) {
	case let transitions: []bonsai::transition =>
		yield transitions;
	case let err: json::error =>
		fmt::fatal("Failed to parse the json:", json::strerror(err));
	case let err: tree::error =>
		fmt::fatal("Failed to parse the json:", tree::strerror(err));
	case let err: bonsai::error =>
		fmt::fatal("Failed to parse the json:", bonsai::strerror(err));
	};

	signal::block(signal::SIGINT, signal::SIGTERM);
	const sigfd = signal::signalfd(signal::SIGINT, signal::SIGTERM)!;
	defer io::close(sigfd)!;

	let voyager = tree::voyager {
		tree = branches,
		available_branches = branches,
	let state = bonsai::state_machine {
		all_transitions = transitions,
		available_transitions = transitions,
		context_elements = [],
		wait_pid = 0,
		wait_pipes = unix::pipe(unix::pipe_flag::NONBLOCK)!,
	};

	const server = bind(sigfd, daemonize, voyager);
	const server = bind(sigfd, daemonize, state);

	if (daemonize) {
		match (exec::fork()) {

M cmd/bonsaid/socket.ha => cmd/bonsaid/socket.ha +8 -8
@@ 15,7 15,7 @@ use unix::poll::{event};
use unix::poll;
use unix::signal;
use unix;
use bonsai::tree;
use bonsai;

def CLIENT_MAXBUF: size = 16777216; // 16 MiB
def MAX_CLIENTS: size = 256;


@@ 31,13 31,13 @@ type server = struct {
	daemonized: bool,
	terminate: bool,

	voyager: tree::voyager,
	state: bonsai::state_machine,
};

fn bind(
	signalfd: io::file,
	daemonize: bool,
	voyager: tree::voyager
	state: bonsai::state_machine
) server = {
	const statedir = match (dirs::runtime()) {
	case let err: fs::error =>


@@ 57,7 57,7 @@ fn bind(
	};
	os::chmod(sockpath, 0o700)!;

	let wait_pipes = voyager.wait_pipes as (io::file, io::file);
	let wait_pipes = state.wait_pipes as (io::file, io::file);
	let pollfd = alloc([
		poll::pollfd {
			fd = sock,


@@ 81,7 81,7 @@ fn bind(
		pollfd = pollfd,
		daemonized = daemonize,
		sock = sock,
		voyager = voyager,
		state = state,
		...
	};
};


@@ 117,9 117,9 @@ fn dispatch(serv: *server) bool = {
			return false;
		};
		if (serv.pollfd[2].revents & event::POLLOUT != 0) {
			if (tree::sync_back_wait_child(&serv.voyager, false)) {
				tree::voyager_forward_available_branches(&serv.voyager);
				tree::trigger_delay_branch(&serv.voyager);
			if (bonsai::sync_back_wait_child(&serv.state, false)) {
				bonsai::state_forward_available_transitions(&serv.state);
				bonsai::trigger_delay_transition(&serv.state);
			};
		};
		for (let i = POLLFD_RESERVED; i < len(serv.pollfd); i += 1) {

R fixtures/basic_tree.json => fixtures/basic_transitions.json +6 -6
@@ 5,15 5,15 @@
      "my_env": "true",
      "another_env": "also present"
    },
    "branches": [
    "transitions": [
      {
        "type": "event",
        "event_name": "typical_event",
        "branches": [
        "transitions": [
          {
            "type": "delay",
            "delay_duration": 2000,
            "branches": [
            "transitions": [
              {
                "type": "exec",
                "command": [


@@ 28,7 28,7 @@
          },
          {
            "type": "context",
            "branches": [
            "transitions": [
              {
                "type": "exec",
                "command": [


@@ 51,11 51,11 @@
    "contexts": {
      "my_env": "false"
    },
    "branches": [
    "transitions": [
      {
        "type": "event",
        "event_name": "typical_event",
        "branches": [
        "transitions": [
          {
            "type": "exec",
            "command": [