Simple parallel expression engine for erlang.
Every form and/or has a type
field. It also should set line
to get helpful errors that map to the correct line of the original source. value
and children
can be present depending on the type.
calls, assignments and variables may also have the following fields:
silent
- return undefined if the expression failstimeout
- timeout after msspawn
- spawn the function in a wrapper process for an async response
#{
type => literal,
value => atom
}
#{
type => literal,
value => <<"binary">>
}
#{
type => literal,
value => 3.14
}
#{
type => literal,
value => 123
}
#{
type => list,
children => [
#{
type => literal,
value => first
},
#{
type => literal,
value => second
},
#{
type => literal,
value => third
}
]
}
#{
type => tuple,
children => [
#{
type => literal,
value => first
},
#{
type => literal,
value => second
},
#{
type => literal,
value => third
}
]
}
#{
type => map,
children => [
#{
type => tuple,
children => [
#{
type => literal,
value => first_key
},
#{
type => literal,
value => first_value
}
]
},
#{
type => tuple,
children => [
#{
type => literal,
value => second_key
},
#{
type => literal,
value => second_value
}
]
},
#{
type => tuple,
children => [
#{
type => literal,
value => third_key
},
#{
type => literal,
value => third_value
}
]
}
]
}
#{
type => call,
value => {module, function},
children => [
#{
type => literal,
value => first_argument
},
#{
type => literal,
value => second_argument
}
#{
type => literal,
value => third_argument
}
]
}
#{
type => 'cond',
children => [
%% should return 'true' or 'false'
#{
type => call,
value => {module, is_valid}
},
%% only called if 'true'
#{
type => call,
value => {module, truths}
},
%% only called if 'false'
#{
type => call,
value => {module, falsities}
}
]
}
or
#{
type => 'cond',
children => [
%% should return 'true' or 'false'
#{
type => call,
value => {module, is_valid}
},
%% only called if 'true'
#{
type => call,
value => {module, truths}
}
%% otherwise returns 'undefined'
]
}
#{
type => comprehension,
children => [
#{
type => assign,
value => 'User',
children => [
#{
type => list,
children => [
#{
type => literal,
value => <<"1">>
},
#{
type => literal,
value => <<"2">>
},
#{
type => literal,
value => <<"3">>
},
#{
type => literal,
value => <<"4">>
},
#{
type => literal,
value => <<"5">>
}
]
}
]
},
#{
type => call,
value => {maps, get},
children => [
#{
type => literal,
value => name
},
#{
type => variable,
value => 'User'
}
]
}
]
}.
#{
type => assign,
value => 'MyVariable',
children => [
#{
type => literal,
value => <<"my value">>
}
]
}
#{
type => variable,
value => 'MyVariable'
}
The runtime behaves lazily, meaning it only evaluates the expressions when it needs the answer. It also uses a kind of error monad to handle exceptions.
-
each expr in pending
- is it root and is it done?
- yes
- return the value from
values
- return the value from
- no
- are the dependencies ready?
- yes
- push the
expr
onfns
- push the
- no
- push the dependencies on
pending
- push the dependencies on
- yes
- are the dependencies ready?
- yes
- is it root and is it done?
-
while messages
- recieve with timeout 0
- timeout
- exit loop
- value
- set the return value in
values
frompid
id
- remove
pid
frompids
- set the return value in
- error
- push
error
onerrors
- push
- timeout
- recieve with timeout 0
-
each fn in fns
- execute function with arguments
- error
- push
error
onerrors
- push
- pid
- push
pid
onpids
with a start time
- push
- success
- set the return value in
values
fromfn
id
- set the return value in
- error
- execute function with arguments
- each error in errors
- is silent?
- set
fn
result toundefined
- set
- otherwise
- crash
- is silent?
$ make test