Skip to content

Commit 4648b55

Browse files
committed
feat: added newest function handler impl
1 parent 3d5a8e4 commit 4648b55

4 files changed

Lines changed: 321 additions & 0 deletions

File tree

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
//! Handler argument representation and typed extraction contracts.
2+
3+
use crate::types::errors::runtime_error::RuntimeError;
4+
use crate::types::signal::Signal;
5+
use std::convert::Infallible;
6+
use tucana::shared::value::Kind;
7+
use tucana::shared::{ListValue, NumberValue, Struct, Value};
8+
9+
use crate::value::{number_to_f64, number_to_i64_lossy};
10+
#[derive(Clone, Debug)]
11+
pub enum Argument {
12+
/// Eager value that can be consumed immediately by a handler.
13+
Eval(Value),
14+
/// Deferred node execution handle, evaluated by calling `run(node_id)`.
15+
Thunk(i64),
16+
}
17+
18+
#[derive(Clone, Copy, Debug)]
19+
pub enum ParameterNode {
20+
/// Argument must be resolved before the handler is called.
21+
Eager,
22+
/// Argument is passed as a thunk and may be executed by the handler.
23+
Lazy,
24+
}
25+
26+
/// Conversion interface used by `args!` to parse typed handler inputs.
27+
pub trait TryFromArgument: Sized {
28+
fn try_from_argument(a: &Argument) -> Result<Self, Signal>;
29+
}
30+
31+
fn type_err(msg: &str, a: &Argument) -> Signal {
32+
Signal::Failure(RuntimeError::new(
33+
"T-RT-000000",
34+
"InvalidArgumentRuntimeError",
35+
format!("{} but it was the arugment: {:?}", msg, a),
36+
))
37+
}
38+
39+
impl TryFromArgument for Value {
40+
fn try_from_argument(a: &Argument) -> Result<Self, Signal> {
41+
match a {
42+
Argument::Eval(v) => Ok(v.clone()),
43+
_ => Err(type_err("Expected evaluated value but got lazy thunk", a)),
44+
}
45+
}
46+
}
47+
48+
impl TryFromArgument for NumberValue {
49+
fn try_from_argument(a: &Argument) -> Result<Self, Signal> {
50+
match a {
51+
Argument::Eval(Value {
52+
kind: Some(Kind::NumberValue(n)),
53+
}) => Ok(*n),
54+
_ => Err(type_err("Expected number", a)),
55+
}
56+
}
57+
}
58+
59+
impl TryFromArgument for i64 {
60+
fn try_from_argument(a: &Argument) -> Result<Self, Signal> {
61+
match a {
62+
Argument::Eval(Value {
63+
kind: Some(Kind::NumberValue(n)),
64+
}) => number_to_i64_lossy(n).ok_or_else(|| type_err("Expected number", a)),
65+
_ => Err(type_err("Expected number", a)),
66+
}
67+
}
68+
}
69+
70+
impl TryFromArgument for f64 {
71+
fn try_from_argument(a: &Argument) -> Result<Self, Signal> {
72+
match a {
73+
Argument::Eval(Value {
74+
kind: Some(Kind::NumberValue(n)),
75+
}) => number_to_f64(n).ok_or_else(|| type_err("Expected number", a)),
76+
_ => Err(type_err("Expected number", a)),
77+
}
78+
}
79+
}
80+
81+
impl TryFromArgument for bool {
82+
fn try_from_argument(a: &Argument) -> Result<Self, Signal> {
83+
match a {
84+
Argument::Eval(Value {
85+
kind: Some(Kind::BoolValue(b)),
86+
}) => Ok(*b),
87+
_ => Err(type_err("Expected boolean", a)),
88+
}
89+
}
90+
}
91+
92+
impl TryFromArgument for String {
93+
fn try_from_argument(a: &Argument) -> Result<Self, Signal> {
94+
match a {
95+
Argument::Eval(Value {
96+
kind: Some(Kind::StringValue(s)),
97+
}) => Ok(s.clone()),
98+
_ => Err(type_err("Expected string", a)),
99+
}
100+
}
101+
}
102+
103+
impl TryFromArgument for Struct {
104+
fn try_from_argument(a: &Argument) -> Result<Self, Signal> {
105+
match a {
106+
Argument::Eval(Value {
107+
kind: Some(Kind::StructValue(s)),
108+
}) => Ok(s.clone()),
109+
_ => Err(type_err("Expected struct", a)),
110+
}
111+
}
112+
}
113+
114+
impl TryFromArgument for ListValue {
115+
fn try_from_argument(a: &Argument) -> Result<Self, Signal> {
116+
match a {
117+
Argument::Eval(Value {
118+
kind: Some(Kind::ListValue(list)),
119+
}) => Ok(list.clone()),
120+
_ => Err(Signal::Failure(RuntimeError::new(
121+
"T-RT-000000",
122+
"InvalidArgumentRuntimeError",
123+
format!("Expected array (ListValue) but it was: {:?}", a),
124+
))),
125+
}
126+
}
127+
}
128+
129+
impl From<Infallible> for RuntimeError {
130+
fn from(never: Infallible) -> Self {
131+
match never {}
132+
}
133+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//! Handler argument parsing macros.
2+
3+
/// Pulls typed parameters from a slice of `Argument` using your `TryFromArgument`
4+
/// impls. Fails early with your `Signal::Failure(RuntimeError::new("T-RT-000000", ...))`.
5+
macro_rules! args {
6+
($args_ident:ident => $( $name:ident : $ty:ty ),+ $(,)?) => {
7+
// Arity check
8+
let __expected: usize = 0usize $(+ { let _ = ::core::any::type_name::<$ty>(); 1usize })*;
9+
if $args_ident.len() != __expected {
10+
return $crate::types::signal::Signal::Failure(
11+
$crate::types::errors::runtime_error::RuntimeError::new("T-RT-000000",
12+
"InvalidArgumentRuntimeError",
13+
format!("Expected {__expected} args but received {}", $args_ident.len()),
14+
)
15+
);
16+
}
17+
18+
// Typed extraction
19+
let mut __i: usize = 0;
20+
$(
21+
let $name: $ty = match <
22+
$ty as $crate::handler::argument::TryFromArgument
23+
>::try_from_argument(& $args_ident[__i]) {
24+
Ok(v) => v,
25+
Err(sig) => {
26+
log::debug!(
27+
"Failed to parse argument '{}' (index {}, type {})",
28+
stringify!($name),
29+
__i,
30+
::core::any::type_name::<$ty>(),
31+
);
32+
return sig;
33+
}
34+
};
35+
__i += 1;
36+
)+
37+
};
38+
}
39+
40+
/// Asserts there are no arguments.
41+
macro_rules! no_args {
42+
($args_ident:ident) => {
43+
if !$args_ident.is_empty() {
44+
return $crate::types::signal::Signal::Failure(
45+
$crate::types::errors::runtime_error::RuntimeError::new(
46+
"T-RT-000000",
47+
"InvalidArgumentRuntimeError",
48+
format!("Expected 0 args but received {}", $args_ident.len()),
49+
),
50+
);
51+
}
52+
};
53+
}
54+
55+
pub(crate) use args;
56+
pub(crate) use no_args;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//! Runtime handler-facing infrastructure.
2+
//!
3+
//! This module contains the argument model, extraction macros, and function
4+
//! registry used to invoke runtime handlers.
5+
6+
pub mod argument;
7+
pub mod macros;
8+
pub mod registry;
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
//! Runtime handler registry and callable function signatures.
2+
3+
use crate::handler::argument::{Argument, ParameterNode};
4+
use crate::runtime::execution::value_store::ValueStore;
5+
use crate::runtime::functions::ALL_FUNCTION_SETS;
6+
use crate::types::signal::Signal;
7+
use std::collections::HashMap;
8+
9+
/// Handler function type.
10+
/// - For eager params, the executor will already convert them to Argument::Eval(Value).
11+
/// - For lazy params, the executor will pass Argument::Thunk(node_id).
12+
/// - If a handler wants to execute a lazy arg, it calls run(node_id).
13+
pub type HandlerFn = fn(
14+
args: &[Argument],
15+
ctx: &mut ValueStore,
16+
run: &mut dyn FnMut(i64, &mut ValueStore) -> Signal,
17+
) -> Signal;
18+
19+
#[derive(Clone, Copy)]
20+
pub enum ParamSpec {
21+
/// All parameters are evaluated eagerly.
22+
AllEager(u8),
23+
/// Per-parameter evaluation mode.
24+
Explicit(&'static [ParameterNode]),
25+
}
26+
27+
impl ParamSpec {
28+
pub fn mode_at(self, index: usize) -> ParameterNode {
29+
match self {
30+
ParamSpec::AllEager(_) => ParameterNode::Eager,
31+
ParamSpec::Explicit(modes) => modes.get(index).copied().unwrap_or(ParameterNode::Eager),
32+
}
33+
}
34+
}
35+
36+
#[derive(Clone, Copy)]
37+
pub struct HandlerFunctionEntry {
38+
/// Callable implementation.
39+
pub handler: HandlerFn,
40+
/// Evaluation strategy for the handler parameters.
41+
pub param_spec: ParamSpec,
42+
}
43+
44+
impl HandlerFunctionEntry {
45+
pub const fn eager(handler: HandlerFn, param_count: u8) -> Self {
46+
Self {
47+
handler,
48+
param_spec: ParamSpec::AllEager(param_count),
49+
}
50+
}
51+
52+
pub const fn modes(handler: HandlerFn, param_modes: &'static [ParameterNode]) -> Self {
53+
Self {
54+
handler,
55+
param_spec: ParamSpec::Explicit(param_modes),
56+
}
57+
}
58+
59+
pub fn param_mode(&self, index: usize) -> ParameterNode {
60+
self.param_spec.mode_at(index)
61+
}
62+
}
63+
64+
#[derive(Clone, Copy)]
65+
pub struct FunctionRegistration {
66+
pub id: &'static str,
67+
pub entry: HandlerFunctionEntry,
68+
}
69+
70+
impl FunctionRegistration {
71+
pub const fn eager(id: &'static str, handler: HandlerFn, param_count: u8) -> Self {
72+
Self {
73+
id,
74+
entry: HandlerFunctionEntry::eager(handler, param_count),
75+
}
76+
}
77+
78+
pub const fn modes(
79+
id: &'static str,
80+
handler: HandlerFn,
81+
param_modes: &'static [ParameterNode],
82+
) -> Self {
83+
Self {
84+
id,
85+
entry: HandlerFunctionEntry::modes(handler, param_modes),
86+
}
87+
}
88+
}
89+
90+
/// Holds all registered handlers.
91+
pub struct FunctionStore {
92+
functions: HashMap<&'static str, HandlerFunctionEntry>,
93+
}
94+
95+
impl Default for FunctionStore {
96+
fn default() -> Self {
97+
let mut store = Self::new();
98+
for set in ALL_FUNCTION_SETS {
99+
store.populate(set);
100+
}
101+
store
102+
}
103+
}
104+
105+
impl FunctionStore {
106+
/// Create a new, empty store.
107+
pub fn new() -> Self {
108+
FunctionStore {
109+
functions: HashMap::new(),
110+
}
111+
}
112+
113+
/// Look up a handler by its ID.
114+
pub fn get(&self, id: &str) -> Option<&HandlerFunctionEntry> {
115+
self.functions.get(id)
116+
}
117+
118+
/// Register a group of handlers.
119+
pub fn populate(&mut self, regs: &[FunctionRegistration]) {
120+
for reg in regs {
121+
self.functions.insert(reg.id, reg.entry);
122+
}
123+
}
124+
}

0 commit comments

Comments
 (0)