adze_ts_format_core/
lib.rs1#![forbid(unsafe_op_in_unsafe_fn)]
7#![deny(missing_docs)]
8#![cfg_attr(feature = "strict_api", deny(unreachable_pub))]
9#![cfg_attr(not(feature = "strict_api"), warn(unreachable_pub))]
10#![cfg_attr(feature = "strict_docs", deny(missing_docs))]
11#![cfg_attr(not(feature = "strict_docs"), allow(missing_docs))]
12
13use adze_glr_core::{Action, ParseTable};
14
15#[repr(u8)]
26#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
27pub enum TSActionTag {
28 Error = 0,
30 Shift = 1,
32 Recover = 2,
34 Reduce = 3,
36 Accept = 4,
38}
39
40#[must_use]
56pub fn choose_action(cell: &[Action]) -> Option<Action> {
57 if cell.is_empty() {
58 return None;
59 }
60
61 if let Some(a) = cell.iter().find(|a| matches!(a, Action::Accept)) {
62 return Some(a.clone());
63 }
64 if let Some(a) = cell.iter().find(|a| matches!(a, Action::Shift(_))) {
65 return Some(a.clone());
66 }
67 if let Some(a) = cell.iter().find(|a| matches!(a, Action::Reduce(_))) {
68 return Some(a.clone());
69 }
70 Some(Action::Error)
71}
72
73#[must_use]
77pub fn choose_action_with_precedence(cell: &[Action], parse_table: &ParseTable) -> Option<Action> {
78 if cell.is_empty() {
79 return None;
80 }
81
82 let mut sorted = cell.to_vec();
83 sorted.sort_by_key(|a| -action_priority(a, parse_table));
84 sorted.first().cloned()
85}
86
87#[inline]
88fn action_priority(action: &Action, parse_table: &ParseTable) -> i32 {
89 use Action::*;
90
91 if matches!(action, Accept) {
92 return 3_000_000;
93 }
94
95 let mut prec = 0i32;
96 if let Reduce(rid) = action {
97 if (rid.0 as usize) < parse_table.dynamic_prec_by_rule.len() {
98 prec = parse_table.dynamic_prec_by_rule[rid.0 as usize] as i32;
99 }
100
101 if (rid.0 as usize) < parse_table.rule_assoc_by_rule.len() {
102 prec = prec.saturating_add(parse_table.rule_assoc_by_rule[rid.0 as usize] as i32);
103 }
104
105 if prec > 0 {
106 return 2_000_000 + prec;
107 }
108 return 1_500_000 + prec;
109 }
110
111 if matches!(action, Shift(_)) {
112 return 2_000_000;
113 }
114
115 0
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn verify_action_tag_constants() {
124 assert_eq!(TSActionTag::Error as u8, 0, "Error tag must be 0");
125 assert_eq!(TSActionTag::Shift as u8, 1, "Shift tag must be 1");
126 assert_eq!(
127 TSActionTag::Reduce as u8,
128 3,
129 "Reduce tag must be 3 (Tree-sitter uses 2 for Recover)"
130 );
131 assert_eq!(TSActionTag::Accept as u8, 4, "Accept tag must be 4");
132
133 assert!(TSActionTag::Error < TSActionTag::Shift);
134 assert!(TSActionTag::Shift < TSActionTag::Reduce);
135 assert!(TSActionTag::Reduce < TSActionTag::Accept);
136 }
137
138 #[test]
139 fn choose_action_prefers_shift_over_reduce() {
140 use adze_glr_core::{RuleId, StateId};
141
142 let cell = vec![
143 Action::Shift(StateId(1)),
144 Action::Reduce(RuleId(0)),
145 Action::Accept,
146 Action::Error,
147 ];
148
149 let chosen = choose_action(&cell).expect("expected one action");
150 assert_eq!(chosen, Action::Accept);
151 }
152}