1use rustc_hash::FxHashMap;
17use shuck_ast::{BuiltinCommand, CaseTerminator, Command, CompoundCommand, Span};
18use shuck_parser::ZshEmulationMode;
19use std::marker::PhantomData;
20
21use crate::cfg::FlowContext;
22use crate::source_closure::SourcePathTemplate;
23use crate::{BindingId, ScopeId, SpanKey};
24
25#[derive(Debug, Clone, Default)]
26pub(crate) struct RecordedProgram {
27 file_commands: RecordedCommandRange,
28 function_bodies: FxHashMap<ScopeId, RecordedCommandRange>,
29 commands: Vec<RecordedCommand>,
30 command_sequence_items: Vec<CommandId>,
31 statement_sequence_commands: Vec<StatementSequenceCommand>,
32 isolated_regions: Vec<IsolatedRegion>,
33 case_arms: Vec<RecordedCaseArm>,
34 pipeline_segments: Vec<RecordedPipelineSegment>,
35 list_items: Vec<RecordedListItem>,
36 elif_branches: Vec<RecordedElifBranch>,
37 command_info_records: Vec<RecordedCommandInfo>,
38 pub(crate) command_infos: FxHashMap<SpanKey, RecordedCommandInfoId>,
39 pub(crate) function_body_scopes: FxHashMap<BindingId, ScopeId>,
40 pub(crate) call_command_spans: FxHashMap<SpanKey, Span>,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub struct StatementSequenceCommand {
46 body_span: Span,
47 stmt_span: Span,
48}
49
50impl StatementSequenceCommand {
51 pub fn body_span(&self) -> Span {
53 self.body_span
54 }
55
56 pub fn stmt_span(&self) -> Span {
58 self.stmt_span
59 }
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
64pub struct CommandId(pub(crate) u32);
65
66impl CommandId {
67 pub fn index(self) -> usize {
69 self.0 as usize
70 }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
74pub(crate) struct RecordedCommandInfoId(u32);
75
76impl RecordedCommandInfoId {
77 fn index(self) -> usize {
78 self.0 as usize
79 }
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
84pub enum CommandKind {
85 Simple,
87 Builtin(BuiltinCommandKind),
89 Decl,
91 Binary,
93 Compound(CompoundCommandKind),
95 Function,
97 AnonymousFunction,
99}
100
101impl CommandKind {
102 pub fn from_command(command: &Command) -> Self {
104 match command {
105 Command::Simple(_) => Self::Simple,
106 Command::Builtin(command) => Self::Builtin(BuiltinCommandKind::from_builtin(command)),
107 Command::Decl(_) => Self::Decl,
108 Command::Binary(_) => Self::Binary,
109 Command::Compound(command) => {
110 Self::Compound(CompoundCommandKind::from_compound(command))
111 }
112 Command::Function(_) => Self::Function,
113 Command::AnonymousFunction(_) => Self::AnonymousFunction,
114 }
115 }
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
120pub enum BuiltinCommandKind {
121 Break,
123 Continue,
125 Return,
127 Exit,
129}
130
131impl BuiltinCommandKind {
132 fn from_builtin(command: &BuiltinCommand) -> Self {
133 match command {
134 BuiltinCommand::Break(_) => Self::Break,
135 BuiltinCommand::Continue(_) => Self::Continue,
136 BuiltinCommand::Return(_) => Self::Return,
137 BuiltinCommand::Exit(_) => Self::Exit,
138 }
139 }
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
144pub enum CompoundCommandKind {
145 If,
147 For,
149 Repeat,
151 Foreach,
153 ArithmeticFor,
155 While,
157 Until,
159 Case,
161 Select,
163 Subshell,
165 BraceGroup,
167 Arithmetic,
169 Time,
171 Conditional,
173 Coproc,
175 Always,
177}
178
179impl CompoundCommandKind {
180 fn from_compound(command: &CompoundCommand) -> Self {
181 match command {
182 CompoundCommand::If(_) => Self::If,
183 CompoundCommand::For(_) => Self::For,
184 CompoundCommand::Repeat(_) => Self::Repeat,
185 CompoundCommand::Foreach(_) => Self::Foreach,
186 CompoundCommand::ArithmeticFor(_) => Self::ArithmeticFor,
187 CompoundCommand::While(_) => Self::While,
188 CompoundCommand::Until(_) => Self::Until,
189 CompoundCommand::Case(_) => Self::Case,
190 CompoundCommand::Select(_) => Self::Select,
191 CompoundCommand::Subshell(_) => Self::Subshell,
192 CompoundCommand::BraceGroup(_) => Self::BraceGroup,
193 CompoundCommand::Arithmetic(_) => Self::Arithmetic,
194 CompoundCommand::Time(_) => Self::Time,
195 CompoundCommand::Conditional(_) => Self::Conditional,
196 CompoundCommand::Coproc(_) => Self::Coproc,
197 CompoundCommand::Always(_) => Self::Always,
198 }
199 }
200}
201
202#[derive(Debug, Clone, Copy, PartialEq, Eq)]
203pub(crate) struct RecordedRange<T> {
204 start: u32,
205 len: u32,
206 marker: PhantomData<fn() -> T>,
207}
208
209impl<T> RecordedRange<T> {
210 pub(crate) const fn empty() -> Self {
211 Self {
212 start: 0,
213 len: 0,
214 marker: PhantomData,
215 }
216 }
217
218 pub(crate) fn new(start: usize, len: usize) -> Self {
219 Self {
220 start: match u32::try_from(start) {
221 Ok(start) => start,
222 Err(err) => panic!("recorded IR start fits in u32: {err}"),
223 },
224 len: match u32::try_from(len) {
225 Ok(len) => len,
226 Err(err) => panic!("recorded IR length fits in u32: {err}"),
227 },
228 marker: PhantomData,
229 }
230 }
231
232 pub(crate) fn len(self) -> usize {
233 self.len as usize
234 }
235
236 pub(crate) fn is_empty(&self) -> bool {
237 self.len == 0
238 }
239
240 fn slice(self, store: &[T]) -> &[T] {
241 let start = self.start as usize;
242 &store[start..start + self.len()]
243 }
244}
245
246impl<T> Default for RecordedRange<T> {
247 fn default() -> Self {
248 Self::empty()
249 }
250}
251
252pub(crate) type RecordedCommandRange = RecordedRange<CommandId>;
253pub(crate) type RecordedRegionRange = RecordedRange<IsolatedRegion>;
254pub(crate) type RecordedCaseArmRange = RecordedRange<RecordedCaseArm>;
255pub(crate) type RecordedPipelineSegmentRange = RecordedRange<RecordedPipelineSegment>;
256pub(crate) type RecordedListItemRange = RecordedRange<RecordedListItem>;
257pub(crate) type RecordedElifBranchRange = RecordedRange<RecordedElifBranch>;
258
259#[derive(Debug, Clone, Copy)]
260pub(crate) struct RecordedCommand {
261 pub(crate) span: Span,
262 pub(crate) syntax_span: Span,
263 pub(crate) syntax_kind: Option<CommandKind>,
264 pub(crate) scope: Option<ScopeId>,
265 pub(crate) flow_context: Option<FlowContext>,
266 pub(crate) nested_regions: RecordedRegionRange,
267 pub(crate) command_info: Option<RecordedCommandInfoId>,
268 pub(crate) kind: RecordedCommandKind,
269}
270
271#[derive(Debug, Clone, Copy)]
272pub(crate) struct IsolatedRegion {
273 pub(crate) scope: ScopeId,
274 pub(crate) commands: RecordedCommandRange,
275}
276
277#[derive(Debug, Clone, Copy)]
278pub(crate) enum RecordedCommandKind {
279 Linear,
280 Break {
281 depth: usize,
282 },
283 Continue {
284 depth: usize,
285 },
286 Return,
287 Exit,
288 List {
289 first: CommandId,
290 rest: RecordedListItemRange,
291 },
292 If {
293 condition: RecordedCommandRange,
294 then_branch: RecordedCommandRange,
295 elif_branches: RecordedElifBranchRange,
296 else_branch: RecordedCommandRange,
297 },
298 While {
299 condition: RecordedCommandRange,
300 body: RecordedCommandRange,
301 },
302 Until {
303 condition: RecordedCommandRange,
304 body: RecordedCommandRange,
305 },
306 For {
307 body: RecordedCommandRange,
308 },
309 Select {
310 body: RecordedCommandRange,
311 },
312 ArithmeticFor {
313 body: RecordedCommandRange,
314 },
315 Case {
316 arms: RecordedCaseArmRange,
317 },
318 BraceGroup {
319 body: RecordedCommandRange,
320 },
321 Always {
322 body: RecordedCommandRange,
323 always_body: RecordedCommandRange,
324 },
325 Subshell {
326 body: RecordedCommandRange,
327 },
328 Pipeline {
329 segments: RecordedPipelineSegmentRange,
330 },
331}
332
333#[derive(Debug, Clone, Copy)]
334pub(crate) struct RecordedCaseArm {
335 pub(crate) terminator: CaseTerminator,
336 pub(crate) matches_anything: bool,
337 pub(crate) commands: RecordedCommandRange,
338}
339
340#[derive(Debug, Clone, Copy)]
341pub(crate) struct RecordedPipelineSegment {
342 pub(crate) operator_before: Option<RecordedPipelineOperator>,
343 pub(crate) scope: ScopeId,
344 pub(crate) command: CommandId,
345}
346
347#[derive(Debug, Clone, Copy)]
348pub(crate) struct RecordedListItem {
349 pub(crate) operator: RecordedListOperator,
350 pub(crate) operator_span: Span,
351 pub(crate) command: CommandId,
352}
353
354#[derive(Debug, Clone, Copy)]
355pub(crate) struct RecordedPipelineOperator {
356 pub(crate) operator: RecordedPipelineOperatorKind,
357 pub(crate) span: Span,
358}
359
360#[derive(Debug, Clone, Copy, PartialEq, Eq)]
361pub(crate) enum RecordedPipelineOperatorKind {
362 Pipe,
363 PipeAll,
364}
365
366#[derive(Debug, Clone, Copy)]
367pub(crate) struct RecordedElifBranch {
368 pub(crate) condition: RecordedCommandRange,
369 pub(crate) body: RecordedCommandRange,
370}
371
372#[derive(Debug, Clone, Default)]
373pub(crate) struct RecordedCommandInfo {
374 pub(crate) static_callee: Option<compact_str::CompactString>,
375 pub(crate) dynamic_name_span: Option<Span>,
376 pub(crate) static_args: Box<[Option<String>]>,
377 pub(crate) source_path_template: Option<SourcePathTemplate>,
378 pub(crate) zsh_effects: Vec<RecordedZshCommandEffect>,
379}
380
381impl RecordedCommandInfo {
382 pub(crate) fn is_empty(&self) -> bool {
383 self.static_callee.is_none()
384 && self.dynamic_name_span.is_none()
385 && self.static_args.is_empty()
386 && self.source_path_template.is_none()
387 && self.zsh_effects.is_empty()
388 }
389}
390
391#[derive(Debug, Clone)]
392pub(crate) enum RecordedZshCommandEffect {
393 Emulate {
394 mode: ZshEmulationMode,
395 local: bool,
396 },
397 EmulateUnknown {
398 local: bool,
399 },
400 SetOptions {
401 updates: Vec<RecordedZshOptionUpdate>,
402 },
403}
404
405#[derive(Debug, Clone, PartialEq, Eq)]
406pub(crate) enum RecordedZshOptionUpdate {
407 Named { name: Box<str>, enable: bool },
408 UnknownName,
409 LocalOptions { enable: bool },
410}
411
412#[derive(Debug, Clone, Copy, PartialEq, Eq)]
413pub(crate) enum RecordedListOperator {
414 And,
415 Or,
416}
417
418impl RecordedProgram {
419 pub(crate) fn file_commands(&self) -> RecordedCommandRange {
420 self.file_commands
421 }
422
423 pub(crate) fn set_file_commands(&mut self, commands: RecordedCommandRange) {
424 self.file_commands = commands;
425 }
426
427 pub(crate) fn function_body(&self, scope: ScopeId) -> RecordedCommandRange {
428 self.function_bodies
429 .get(&scope)
430 .copied()
431 .unwrap_or_default()
432 }
433
434 pub(crate) fn set_function_body(&mut self, scope: ScopeId, commands: RecordedCommandRange) {
435 self.function_bodies.insert(scope, commands);
436 }
437
438 pub(crate) fn function_bodies(&self) -> &FxHashMap<ScopeId, RecordedCommandRange> {
439 &self.function_bodies
440 }
441
442 pub(crate) fn command(&self, id: CommandId) -> &RecordedCommand {
443 &self.commands[id.index()]
444 }
445
446 pub(crate) fn command_mut(&mut self, id: CommandId) -> &mut RecordedCommand {
447 &mut self.commands[id.index()]
448 }
449
450 pub(crate) fn command_info(&self, id: RecordedCommandInfoId) -> &RecordedCommandInfo {
451 &self.command_info_records[id.index()]
452 }
453
454 pub(crate) fn command_info_for_span(&self, span: Span) -> Option<&RecordedCommandInfo> {
455 self.command_infos
456 .get(&SpanKey::new(span))
457 .map(|id| self.command_info(*id))
458 }
459
460 pub(crate) fn commands(&self) -> &[RecordedCommand] {
461 &self.commands
462 }
463
464 pub(crate) fn commands_in(&self, range: RecordedCommandRange) -> &[CommandId] {
465 range.slice(&self.command_sequence_items)
466 }
467
468 pub fn statement_sequence_commands(&self) -> &[StatementSequenceCommand] {
469 &self.statement_sequence_commands
470 }
471
472 pub(crate) fn push_statement_sequence_command(&mut self, body_span: Span, stmt_span: Span) {
473 self.statement_sequence_commands
474 .push(StatementSequenceCommand {
475 body_span,
476 stmt_span,
477 });
478 }
479
480 pub(crate) fn nested_regions(&self, range: RecordedRegionRange) -> &[IsolatedRegion] {
481 range.slice(&self.isolated_regions)
482 }
483
484 pub(crate) fn case_arms(&self, range: RecordedCaseArmRange) -> &[RecordedCaseArm] {
485 range.slice(&self.case_arms)
486 }
487
488 pub(crate) fn pipeline_segments(
489 &self,
490 range: RecordedPipelineSegmentRange,
491 ) -> &[RecordedPipelineSegment] {
492 range.slice(&self.pipeline_segments)
493 }
494
495 pub(crate) fn list_items(&self, range: RecordedListItemRange) -> &[RecordedListItem] {
496 range.slice(&self.list_items)
497 }
498
499 pub(crate) fn elif_branches(&self, range: RecordedElifBranchRange) -> &[RecordedElifBranch] {
500 range.slice(&self.elif_branches)
501 }
502
503 pub(crate) fn push_command(&mut self, command: RecordedCommand) -> CommandId {
504 let id = CommandId(match u32::try_from(self.commands.len()) {
505 Ok(id) => id,
506 Err(err) => panic!("recorded command count fits in u32: {err}"),
507 });
508 self.commands.push(command);
509 id
510 }
511
512 pub(crate) fn push_command_info(&mut self, info: RecordedCommandInfo) -> RecordedCommandInfoId {
513 let id = RecordedCommandInfoId(match u32::try_from(self.command_info_records.len()) {
514 Ok(id) => id,
515 Err(err) => panic!("recorded command info count fits in u32: {err}"),
516 });
517 self.command_info_records.push(info);
518 id
519 }
520
521 pub(crate) fn push_command_ids(&mut self, command_ids: Vec<CommandId>) -> RecordedCommandRange {
522 push_range(&mut self.command_sequence_items, command_ids)
523 }
524
525 pub(crate) fn push_regions(&mut self, regions: Vec<IsolatedRegion>) -> RecordedRegionRange {
526 push_range(&mut self.isolated_regions, regions)
527 }
528
529 pub(crate) fn push_case_arms(&mut self, arms: Vec<RecordedCaseArm>) -> RecordedCaseArmRange {
530 push_range(&mut self.case_arms, arms)
531 }
532
533 pub(crate) fn push_pipeline_segments(
534 &mut self,
535 segments: Vec<RecordedPipelineSegment>,
536 ) -> RecordedPipelineSegmentRange {
537 push_range(&mut self.pipeline_segments, segments)
538 }
539
540 pub(crate) fn push_list_items(
541 &mut self,
542 list_items: Vec<RecordedListItem>,
543 ) -> RecordedListItemRange {
544 push_range(&mut self.list_items, list_items)
545 }
546
547 pub(crate) fn push_elif_branches(
548 &mut self,
549 elif_branches: Vec<RecordedElifBranch>,
550 ) -> RecordedElifBranchRange {
551 push_range(&mut self.elif_branches, elif_branches)
552 }
553}
554
555fn push_range<T>(store: &mut Vec<T>, mut items: Vec<T>) -> RecordedRange<T> {
556 if items.is_empty() {
557 return RecordedRange::empty();
558 }
559
560 let start = store.len();
561 store.append(&mut items);
562 RecordedRange::new(start, store.len() - start)
563}