Skip to main content

shape_value/
method_id.rs

1//! Typed method identifiers for zero-cost dispatch.
2//!
3//! `MethodId(u16)` replaces string-based method lookup at runtime.
4//! The compiler resolves method names to `MethodId` at compile time,
5//! and the VM dispatches on a `u16` match instead of a PHF string lookup.
6//!
7//! Methods not known at compile time use `MethodId::DYNAMIC`, which
8//! triggers a fallback to string-based lookup.
9
10use serde::{Deserialize, Serialize};
11
12/// A typed method identifier. Wraps a `u16` discriminant that the compiler
13/// resolves from method name strings at compile time.
14///
15/// Known methods get a fixed ID (0..N). Unknown/dynamic methods use
16/// `MethodId::DYNAMIC` (0xFFFF), which causes the VM to fall back to
17/// string-based PHF lookup.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
19#[repr(transparent)]
20pub struct MethodId(pub u16);
21
22impl MethodId {
23    /// Sentinel value for methods not known at compile time.
24    /// The VM falls back to string-based dispatch for this ID.
25    pub const DYNAMIC: MethodId = MethodId(0xFFFF);
26
27    /// Returns true if this is a dynamically-dispatched method (not resolved at compile time).
28    #[inline]
29    pub const fn is_dynamic(self) -> bool {
30        self.0 == 0xFFFF
31    }
32
33    /// Resolve a method name to a MethodId at compile time.
34    /// Returns `MethodId::DYNAMIC` for unknown method names.
35    pub fn from_name(name: &str) -> MethodId {
36        match name {
37            // === Universal intrinsics (0-4) ===
38            "type" => Self::TYPE,
39            "to_string" | "toString" => Self::TO_STRING,
40
41            // === Array methods — higher-order (10-20) ===
42            "map" => Self::MAP,
43            "filter" => Self::FILTER,
44            "reduce" => Self::REDUCE,
45            "forEach" => Self::FOR_EACH,
46            "find" => Self::FIND,
47            "findIndex" => Self::FIND_INDEX,
48            "some" => Self::SOME,
49            "every" => Self::EVERY,
50            "sort" => Self::SORT,
51            "groupBy" | "group_by" => Self::GROUP_BY,
52            "flatMap" => Self::FLAT_MAP,
53
54            // === Array methods — basic (30-39) ===
55            "len" => Self::LEN,
56            "length" => Self::LENGTH,
57            "first" => Self::FIRST,
58            "last" => Self::LAST,
59            "reverse" => Self::REVERSE,
60            "slice" => Self::SLICE,
61            "concat" => Self::CONCAT,
62            "take" => Self::TAKE,
63            "drop" => Self::DROP,
64            "skip" => Self::SKIP,
65
66            // === Array methods — search (40-41) ===
67            "indexOf" => Self::INDEX_OF,
68            "includes" => Self::INCLUDES,
69
70            // === Array methods — transform (50-56) ===
71            "join" => Self::JOIN,
72            "flatten" => Self::FLATTEN,
73            "unique" => Self::UNIQUE,
74            "distinct" => Self::DISTINCT,
75            "distinctBy" => Self::DISTINCT_BY,
76
77            // === Array methods — aggregation (60-64) ===
78            "sum" => Self::SUM,
79            "avg" => Self::AVG,
80            "min" => Self::MIN,
81            "max" => Self::MAX,
82            "count" => Self::COUNT,
83
84            // === Array methods — SQL-like query (70-79) ===
85            "where" => Self::WHERE,
86            "select" => Self::SELECT,
87            "orderBy" => Self::ORDER_BY,
88            "thenBy" => Self::THEN_BY,
89            "takeWhile" => Self::TAKE_WHILE,
90            "skipWhile" => Self::SKIP_WHILE,
91            "single" => Self::SINGLE,
92            "any" => Self::ANY,
93            "all" => Self::ALL,
94
95            // === Array methods — joins (80-82) ===
96            "innerJoin" => Self::INNER_JOIN,
97            "leftJoin" => Self::LEFT_JOIN,
98            "crossJoin" => Self::CROSS_JOIN,
99
100            // === Array methods — sets (85-87) ===
101            "union" => Self::UNION,
102            "intersect" => Self::INTERSECT,
103            "except" => Self::EXCEPT,
104
105            // === DataTable-specific methods (100-129) ===
106            "origin" => Self::ORIGIN,
107            "columns" => Self::COLUMNS,
108            "column" => Self::COLUMN,
109            "head" => Self::HEAD,
110            "tail" => Self::TAIL,
111            "mean" => Self::MEAN,
112            "describe" => Self::DESCRIBE,
113            "aggregate" => Self::AGGREGATE,
114            "index_by" | "indexBy" => Self::INDEX_BY,
115            "limit" => Self::LIMIT,
116            "execute" => Self::EXECUTE,
117            "simulate" => Self::SIMULATE,
118            "correlation" => Self::CORRELATION,
119            "covariance" => Self::COVARIANCE,
120            "rolling_sum" | "rollingSum" => Self::ROLLING_SUM,
121            "rolling_mean" | "rollingMean" => Self::ROLLING_MEAN,
122            "rolling_std" | "rollingStd" => Self::ROLLING_STD,
123            "diff" => Self::DIFF,
124            "pct_change" | "pctChange" => Self::PCT_CHANGE,
125            "forward_fill" | "forwardFill" => Self::FORWARD_FILL,
126
127            // === Column methods (130-133) ===
128            "std" => Self::STD,
129            "toArray" => Self::TO_ARRAY,
130            "abs" => Self::ABS,
131
132            // === IndexedTable methods (140-141) ===
133            "between" => Self::BETWEEN,
134            "resample" => Self::RESAMPLE,
135
136            // === Number methods (150-159) ===
137            "toFixed" | "to_fixed" => Self::TO_FIXED,
138            "toInt" | "to_int" => Self::TO_INT,
139            "toNumber" | "to_number" => Self::TO_NUMBER,
140            "floor" => Self::FLOOR,
141            "ceil" => Self::CEIL,
142            "round" => Self::ROUND,
143
144            // === String methods (160-174) ===
145            "toUpperCase" | "to_upper_case" => Self::TO_UPPER_CASE,
146            "toLowerCase" | "to_lower_case" => Self::TO_LOWER_CASE,
147            "trim" => Self::TRIM,
148            "contains" => Self::CONTAINS,
149            "startsWith" => Self::STARTS_WITH,
150            "endsWith" => Self::ENDS_WITH,
151            "split" => Self::SPLIT,
152            "replace" => Self::REPLACE,
153            "substring" => Self::SUBSTRING,
154
155            // === Option/Result methods (180-189) ===
156            "unwrap" => Self::UNWRAP,
157            "unwrapOr" => Self::UNWRAP_OR,
158            "isSome" => Self::IS_SOME,
159            "isNone" => Self::IS_NONE,
160            "isOk" => Self::IS_OK,
161            "isErr" => Self::IS_ERR,
162            "mapErr" => Self::MAP_ERR,
163
164            // === Array mutation methods (190-192) ===
165            "push" => Self::PUSH,
166            "pop" => Self::POP,
167            "isEmpty" => Self::IS_EMPTY,
168
169            _ => Self::DYNAMIC,
170        }
171    }
172
173    /// Get the canonical method name for a known MethodId.
174    /// Returns `None` for `DYNAMIC` IDs.
175    pub fn name(self) -> Option<&'static str> {
176        match self {
177            Self::TYPE => Some("type"),
178            Self::TO_STRING => Some("toString"),
179            Self::MAP => Some("map"),
180            Self::FILTER => Some("filter"),
181            Self::REDUCE => Some("reduce"),
182            Self::FOR_EACH => Some("forEach"),
183            Self::FIND => Some("find"),
184            Self::FIND_INDEX => Some("findIndex"),
185            Self::SOME => Some("some"),
186            Self::EVERY => Some("every"),
187            Self::SORT => Some("sort"),
188            Self::GROUP_BY => Some("groupBy"),
189            Self::FLAT_MAP => Some("flatMap"),
190            Self::LEN => Some("len"),
191            Self::LENGTH => Some("length"),
192            Self::FIRST => Some("first"),
193            Self::LAST => Some("last"),
194            Self::REVERSE => Some("reverse"),
195            Self::SLICE => Some("slice"),
196            Self::CONCAT => Some("concat"),
197            Self::TAKE => Some("take"),
198            Self::DROP => Some("drop"),
199            Self::SKIP => Some("skip"),
200            Self::INDEX_OF => Some("indexOf"),
201            Self::INCLUDES => Some("includes"),
202            Self::JOIN => Some("join"),
203            Self::FLATTEN => Some("flatten"),
204            Self::UNIQUE => Some("unique"),
205            Self::DISTINCT => Some("distinct"),
206            Self::DISTINCT_BY => Some("distinctBy"),
207            Self::SUM => Some("sum"),
208            Self::AVG => Some("avg"),
209            Self::MIN => Some("min"),
210            Self::MAX => Some("max"),
211            Self::COUNT => Some("count"),
212            Self::WHERE => Some("where"),
213            Self::SELECT => Some("select"),
214            Self::ORDER_BY => Some("orderBy"),
215            Self::THEN_BY => Some("thenBy"),
216            Self::TAKE_WHILE => Some("takeWhile"),
217            Self::SKIP_WHILE => Some("skipWhile"),
218            Self::SINGLE => Some("single"),
219            Self::ANY => Some("any"),
220            Self::ALL => Some("all"),
221            Self::INNER_JOIN => Some("innerJoin"),
222            Self::LEFT_JOIN => Some("leftJoin"),
223            Self::CROSS_JOIN => Some("crossJoin"),
224            Self::UNION => Some("union"),
225            Self::INTERSECT => Some("intersect"),
226            Self::EXCEPT => Some("except"),
227            Self::ORIGIN => Some("origin"),
228            Self::COLUMNS => Some("columns"),
229            Self::COLUMN => Some("column"),
230            Self::HEAD => Some("head"),
231            Self::TAIL => Some("tail"),
232            Self::MEAN => Some("mean"),
233            Self::DESCRIBE => Some("describe"),
234            Self::AGGREGATE => Some("aggregate"),
235            Self::INDEX_BY => Some("indexBy"),
236            Self::LIMIT => Some("limit"),
237            Self::EXECUTE => Some("execute"),
238            Self::SIMULATE => Some("simulate"),
239            Self::CORRELATION => Some("correlation"),
240            Self::COVARIANCE => Some("covariance"),
241            Self::ROLLING_SUM => Some("rollingSum"),
242            Self::ROLLING_MEAN => Some("rollingMean"),
243            Self::ROLLING_STD => Some("rollingStd"),
244            Self::DIFF => Some("diff"),
245            Self::PCT_CHANGE => Some("pctChange"),
246            Self::FORWARD_FILL => Some("forwardFill"),
247            Self::STD => Some("std"),
248            Self::TO_ARRAY => Some("toArray"),
249            Self::ABS => Some("abs"),
250            Self::BETWEEN => Some("between"),
251            Self::RESAMPLE => Some("resample"),
252            Self::TO_FIXED => Some("toFixed"),
253            Self::TO_INT => Some("toInt"),
254            Self::TO_NUMBER => Some("toNumber"),
255            Self::FLOOR => Some("floor"),
256            Self::CEIL => Some("ceil"),
257            Self::ROUND => Some("round"),
258            Self::TO_UPPER_CASE => Some("toUpperCase"),
259            Self::TO_LOWER_CASE => Some("toLowerCase"),
260            Self::TRIM => Some("trim"),
261            Self::CONTAINS => Some("contains"),
262            Self::STARTS_WITH => Some("startsWith"),
263            Self::ENDS_WITH => Some("endsWith"),
264            Self::SPLIT => Some("split"),
265            Self::REPLACE => Some("replace"),
266            Self::SUBSTRING => Some("substring"),
267            Self::UNWRAP => Some("unwrap"),
268            Self::UNWRAP_OR => Some("unwrapOr"),
269            Self::IS_SOME => Some("isSome"),
270            Self::IS_NONE => Some("isNone"),
271            Self::IS_OK => Some("isOk"),
272            Self::IS_ERR => Some("isErr"),
273            Self::MAP_ERR => Some("mapErr"),
274            Self::PUSH => Some("push"),
275            Self::POP => Some("pop"),
276            Self::IS_EMPTY => Some("isEmpty"),
277            _ => None,
278        }
279    }
280
281    // === Universal intrinsics ===
282    pub const TYPE: MethodId = MethodId(0);
283    pub const TO_STRING: MethodId = MethodId(1);
284
285    // === Array methods — higher-order ===
286    pub const MAP: MethodId = MethodId(10);
287    pub const FILTER: MethodId = MethodId(11);
288    pub const REDUCE: MethodId = MethodId(12);
289    pub const FOR_EACH: MethodId = MethodId(13);
290    pub const FIND: MethodId = MethodId(14);
291    pub const FIND_INDEX: MethodId = MethodId(15);
292    pub const SOME: MethodId = MethodId(16);
293    pub const EVERY: MethodId = MethodId(17);
294    pub const SORT: MethodId = MethodId(18);
295    pub const GROUP_BY: MethodId = MethodId(19);
296    pub const FLAT_MAP: MethodId = MethodId(20);
297
298    // === Array methods — basic ===
299    pub const LEN: MethodId = MethodId(30);
300    pub const LENGTH: MethodId = MethodId(31);
301    pub const FIRST: MethodId = MethodId(32);
302    pub const LAST: MethodId = MethodId(33);
303    pub const REVERSE: MethodId = MethodId(34);
304    pub const SLICE: MethodId = MethodId(35);
305    pub const CONCAT: MethodId = MethodId(36);
306    pub const TAKE: MethodId = MethodId(37);
307    pub const DROP: MethodId = MethodId(38);
308    pub const SKIP: MethodId = MethodId(39);
309
310    // === Array methods — search ===
311    pub const INDEX_OF: MethodId = MethodId(40);
312    pub const INCLUDES: MethodId = MethodId(41);
313
314    // === Array methods — transform ===
315    pub const JOIN: MethodId = MethodId(50);
316    pub const FLATTEN: MethodId = MethodId(51);
317    pub const UNIQUE: MethodId = MethodId(52);
318    pub const DISTINCT: MethodId = MethodId(53);
319    pub const DISTINCT_BY: MethodId = MethodId(54);
320
321    // === Array methods — aggregation ===
322    pub const SUM: MethodId = MethodId(60);
323    pub const AVG: MethodId = MethodId(61);
324    pub const MIN: MethodId = MethodId(62);
325    pub const MAX: MethodId = MethodId(63);
326    pub const COUNT: MethodId = MethodId(64);
327
328    // === Array methods — SQL-like query ===
329    pub const WHERE: MethodId = MethodId(70);
330    pub const SELECT: MethodId = MethodId(71);
331    pub const ORDER_BY: MethodId = MethodId(72);
332    pub const THEN_BY: MethodId = MethodId(73);
333    pub const TAKE_WHILE: MethodId = MethodId(74);
334    pub const SKIP_WHILE: MethodId = MethodId(75);
335    pub const SINGLE: MethodId = MethodId(76);
336    pub const ANY: MethodId = MethodId(77);
337    pub const ALL: MethodId = MethodId(78);
338
339    // === Array methods — joins ===
340    pub const INNER_JOIN: MethodId = MethodId(80);
341    pub const LEFT_JOIN: MethodId = MethodId(81);
342    pub const CROSS_JOIN: MethodId = MethodId(82);
343
344    // === Array methods — sets ===
345    pub const UNION: MethodId = MethodId(85);
346    pub const INTERSECT: MethodId = MethodId(86);
347    pub const EXCEPT: MethodId = MethodId(87);
348
349    // === DataTable-specific methods ===
350    pub const ORIGIN: MethodId = MethodId(100);
351    pub const COLUMNS: MethodId = MethodId(101);
352    pub const COLUMN: MethodId = MethodId(102);
353    pub const HEAD: MethodId = MethodId(103);
354    pub const TAIL: MethodId = MethodId(104);
355    pub const MEAN: MethodId = MethodId(105);
356    pub const DESCRIBE: MethodId = MethodId(106);
357    pub const AGGREGATE: MethodId = MethodId(107);
358    pub const INDEX_BY: MethodId = MethodId(108);
359    pub const LIMIT: MethodId = MethodId(109);
360    pub const EXECUTE: MethodId = MethodId(110);
361    pub const SIMULATE: MethodId = MethodId(111);
362    pub const CORRELATION: MethodId = MethodId(112);
363    pub const COVARIANCE: MethodId = MethodId(113);
364    pub const ROLLING_SUM: MethodId = MethodId(114);
365    pub const ROLLING_MEAN: MethodId = MethodId(115);
366    pub const ROLLING_STD: MethodId = MethodId(116);
367    pub const DIFF: MethodId = MethodId(117);
368    pub const PCT_CHANGE: MethodId = MethodId(118);
369    pub const FORWARD_FILL: MethodId = MethodId(119);
370
371    // === Column methods ===
372    pub const STD: MethodId = MethodId(130);
373    pub const TO_ARRAY: MethodId = MethodId(131);
374    pub const ABS: MethodId = MethodId(132);
375
376    // === IndexedTable methods ===
377    pub const BETWEEN: MethodId = MethodId(140);
378    pub const RESAMPLE: MethodId = MethodId(141);
379
380    // === Number methods ===
381    pub const TO_FIXED: MethodId = MethodId(150);
382    pub const TO_INT: MethodId = MethodId(151);
383    pub const TO_NUMBER: MethodId = MethodId(152);
384    pub const FLOOR: MethodId = MethodId(153);
385    pub const CEIL: MethodId = MethodId(154);
386    pub const ROUND: MethodId = MethodId(155);
387
388    // === String methods ===
389    pub const TO_UPPER_CASE: MethodId = MethodId(160);
390    pub const TO_LOWER_CASE: MethodId = MethodId(161);
391    pub const TRIM: MethodId = MethodId(162);
392    pub const CONTAINS: MethodId = MethodId(163);
393    pub const STARTS_WITH: MethodId = MethodId(164);
394    pub const ENDS_WITH: MethodId = MethodId(165);
395    pub const SPLIT: MethodId = MethodId(166);
396    pub const REPLACE: MethodId = MethodId(167);
397    pub const SUBSTRING: MethodId = MethodId(168);
398
399    // === Option/Result methods ===
400    pub const UNWRAP: MethodId = MethodId(180);
401    pub const UNWRAP_OR: MethodId = MethodId(181);
402    pub const IS_SOME: MethodId = MethodId(182);
403    pub const IS_NONE: MethodId = MethodId(183);
404    pub const IS_OK: MethodId = MethodId(184);
405    pub const IS_ERR: MethodId = MethodId(185);
406    pub const MAP_ERR: MethodId = MethodId(186);
407
408    // === Array mutation methods ===
409    pub const PUSH: MethodId = MethodId(190);
410    pub const POP: MethodId = MethodId(191);
411    pub const IS_EMPTY: MethodId = MethodId(192);
412}
413
414impl std::fmt::Display for MethodId {
415    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
416        if let Some(name) = self.name() {
417            write!(f, "{}(#{})", name, self.0)
418        } else if self.is_dynamic() {
419            write!(f, "<dynamic>")
420        } else {
421            write!(f, "<unknown #{}>", self.0)
422        }
423    }
424}
425
426#[cfg(test)]
427mod tests {
428    use super::*;
429
430    #[test]
431    fn test_known_methods_roundtrip() {
432        let methods = [
433            "map",
434            "filter",
435            "reduce",
436            "len",
437            "sum",
438            "avg",
439            "min",
440            "max",
441            "sort",
442            "first",
443            "last",
444            "push",
445            "pop",
446            "join",
447            "split",
448            "trim",
449            "replace",
450            "contains",
451            "toUpperCase",
452            "toLowerCase",
453            "type",
454            "toString",
455            "toFixed",
456            "floor",
457            "ceil",
458            "round",
459            "abs",
460        ];
461        for name in methods {
462            let id = MethodId::from_name(name);
463            assert!(!id.is_dynamic(), "expected known ID for '{}'", name);
464            assert!(id.name().is_some(), "expected name for '{}'", name);
465        }
466    }
467
468    #[test]
469    fn test_unknown_method_is_dynamic() {
470        let id = MethodId::from_name("nonexistent_method");
471        assert!(id.is_dynamic());
472        assert_eq!(id, MethodId::DYNAMIC);
473        assert!(id.name().is_none());
474    }
475
476    #[test]
477    fn test_aliases_resolve_to_same_id() {
478        assert_eq!(
479            MethodId::from_name("to_string"),
480            MethodId::from_name("toString")
481        );
482        assert_eq!(
483            MethodId::from_name("group_by"),
484            MethodId::from_name("groupBy")
485        );
486        assert_eq!(
487            MethodId::from_name("index_by"),
488            MethodId::from_name("indexBy")
489        );
490        assert_eq!(
491            MethodId::from_name("rollingSum"),
492            MethodId::from_name("rolling_sum")
493        );
494        assert_eq!(
495            MethodId::from_name("pctChange"),
496            MethodId::from_name("pct_change")
497        );
498        assert_eq!(
499            MethodId::from_name("forwardFill"),
500            MethodId::from_name("forward_fill")
501        );
502        assert_eq!(
503            MethodId::from_name("toFixed"),
504            MethodId::from_name("to_fixed")
505        );
506        assert_eq!(
507            MethodId::from_name("toUpperCase"),
508            MethodId::from_name("to_upper_case")
509        );
510    }
511
512    #[test]
513    fn test_display() {
514        assert_eq!(format!("{}", MethodId::MAP), "map(#10)");
515        assert_eq!(format!("{}", MethodId::DYNAMIC), "<dynamic>");
516        assert_eq!(format!("{}", MethodId(9999)), "<unknown #9999>");
517    }
518}