Skip to main content

typr_core/processes/parsing/
mod.rs

1#![allow(dead_code)]
2
3pub mod elements;
4pub mod indexation;
5pub mod lang_token;
6pub mod operation_priority;
7pub mod type_token;
8pub mod types;
9pub mod vector_priority;
10
11use crate::components::context::config::Config;
12use crate::components::error_message::help_data::HelpData;
13use crate::components::error_message::syntax_error::SyntaxError;
14use crate::components::language::operators::custom_op;
15use crate::components::language::operators::Op;
16use crate::components::language::var::Var;
17use crate::components::language::Lang;
18use crate::components::language::ModulePosition;
19use crate::components::r#type::vector_type::ConstructorCategory;
20use crate::components::r#type::Type;
21use crate::processes::parsing::elements::break_exp;
22use crate::processes::parsing::elements::chars;
23use crate::processes::parsing::elements::next_exp;
24use crate::processes::parsing::elements::parse_elements;
25use crate::processes::parsing::elements::return_exp;
26use crate::processes::parsing::elements::scope;
27use crate::processes::parsing::elements::single_element;
28use crate::processes::parsing::elements::tag_exp;
29use crate::processes::parsing::elements::tuple_exp;
30use crate::processes::parsing::elements::variable;
31use crate::processes::parsing::elements::variable2;
32use crate::processes::parsing::elements::variable_exp;
33use crate::processes::parsing::elements::variable_recognizer;
34use crate::processes::parsing::elements::vector;
35use crate::processes::parsing::elements::Case;
36use crate::processes::parsing::types::ltype;
37use crate::processes::parsing::types::pascal_case_no_space;
38use crate::processes::parsing::types::single_letter_type_alias;
39use crate::processes::parsing::types::type_alias;
40use nom::branch::alt;
41use nom::bytes::complete::tag;
42use nom::bytes::complete::take_while1;
43use nom::character::complete::line_ending;
44use nom::character::complete::multispace0;
45use nom::character::complete::multispace1;
46use nom::character::complete::not_line_ending;
47use nom::combinator::map;
48use nom::combinator::opt;
49use nom::multi::many0;
50use nom::multi::many1;
51use nom::multi::separated_list0;
52use nom::sequence::delimited;
53use nom::sequence::pair;
54use nom::sequence::preceded;
55use nom::sequence::terminated;
56use nom::IResult;
57use nom::Parser;
58use nom_locate::LocatedSpan;
59use std::ops::Deref;
60
61type Span<'a> = LocatedSpan<&'a str, String>;
62
63use std::cell::RefCell;
64
65thread_local! {
66    static PARSE_ERRORS: RefCell<Vec<SyntaxError>> = const { RefCell::new(Vec::new()) };
67}
68
69pub fn push_parse_error(err: SyntaxError) {
70    PARSE_ERRORS.with(|e| e.borrow_mut().push(err));
71}
72
73fn take_parse_errors() -> Vec<SyntaxError> {
74    PARSE_ERRORS.with(|e| e.borrow_mut().drain(..).collect())
75}
76
77/// Result of parsing containing the AST and any syntax errors collected
78#[derive(Debug, Clone)]
79pub struct ParseResult {
80    pub ast: Lang,
81    pub errors: Vec<SyntaxError>,
82}
83
84impl ParseResult {
85    pub fn new(ast: Lang) -> Self {
86        let errors = take_parse_errors();
87        ParseResult { ast, errors }
88    }
89
90    pub fn has_errors(&self) -> bool {
91        !self.errors.is_empty()
92    }
93
94    pub fn get_ast(&self) -> &Lang {
95        &self.ast
96    }
97
98    pub fn get_clean_ast(&self) -> Lang {
99        self.ast.clone()
100    }
101}
102
103fn pattern_var(s: Span) -> IResult<Span, (Vec<Lang>, Option<String>)> {
104    let res = alt((tag_exp, variable2)).parse(s);
105    match res {
106        Ok((s, Lang::Tag { name, value: val, .. })) => {
107            if let Lang::Variable {
108                name: name2,
109                is_opaque: mutopa,
110                related_type: typ,
111                help_data: h,
112            } = *val
113            {
114                Ok((
115                    s,
116                    (
117                        vec![Lang::Variable {
118                            name: name2.to_string(),
119                            is_opaque: mutopa,
120                            related_type: typ,
121                            help_data: h.clone(),
122                        }],
123                        Some(name.to_string()),
124                    ),
125                ))
126            } else {
127                Ok((s, (vec![], Some(name.to_string()))))
128            }
129        }
130        Ok((
131            s,
132            Lang::Variable {
133                name,
134                is_opaque: mutopa,
135                related_type: typ,
136                help_data: h,
137            },
138        )) => Ok((
139            s,
140            (
141                vec![Lang::Variable {
142                    name,
143                    is_opaque: mutopa,
144                    related_type: typ,
145                    help_data: h.clone(),
146                }],
147                None,
148            ),
149        )),
150        Err(r) => Err(r),
151        _ => todo!(),
152    }
153}
154
155fn single_parse(s: Span) -> IResult<Span, Lang> {
156    let res = (parse_elements, opt(terminated(tag(";"), multispace0))).parse(s);
157    match res {
158        Ok((s, (exp, Some(_)))) => Ok((s, exp)),
159        Ok((s, (exp, None))) => {
160            push_parse_error(SyntaxError::ForgottenSemicolon(exp.clone().into()));
161            Ok((s, exp))
162        }
163        Err(r) => Err(r),
164    }
165}
166
167fn equality_operator(s: Span) -> IResult<Span, Span> {
168    terminated(alt((tag("="), tag("<-"))), multispace0).parse(s)
169}
170
171fn base_let_exp(s: Span) -> IResult<Span, Vec<Lang>> {
172    let res = (
173        terminated(tag("let"), multispace0),
174        pattern_var,
175        opt(preceded(terminated(tag(":"), multispace0), ltype)),
176        equality_operator,
177        single_parse,
178    )
179        .parse(s);
180    match res {
181        Ok((
182            s,
183            (
184                _let,
185                (pat_var, None),
186                typ,
187                _eq,
188                Lang::Function {
189                    parameters: params,
190                    return_type: ty,
191                    body,
192                    help_data: h,
193                },
194            ),
195        )) if !params.is_empty() => {
196            let newvar = Var::from_language(pat_var[0].clone())
197                .unwrap()
198                .set_type(params[0].1.clone());
199            if let Lang::Variable { name, help_data, .. } = &pat_var[0] {
200                if name.chars().next().is_some_and(|c| c.is_uppercase()) {
201                    push_parse_error(SyntaxError::LetInsteadOfType {
202                        name: name.clone(),
203                        help_data: help_data.clone(),
204                    });
205                }
206            }
207            Ok((
208                s,
209                vec![Lang::Let {
210                    variable: Box::new(newvar.to_language()),
211                    r#type: typ.unwrap_or(Type::Empty(HelpData::default())),
212                    expression: Box::new(Lang::Function {
213                        parameters: params,
214                        return_type: ty,
215                        body,
216                        help_data: h,
217                    }),
218                    is_public: false,
219                    is_testable: false,
220                    is_export: false,
221                    help_data: _let.into(),
222                }],
223            ))
224        }
225        Ok((s, (_let, (pat_var, None), typ, _eq, body))) => {
226            if let Lang::Variable { name, help_data, .. } = &pat_var[0] {
227                if name.chars().next().is_some_and(|c| c.is_uppercase()) {
228                    push_parse_error(SyntaxError::LetInsteadOfType {
229                        name: name.clone(),
230                        help_data: help_data.clone(),
231                    });
232                }
233            }
234            Ok((
235                s,
236                vec![Lang::Let {
237                    variable: Box::new(pat_var[0].clone()),
238                    r#type: typ.clone().unwrap_or(Type::Empty(HelpData::default())),
239                    expression: Box::new(body),
240                    is_public: false,
241                    is_testable: false,
242                    is_export: false,
243                    help_data: _let.into(),
244                }],
245            ))
246        }
247        Ok((s, (_let, (pat_var, Some(_)), typ, eq, body))) => {
248            if pat_var.len() == 1 {
249                Ok((
250                    s,
251                    vec![Lang::Let {
252                        variable: Box::new(pat_var[0].clone()),
253                        r#type: typ.clone().unwrap_or(Type::Empty(HelpData::default())),
254                        expression: Box::new(Lang::Operator {
255                            operator: Op::Dollar(HelpData::default()),
256                            rhs: Box::new(Lang::Number {
257                                value: 0.0,
258                                help_data: eq.into(),
259                            }),
260                            lhs: Box::new(body),
261                            help_data: pat_var.into(),
262                        }),
263                        is_public: false,
264                        is_testable: false,
265                        is_export: false,
266                        help_data: _let.into(),
267                    }],
268                ))
269            } else {
270                Ok((
271                    s,
272                    pat_var
273                        .iter()
274                        .map(|x| Lang::Let {
275                            variable: Box::new(x.clone()),
276                            r#type: typ.clone().unwrap_or(Type::Empty(HelpData::default())),
277                            expression: Box::new(body.clone()),
278                            is_public: false,
279                            is_testable: false,
280                            is_export: false,
281                            help_data: HelpData::default(),
282                        })
283                        .collect::<Vec<_>>(),
284                ))
285            }
286        }
287        Err(r) => Err(r),
288    }
289}
290
291fn let_tuple_exp(s: Span) -> IResult<Span, Vec<Lang>> {
292    let res = (
293        terminated(tag("let"), multispace0),
294        tuple_exp,
295        opt(preceded(terminated(tag(":"), multispace0), ltype)),
296        equality_operator,
297        single_parse,
298    )
299        .parse(s);
300    match res {
301        Ok((
302            s,
303            (
304                _let,
305                Lang::Tuple {
306                    value: elements,
307                    help_data: _th,
308                },
309                typ,
310                _eq,
311                body,
312            ),
313        )) => {
314            // Arity check only when the source is syntactically a tuple literal
315            // (`:{...}`) — the only case where the element count is known without
316            // type-checking. A generic expression (function call, variable, ...)
317            // still falls through to the existing generic tuple-indexing checks.
318            if let Lang::Tuple {
319                value: body_elements, ..
320            } = &body
321            {
322                if elements.len() != body_elements.len() {
323                    push_parse_error(SyntaxError::TupleDestructureArityMismatch {
324                        expected: elements.len(),
325                        found: body_elements.len(),
326                        help_data: _th.clone(),
327                    });
328                }
329            }
330
331            let tmp_name = "__tuple_tmp__";
332            let tmp_var = Var::from_name(tmp_name).to_language();
333
334            // First: let __tuple_tmp__ <- body;
335            let tmp_let = Lang::Let {
336                variable: Box::new(tmp_var.clone()),
337                r#type: typ.unwrap_or(Type::Empty(HelpData::default())),
338                expression: Box::new(body),
339                is_public: false,
340                is_testable: false,
341                is_export: false,
342                help_data: _let.into(),
343            };
344
345            // Then: let a <- 1.__tuple_tmp__; let b <- 2.__tuple_tmp__; ...
346            let mut result = vec![tmp_let];
347            for (i, elem) in elements.iter().enumerate() {
348                if let Lang::Variable { name, .. } = elem {
349                    if name == "_" {
350                        continue; // skip wildcard
351                    }
352                }
353                result.push(Lang::Let {
354                    variable: Box::new(elem.clone()),
355                    r#type: Type::Empty(HelpData::default()),
356                    expression: Box::new(Lang::Operator {
357                        operator: Op::Dot(HelpData::default()),
358                        rhs: Box::new(Lang::Integer {
359                            value: (i + 1) as i32,
360                            help_data: HelpData::default(),
361                        }),
362                        lhs: Box::new(tmp_var.clone()),
363                        help_data: HelpData::default(),
364                    }),
365                    is_public: false,
366                    is_testable: false,
367                    is_export: false,
368                    help_data: HelpData::default(),
369                });
370            }
371
372            Ok((s, result))
373        }
374        Ok(_) => unreachable!("tuple_exp always returns Lang::Tuple"),
375        Err(r) => Err(r),
376    }
377}
378
379fn let_exp(s: Span) -> IResult<Span, Vec<Lang>> {
380    let res = (
381        opt(terminated(
382            alt((tag("@export"), tag("@pub"), tag("@testable"))),
383            multispace0,
384        )),
385        base_let_exp,
386    )
387        .parse(s);
388    match res {
389        Ok((s, (None, le))) => Ok((s, le)),
390        Ok((s, (Some(annotation), le))) => {
391            // `@export` → public + testable + exported in R package (RFC-TR-032).
392            // `@pub`    → public + testable (RFC-TR-032, §3.2).
393            // `@testable` → private but exposed as `M$.test_<name>` in test builds.
394            let frag = *annotation.fragment();
395            let is_pub = frag == "@pub" || frag == "@export";
396            let is_test = frag == "@testable" || frag == "@pub" || frag == "@export";
397            let is_exp = frag == "@export";
398            let new_le = le
399                .iter()
400                .map(|x| match x {
401                    Lang::Let {
402                        variable: var,
403                        r#type: typ,
404                        expression: body,
405                        is_public: _,
406                        is_testable: _,
407                        is_export: _,
408                        help_data: h,
409                    } => {
410                        let vari = Var::from_language(var.deref().clone()).unwrap().to_language();
411                        Lang::Let {
412                            variable: Box::new(vari),
413                            r#type: typ.clone(),
414                            expression: body.clone(),
415                            is_public: is_pub,
416                            is_testable: is_test,
417                            is_export: is_exp,
418                            help_data: h.clone(),
419                        }
420                    }
421                    lan => lan.clone(),
422                })
423                .collect();
424            Ok((s, new_le))
425        }
426        Err(r) => Err(r),
427    }
428}
429
430/// Parses a `typeconstructor` declaration that registers a new type constructor:
431///   `typeconstructor Tibble[N] record;`
432///   `typeconstructor Matrix[N, M, T] recursive;`
433fn typeconstructor_exp(s: Span) -> IResult<Span, Vec<Lang>> {
434    let res = (
435        terminated(tag("typeconstructor"), multispace0),
436        pascal_case_no_space,
437        delimited(
438            terminated(tag("["), multispace0),
439            separated_list0(terminated(tag(","), multispace0), terminated(ltype, multispace0)),
440            terminated(tag("]"), multispace0),
441        ),
442        terminated(alt((tag("recursive"), tag("record"))), multispace0),
443        terminated(tag(";"), multispace0),
444    )
445        .parse(s);
446    match res {
447        Ok((s, (_kw, (name, h), params, category, _semi))) => {
448            let category = match *category.fragment() {
449                "recursive" => ConstructorCategory::Recursive,
450                _ => ConstructorCategory::Record,
451            };
452            Ok((
453                s,
454                vec![Lang::TypeConstructor {
455                    name,
456                    parameters: params,
457                    category,
458                    help_data: h,
459                }],
460            ))
461        }
462        Err(r) => Err(r),
463    }
464}
465
466/// Detects `type X <- ...;` / `opaque X <- ...;` where the alias name is a single
467/// uppercase letter — reserved for generic type variables (`T`, `U`, ...) elsewhere in
468/// the grammar. Without this dedicated parser, `type_alias`/`base_type_exp` simply fail
469/// to match (`pascal_case_no_space` requires 2+ characters) and the whole statement falls
470/// through to a much later, permissive alternative that treats the bare `type`/`opaque`
471/// keyword as a variable reference — silently dropping the rest of the line with no error
472/// (see bug_single_letter_alias_name_unparseable). Parses the full statement so nothing is
473/// lost from the AST, but flags it as a fatal `SingleLetterTypeName` error: single-letter
474/// alias names are forbidden outright (not legalized) to avoid colliding with generics.
475fn single_letter_type_name_exp(s: Span) -> IResult<Span, Vec<Lang>> {
476    let res = (
477        opt(terminated(alt((tag("@export"), tag("@pub"))), multispace0)),
478        terminated(alt((tag("type"), tag("opaque"))), multispace0),
479        single_letter_type_alias,
480        equality_operator,
481        ltype,
482        terminated(tag(";"), multispace0),
483    )
484        .parse(s);
485    match res {
486        Ok((s, (_pub, _kw, Type::Alias(name, params, _, h), _eq, ty, _))) => {
487            push_parse_error(SyntaxError::SingleLetterTypeName {
488                name: name.clone(),
489                help_data: h.clone(),
490            });
491            let h2 = if !params.is_empty() {
492                params[0].clone().into()
493            } else {
494                HelpData::default()
495            };
496            let vari = Var::from_name(&name)
497                .set_type(Type::Params(params.clone(), h2))
498                .to_language();
499            Ok((
500                s,
501                vec![Lang::Alias {
502                    identifier: Box::new(vari),
503                    parameters: params,
504                    target_type: ty,
505                    is_public: false,
506                    is_export: false,
507                    help_data: h,
508                }],
509            ))
510        }
511        Ok((s, (_pub, kw, _, _eq, _ty2, _))) => Ok((s, vec![Lang::Empty(kw.into())])),
512        Err(r) => Err(r),
513    }
514}
515
516fn base_type_exp(s: Span) -> IResult<Span, Lang> {
517    let res = (
518        terminated(tag("type"), multispace0),
519        type_alias,
520        equality_operator,
521        ltype,
522        terminated(tag(";"), multispace0),
523    )
524        .parse(s);
525    match res {
526        Ok((s, (_ty, Type::Alias(name, params, _, h), _eq, ty, _))) => {
527            let h2 = if !params.is_empty() {
528                params[0].clone().into()
529            } else {
530                HelpData::default()
531            };
532            let vari = Var::from_name(&name)
533                .set_type(Type::Params(params.clone(), h2))
534                .to_language();
535            Ok((
536                s,
537                Lang::Alias {
538                    identifier: Box::new(vari),
539                    parameters: params,
540                    target_type: ty,
541                    is_public: false,
542                    is_export: false,
543                    help_data: h,
544                },
545            ))
546        }
547        Ok((s, (_ty, _, _eq, _ty2, _))) => Ok((s, Lang::Empty(_ty.into()))),
548        Err(r) => Err(r),
549    }
550}
551
552fn type_exp(s: Span) -> IResult<Span, Vec<Lang>> {
553    let res = (
554        opt(terminated(alt((tag("@export"), tag("@pub"))), multispace0)),
555        base_type_exp,
556    )
557        .parse(s);
558    match res {
559        Ok((
560            s,
561            (
562                Some(annotation),
563                Lang::Alias {
564                    identifier: var,
565                    parameters: params,
566                    target_type: typ,
567                    help_data: h,
568                    ..
569                },
570            ),
571        )) => {
572            // `@export` → public + exported in R package (mirrors `Let`'s RFC-TR-032).
573            // `@pub`    → public only.
574            let is_exp = *annotation.fragment() == "@export";
575            Ok((
576                s,
577                vec![Lang::Alias {
578                    identifier: var,
579                    parameters: params,
580                    target_type: typ,
581                    is_public: true,
582                    is_export: is_exp,
583                    help_data: h,
584                }],
585            ))
586        }
587        Ok((
588            s,
589            (
590                None,
591                Lang::Alias {
592                    identifier: var,
593                    parameters: params,
594                    target_type: typ,
595                    help_data: h,
596                    ..
597                },
598            ),
599        )) => {
600            let vari = Var::from_language(var.deref().clone()).unwrap().to_language();
601            Ok((
602                s,
603                vec![Lang::Alias {
604                    identifier: Box::new(vari),
605                    parameters: params,
606                    target_type: typ,
607                    is_public: false,
608                    is_export: false,
609                    help_data: h,
610                }],
611            ))
612        }
613        Err(r) => Err(r),
614        _ => todo!(),
615    }
616}
617
618/// Detects `type <lowercase_var> <- <expr>` which should be `let` instead.
619/// Parses the full expression and returns a `Lang::Let` with a push_parse_error.
620fn type_instead_of_let_exp(s: Span) -> IResult<Span, Vec<Lang>> {
621    let res = (
622        opt(terminated(tag("@pub"), multispace0)),
623        terminated(tag("type"), multispace0),
624        terminated(variable_exp, multispace0),
625        equality_operator,
626        single_parse,
627    )
628        .parse(s);
629    match res {
630        Ok((s, (pub_ann, type_kw, (name, h), _eq, body))) => {
631            push_parse_error(SyntaxError::TypeInsteadOfLet {
632                name: name.clone(),
633                help_data: h.clone(),
634            });
635            let is_pub = pub_ann.is_some();
636            Ok((
637                s,
638                vec![Lang::Let {
639                    variable: Box::new(Lang::Variable {
640                        name,
641                        is_opaque: false,
642                        related_type: Type::Empty(HelpData::default()),
643                        help_data: h,
644                    }),
645                    r#type: Type::Empty(type_kw.clone().into()),
646                    expression: Box::new(body),
647                    is_public: is_pub,
648                    is_testable: is_pub,
649                    is_export: false,
650                    help_data: type_kw.into(),
651                }],
652            ))
653        }
654        Err(r) => Err(r),
655    }
656}
657
658fn base_opaque_exp(s: Span) -> IResult<Span, Lang> {
659    let res = (
660        terminated(tag("opaque"), multispace0),
661        type_alias,
662        equality_operator,
663        ltype,
664        terminated(tag(";"), multispace0),
665    )
666        .parse(s);
667    match res {
668        Ok((s, (_ty, Type::Alias(name, params, _, h), _eq, ty, _))) => {
669            let vari = Var::from_name(&name)
670                .set_type(Type::Params(params.clone(), params.clone().into()))
671                .set_opacity(true)
672                .to_language();
673            Ok((
674                s,
675                Lang::Alias {
676                    identifier: Box::new(vari),
677                    parameters: params,
678                    target_type: ty,
679                    is_public: false,
680                    is_export: false,
681                    help_data: h,
682                },
683            ))
684        }
685        Ok((s, (_ty, _, _eq, _ty2, _))) => Ok((s, Lang::Empty(_ty.into()))),
686        Err(r) => Err(r),
687    }
688}
689
690fn opaque_exp(s: Span) -> IResult<Span, Vec<Lang>> {
691    let res = (
692        opt(terminated(alt((tag("@export"), tag("@pub"))), multispace0)),
693        base_opaque_exp,
694    )
695        .parse(s);
696    match res {
697        Ok((
698            s,
699            (
700                Some(annotation),
701                Lang::Alias {
702                    identifier: var,
703                    parameters: params,
704                    target_type: typ,
705                    help_data: h,
706                    ..
707                },
708            ),
709        )) => {
710            let is_exp = *annotation.fragment() == "@export";
711            let vari = Var::from_language(var.deref().clone())
712                .unwrap()
713                .set_opacity(true)
714                .to_language();
715            Ok((
716                s,
717                vec![Lang::Alias {
718                    identifier: Box::new(vari),
719                    parameters: params,
720                    target_type: typ,
721                    is_public: true,
722                    is_export: is_exp,
723                    help_data: h,
724                }],
725            ))
726        }
727        Ok((
728            s,
729            (
730                None,
731                Lang::Alias {
732                    identifier: var,
733                    parameters: params,
734                    target_type: typ,
735                    help_data: h,
736                    ..
737                },
738            ),
739        )) => {
740            let vari = Var::from_language(var.deref().clone())
741                .unwrap()
742                .set_opacity(true)
743                .to_language();
744            Ok((
745                s,
746                vec![Lang::Alias {
747                    identifier: Box::new(vari),
748                    parameters: params,
749                    target_type: typ,
750                    is_public: false,
751                    is_export: false,
752                    help_data: h,
753                }],
754            ))
755        }
756        Err(r) => Err(r),
757        _ => todo!(),
758    }
759}
760
761pub fn module(s: Span) -> IResult<Span, Vec<Lang>> {
762    let res = (
763        terminated(tag("module"), multispace0),
764        terminated(variable_recognizer, multispace0),
765        terminated(tag("{"), multispace0),
766        base_parse,
767        terminated(tag("}"), multispace0),
768        opt(terminated(tag(";"), multispace0)),
769    )
770        .parse(s);
771    match res {
772        Ok((s, (modu, (name, _), _op, v, _cl, _dv))) => Ok((
773            s,
774            vec![Lang::Module {
775                name,
776                body: v,
777                module_position: ModulePosition::Internal,
778                config: Config::default(),
779                help_data: modu.into(),
780            }],
781        )),
782        Err(r) => Err(r),
783    }
784}
785
786fn import_module(s: Span) -> IResult<Span, Vec<Lang>> {
787    let res = (
788        terminated(tag("import"), multispace0),
789        terminated(variable_recognizer, multispace0),
790        opt((
791            terminated(tag("as"), multispace0),
792            terminated(variable_recognizer, multispace0),
793        )),
794        opt(terminated(tag(";"), multispace0)),
795    )
796        .parse(s);
797    match res {
798        Ok((s, (import_kw, (name, _), None, _))) => Ok((
799            s,
800            vec![Lang::ModuleImport {
801                value: name,
802                help_data: import_kw.into(),
803            }],
804        )),
805        Ok((s, (_, (name, h), Some((_, (alias, _))), _))) => {
806            let module_var = Lang::Variable {
807                name,
808                is_opaque: false,
809                related_type: Type::Empty(HelpData::default()),
810                help_data: h.clone(),
811            };
812            let alias_var = Var::from_name(&alias).to_language();
813            Ok((
814                s,
815                vec![Lang::Let {
816                    variable: Box::new(alias_var),
817                    r#type: Type::Empty(HelpData::default()),
818                    expression: Box::new(module_var),
819                    is_public: false,
820                    is_testable: false,
821                    is_export: false,
822                    help_data: h,
823                }],
824            ))
825        }
826        Err(r) => Err(r),
827    }
828}
829
830fn import_from_exp(s: Span) -> IResult<Span, Vec<Lang>> {
831    let res = (
832        terminated(tag("@importFrom"), multispace1),
833        terminated(
834            take_while1(|c: char| c.is_alphanumeric() || c == '.' || c == '_'),
835            multispace1,
836        ),
837        many1(terminated(
838            take_while1(|c: char| c.is_alphanumeric() || c == '.' || c == '_'),
839            multispace0,
840        )),
841        opt(terminated(tag(";"), multispace0)),
842    )
843        .parse(s);
844    match res {
845        Ok((s, (kw, pkg, fns, _))) => Ok((
846            s,
847            vec![Lang::ImportFrom {
848                package: pkg.fragment().to_string(),
849                functions: fns.iter().map(|f| f.fragment().to_string()).collect(),
850                help_data: kw.into(),
851            }],
852        )),
853        Err(r) => Err(r),
854    }
855}
856
857fn assign(s: Span) -> IResult<Span, Vec<Lang>> {
858    let res = (
859        variable,
860        alt((terminated(tag("="), multispace0), terminated(tag("<-"), multispace0))),
861        parse_elements,
862        opt(terminated(tag(";"), multispace0)),
863    )
864        .parse(s);
865    match res {
866        Ok((s, ((var, _), _eq, exp, Some(_)))) => Ok((
867            s,
868            vec![Lang::Assign {
869                identifier: Box::new(var.clone()),
870                expression: Box::new(exp),
871                help_data: var.into(),
872            }],
873        )),
874        Ok((s, ((var, _), _eq, exp, None))) => {
875            push_parse_error(SyntaxError::ForgottenSemicolon(exp.clone().into()));
876            let assign = Lang::Assign {
877                identifier: Box::new(var.clone()),
878                expression: Box::new(exp),
879                help_data: var.into(),
880            };
881            Ok((s, vec![assign]))
882        }
883        Err(r) => Err(r),
884    }
885}
886
887fn comment(s: Span) -> IResult<Span, Vec<Lang>> {
888    let res = (tag("#"), not_line_ending, opt(line_ending), multispace0).parse(s);
889    match res {
890        Ok((s, (_hashtag, txt, _, _))) => Ok((
891            s,
892            vec![Lang::Comment {
893                value: txt.to_string(),
894                help_data: _hashtag.into(),
895            }],
896        )),
897        Err(r) => Err(r),
898    }
899}
900
901/// Recognizes a `//`-style comment (the C/JS/R convention) and treats it
902/// exactly like a real `#`-comment — consumed to end of line, dropped from
903/// the AST, non-fatal — but flags a recoverable `WrongCommentSyntax` error:
904/// TypR only ever uses `#`. Without this, `//` used to silently tokenize as
905/// a (dead) division-like operator and fail much later with a confusing
906/// "function `//` not defined" type error; now that the dead-operator
907/// tokenizer entry for `//` is gone (see `operators.rs::op()`), it would
908/// otherwise leave unparseable trailing text that gets silently dropped
909/// from the AST instead — this dedicated parser keeps the common typo both
910/// harmless (no code loss) and visible (a located warning).
911fn wrong_comment(s: Span) -> IResult<Span, Vec<Lang>> {
912    let res = (tag("//"), not_line_ending, opt(line_ending), multispace0).parse(s);
913    match res {
914        Ok((s, (slashes, txt, _, _))) => {
915            push_parse_error(SyntaxError::WrongCommentSyntax(slashes.clone().into()));
916            Ok((
917                s,
918                vec![Lang::Comment {
919                    value: txt.to_string(),
920                    help_data: slashes.into(),
921                }],
922            ))
923        }
924        Err(r) => Err(r),
925    }
926}
927
928pub fn simple_exp(s: Span) -> IResult<Span, Vec<Lang>> {
929    let res = (parse_elements, opt(terminated(tag(";"), multispace0))).parse(s);
930    match res {
931        Ok((s, (lang, Some(_)))) => Ok((s, vec![lang])),
932        Ok((s, (lang, None))) => {
933            push_parse_error(SyntaxError::ForgottenSemicolon(lang.clone().into()));
934            Ok((s, vec![lang]))
935        }
936        Err(r) => Err(r),
937    }
938}
939
940fn mod_imp(s: Span) -> IResult<Span, Vec<Lang>> {
941    let res = (
942        terminated(tag("mod"), multispace0),
943        terminated(variable_exp, multispace0),
944        terminated(tag(";"), multispace0),
945    )
946        .parse(s);
947    match res {
948        Ok((s, (_mod, (name, _), _sc))) => Ok((
949            s,
950            vec![Lang::ModuleImport {
951                value: name.to_string(),
952                help_data: _mod.into(),
953            }],
954        )),
955        Err(r) => Err(r),
956    }
957}
958
959fn import_var(s: Span) -> IResult<Span, Vec<Lang>> {
960    let res = (
961        terminated(tag("use"), multispace0),
962        variable,
963        terminated(tag(";"), multispace0),
964    )
965        .parse(s);
966    match res {
967        Ok((s, (_use, (lang, case), _sc))) => {
968            let res = match case {
969                Case::Maj => Var::from_language(lang).unwrap().to_alias_lang(),
970                _ => Var::from_language(lang).unwrap().to_let(),
971            };
972            Ok((s, vec![res]))
973        }
974        Err(r) => Err(r),
975    }
976}
977
978fn import_type(s: Span) -> IResult<Span, Vec<Lang>> {
979    let res = (
980        terminated(tag("use"), multispace0),
981        type_alias,
982        terminated(tag(";"), multispace0),
983    )
984        .parse(s);
985
986    match res {
987        Ok((s, (_use, alias, _sc))) => Ok((
988            s,
989            vec![Lang::Import {
990                value: alias,
991                help_data: _use.into(),
992            }],
993        )),
994        Err(r) => Err(r),
995    }
996}
997
998fn tests(s: Span) -> IResult<Span, Vec<Lang>> {
999    let res = (tag("Test"), delimited(tag("["), base_parse, tag("]"))).parse(s);
1000    match res {
1001        Ok((s, (_t, body))) => Ok((
1002            s,
1003            vec![Lang::Test {
1004                value: body,
1005                help_data: _t.into(),
1006            }],
1007        )),
1008        Err(r) => Err(r),
1009    }
1010}
1011
1012fn library(s: Span) -> IResult<Span, Vec<Lang>> {
1013    let res = (tag("library("), variable_exp, tag(")"), opt(tag(";")), multispace0).parse(s);
1014
1015    match res {
1016        Ok((s, (_lib, (var, h), _cl, Some(_col), _))) => Ok((
1017            s,
1018            vec![Lang::Library {
1019                value: var,
1020                help_data: h.clone(),
1021            }],
1022        )),
1023        Ok((_, (_lib, _var, _cl, None, _))) => {
1024            panic!("You forgot to put a ';' at the end of the line")
1025        }
1026        Err(r) => Err(r),
1027    }
1028}
1029
1030fn use_item_exp(s: Span) -> IResult<Span, crate::components::language::use_lang::UseItem> {
1031    use crate::components::language::use_lang::UseItem;
1032    // Parse: Ident (as Ident)?
1033    let res = (
1034        terminated(variable_recognizer, multispace0),
1035        opt(preceded(
1036            pair(tag("as"), multispace0),
1037            terminated(variable_recognizer, multispace0),
1038        )),
1039    )
1040        .parse(s);
1041    match res {
1042        Ok((s, ((name, _), alias_opt))) => {
1043            let alias = alias_opt.map(|(alias_name, _)| alias_name);
1044            Ok((s, UseItem { name, alias }))
1045        }
1046        Err(r) => Err(r),
1047    }
1048}
1049
1050fn use_items_selector(s: Span) -> IResult<Span, crate::components::language::use_lang::UseSelector> {
1051    use crate::components::language::use_lang::UseSelector;
1052    let res = (
1053        pair(tag("{"), multispace0),
1054        separated_list0(pair(tag(","), multispace0), use_item_exp),
1055        opt(pair(tag(","), multispace0)),
1056        pair(tag("}"), multispace0),
1057    )
1058        .parse(s);
1059    match res {
1060        Ok((s, (_, items, _, _))) => Ok((s, UseSelector::Items(items))),
1061        Err(r) => Err(r),
1062    }
1063}
1064
1065fn use_selector_exp(s: Span) -> IResult<Span, crate::components::language::use_lang::UseSelector> {
1066    use crate::components::language::use_lang::UseSelector;
1067    alt((
1068        map(terminated(tag("*"), multispace0), |_| UseSelector::Wildcard),
1069        use_items_selector,
1070    ))
1071    .parse(s)
1072}
1073
1074fn use_module_directive(s: Span) -> IResult<Span, Vec<Lang>> {
1075    use crate::components::language::use_lang::UseSelector;
1076
1077    // Parse: use FirstSeg :: ...
1078    let res = (
1079        terminated(tag("use"), multispace0),
1080        terminated(variable_recognizer, multispace0),
1081        pair(tag("::"), multispace0),
1082    )
1083        .parse(s);
1084
1085    let (s, (use_kw, (first_seg, _), _)) = res?;
1086
1087    let mut path = vec![first_seg];
1088    let mut current_s = s;
1089    let selector;
1090
1091    loop {
1092        match use_selector_exp(current_s.clone()) {
1093            Ok((s2, sel)) => {
1094                selector = sel;
1095                current_s = s2;
1096                break;
1097            }
1098            Err(_) => match terminated(variable_recognizer, multispace0).parse(current_s.clone()) {
1099                Ok((s2, (seg, _))) => {
1100                    let colon_res: IResult<Span, (Span, Span)> = pair(tag("::"), multispace0).parse(s2.clone());
1101                    match colon_res {
1102                        Ok((s3, _)) => {
1103                            path.push(seg);
1104                            current_s = s3;
1105                        }
1106                        Err(_) => {
1107                            use crate::components::language::use_lang::UseItem;
1108                            selector = UseSelector::Items(vec![UseItem { name: seg, alias: None }]);
1109                            current_s = s2;
1110                            break;
1111                        }
1112                    }
1113                }
1114                Err(e) => return Err(e),
1115            },
1116        }
1117    }
1118
1119    let res = terminated(tag(";"), multispace0).parse(current_s);
1120    let (s, _) = res?;
1121
1122    Ok((
1123        s,
1124        vec![Lang::UseModule {
1125            module_path: path,
1126            selector,
1127            help_data: use_kw.into(),
1128        }],
1129    ))
1130}
1131
1132fn use_exp(s: Span) -> IResult<Span, Vec<Lang>> {
1133    let res = (
1134        tag("use("),
1135        chars,
1136        tag(", "),
1137        alt((vector, chars)),
1138        terminated(tag(");"), multispace0),
1139    )
1140        .parse(s);
1141    match res {
1142        Ok((s, (us, lib, _, members, _))) => Ok((
1143            s,
1144            vec![Lang::Use {
1145                lang: Box::new(lib),
1146                members: Box::new(members),
1147                help_data: us.into(),
1148            }],
1149        )),
1150        Err(r) => Err(r),
1151    }
1152}
1153
1154fn custom_operators(s: Span) -> IResult<Span, (String, HelpData)> {
1155    let res = custom_op.parse(s);
1156    match res {
1157        Ok((s, co)) => Ok((s, (co.clone().to_string(), co.into()))),
1158        Err(r) => Err(r),
1159    }
1160}
1161
1162fn return_stmt(s: Span) -> IResult<Span, Vec<Lang>> {
1163    let (s, e) = return_exp(s)?;
1164    Ok((s, vec![e]))
1165}
1166
1167fn stmt_exp(s: Span) -> IResult<Span, Vec<Lang>> {
1168    let res = (parse_elements, terminated(tag(";"), multispace0)).parse(s);
1169    match res {
1170        Ok((s, (lang, _))) => Ok((s, vec![lang])),
1171        Err(r) => Err(r),
1172    }
1173}
1174
1175fn head_lang(lang: &Lang) -> Option<Lang> {
1176    match lang {
1177        Lang::Variable { .. } => Some(lang.clone()),
1178        Lang::Operator {
1179            operator: Op::Pipe(_) | Op::Dot(_),
1180            rhs,
1181            ..
1182        } => head_lang(rhs),
1183        _ => None,
1184    }
1185}
1186
1187fn implicit_mutate(s: Span) -> IResult<Span, Vec<Lang>> {
1188    let res = (parse_elements, terminated(tag("!;"), multispace0)).parse(s);
1189    match res {
1190        Ok((s, (expr, excl))) => match head_lang(&expr) {
1191            Some(lhs) => Ok((
1192                s,
1193                vec![Lang::Assign {
1194                    identifier: Box::new(lhs),
1195                    expression: Box::new(expr),
1196                    help_data: excl.into(),
1197                }],
1198            )),
1199            None => {
1200                push_parse_error(SyntaxError::MutationTargetNotAssignable(expr.clone().into()));
1201                Err(nom::Err::Error(nom::error::Error::new(s, nom::error::ErrorKind::Tag)))
1202            }
1203        },
1204        Err(r) => Err(r),
1205    }
1206}
1207
1208fn signature_variable(s: Span) -> IResult<Span, Vec<Lang>> {
1209    let res = (
1210        tag("@"),
1211        alt((variable_recognizer, custom_operators)),
1212        terminated(tag(":"), multispace0),
1213        ltype,
1214        terminated(tag(";"), multispace0),
1215    )
1216        .parse(s);
1217    match res {
1218        Ok((s, (at, (name, h), _col, typ, _))) => {
1219            let var2 = Var::from_name(&name).set_help_data(h).set_type(typ.clone());
1220            Ok((
1221                s,
1222                vec![Lang::Signature {
1223                    identifier: var2,
1224                    target_type: typ,
1225                    help_data: at.into(),
1226                    is_extern: false,
1227                    extern_r_name: None,
1228                }],
1229            ))
1230        }
1231        Err(r) => Err(r),
1232    }
1233}
1234
1235fn signature_opaque(s: Span) -> IResult<Span, Vec<Lang>> {
1236    let res = (
1237        tag("@"),
1238        type_alias,
1239        terminated(tag(":"), multispace0),
1240        ltype,
1241        terminated(tag(";"), multispace0),
1242    )
1243        .parse(s);
1244    match res {
1245        Ok((s, (at, Type::Alias(name, _params, _, h), _, typ, _))) => {
1246            let var2 = Var::from_name(&name).set_help_data(h.clone()).set_type(typ.clone());
1247            Ok((
1248                s,
1249                vec![Lang::Signature {
1250                    identifier: var2,
1251                    target_type: typ,
1252                    help_data: at.into(),
1253                    is_extern: false,
1254                    extern_r_name: None,
1255                }],
1256            ))
1257        }
1258        Ok((_s, (_, _, _, _, _))) => todo!(),
1259        Err(r) => Err(r),
1260    }
1261}
1262
1263fn signature_extern(s: Span) -> IResult<Span, Vec<Lang>> {
1264    let res = (
1265        tag("@extern"),
1266        multispace1,
1267        opt((
1268            take_while1(|c: char| c.is_alphanumeric() || c == '_' || c == '.'),
1269            tag("::"),
1270        )),
1271        alt((variable_recognizer, custom_operators)),
1272        terminated(tag(":"), multispace0),
1273        ltype,
1274        terminated(tag(";"), multispace0),
1275    )
1276        .parse(s);
1277    match res {
1278        Ok((s, (at, _, pkg_prefix, (name, h), _col, typ, _))) => {
1279            let r_name = pkg_prefix.map(|(pkg, _)| format!("{}::{}", pkg, name));
1280            let var2 = Var::from_name(&name).set_help_data(h).set_type(typ.clone());
1281            Ok((
1282                s,
1283                vec![Lang::Signature {
1284                    identifier: var2,
1285                    target_type: typ,
1286                    help_data: at.into(),
1287                    is_extern: true,
1288                    extern_r_name: r_name,
1289                }],
1290            ))
1291        }
1292        Err(r) => Err(r),
1293    }
1294}
1295
1296pub fn signature(s: Span) -> IResult<Span, Vec<Lang>> {
1297    alt((signature_extern, signature_opaque, signature_variable)).parse(s)
1298}
1299
1300fn for_loop(s: Span) -> IResult<Span, Vec<Lang>> {
1301    let res = (
1302        terminated(tag("for"), multispace0),
1303        terminated(tag("("), multispace0),
1304        terminated(variable_exp, multispace0),
1305        terminated(tag("in"), multispace0),
1306        terminated(single_element, multispace0),
1307        terminated(tag(")"), multispace0),
1308        scope,
1309        terminated(tag(";"), multispace0),
1310    )
1311        .parse(s);
1312    match res {
1313        Ok((s, (_for, _op, (var_str, _h), _in, iterator, _cl, scop, _semi))) => Ok((
1314            s,
1315            vec![Lang::ForLoop {
1316                identifier: Var::from_name(&var_str),
1317                expression: Box::new(iterator),
1318                body: Box::new(scop),
1319                help_data: _for.into(),
1320            }],
1321        )),
1322        Err(r) => Err(r),
1323    }
1324}
1325
1326fn while_loop(s: Span) -> IResult<Span, Vec<Lang>> {
1327    let res = (
1328        terminated(tag("while"), multispace0),
1329        terminated(tag("("), multispace0),
1330        terminated(single_element, multispace0),
1331        terminated(tag(")"), multispace0),
1332        scope,
1333        terminated(tag(";"), multispace0),
1334    )
1335        .parse(s);
1336    match res {
1337        Ok((s, (_while, _op, condition, _cl, scop, _semi))) => Ok((
1338            s,
1339            vec![Lang::WhileLoop {
1340                condition: Box::new(condition),
1341                body: Box::new(scop),
1342                help_data: _while.into(),
1343            }],
1344        )),
1345        Err(r) => Err(r),
1346    }
1347}
1348
1349fn loop_loop(s: Span) -> IResult<Span, Vec<Lang>> {
1350    let res = (
1351        terminated(tag("loop"), multispace0),
1352        scope,
1353        terminated(tag(";"), multispace0),
1354    )
1355        .parse(s);
1356    match res {
1357        Ok((s, (_loop, scop, _semi))) => Ok((
1358            s,
1359            vec![Lang::Loop {
1360                body: Box::new(scop),
1361                help_data: _loop.into(),
1362            }],
1363        )),
1364        Err(r) => Err(r),
1365    }
1366}
1367
1368fn test_block(s: Span) -> IResult<Span, Vec<Lang>> {
1369    let res = (terminated(tag("Test"), multispace0), scope).parse(s);
1370    //parse_block).parse(s);
1371
1372    match res {
1373        Ok((s, (tst, body))) => Ok((
1374            s,
1375            vec![Lang::TestBlock {
1376                value: Box::new(body),
1377                help_data: tst.into(),
1378            }],
1379        )),
1380        Err(r) => Err(r),
1381    }
1382}
1383
1384// main
1385pub fn base_parse(s: Span) -> IResult<Span, Vec<Lang>> {
1386    let res = (
1387        opt(multispace0),
1388        many0(alt((
1389            alt((
1390                library,
1391                break_exp,
1392                next_exp,
1393                use_exp,
1394                test_block,
1395                while_loop,
1396                loop_loop,
1397                for_loop,
1398                import_from_exp,
1399                signature,
1400                tests,
1401                import_module,
1402                use_module_directive,
1403                import_type,
1404            )),
1405            alt((
1406                import_var,
1407                mod_imp,
1408                comment,
1409                wrong_comment,
1410                typeconstructor_exp,
1411                single_letter_type_name_exp,
1412                type_exp,
1413                type_instead_of_let_exp,
1414                opaque_exp,
1415                let_tuple_exp,
1416                let_exp,
1417                module,
1418                assign,
1419                return_stmt,
1420                implicit_mutate,
1421                stmt_exp,
1422            )),
1423        ))),
1424        opt(parse_elements),
1425    )
1426        .parse(s);
1427    match res {
1428        // Case 1: nothing → empty vec (truly empty, distinct from `...`)
1429        Ok((s, (_, v, None))) if v.is_empty() => Ok((s, vec![])),
1430        // Case 2: no statements, one trailing expression (no ";") → return it directly
1431        Ok((s, (_, v, Some(expr)))) if v.is_empty() => Ok((s, vec![expr])),
1432        // Case 3: one or more statements + optional trailing expression
1433        Ok((s, (_, v, trailing))) => {
1434            let mut result: Vec<Lang> = v.into_iter().flatten().collect();
1435            if let Some(expr) = trailing {
1436                result.push(expr);
1437            }
1438            Ok((s, result))
1439        }
1440        Err(r) => Err(r),
1441    }
1442}
1443
1444/// Parse source code and return a ParseResult containing the AST and any syntax errors
1445///
1446/// This function collects syntax errors instead of panicking, allowing the caller
1447/// to handle errors appropriately (e.g., display all errors, continue with partial AST)
1448pub fn parse(s: Span) -> ParseResult {
1449    let res = base_parse(s.clone());
1450    match res {
1451        Ok((remaining, v)) => {
1452            if !remaining.fragment().is_empty() {
1453                let element = remaining.fragment().lines().next().unwrap_or("").trim().to_string();
1454                let line = remaining.location_line();
1455                let help_data = HelpData::from(remaining);
1456                push_parse_error(SyntaxError::UnknownElement {
1457                    element,
1458                    line,
1459                    help_data,
1460                });
1461            }
1462            ParseResult::new(Lang::Lines {
1463                value: v.clone(),
1464                help_data: v.into(),
1465            })
1466        }
1467        Err(_) => panic!("Can't parse string {}", s),
1468    }
1469}
1470
1471/// Parse source code and return just the Lang AST (legacy behavior)
1472///
1473/// This function is kept for backwards compatibility. It returns the AST directly
1474/// and will panic if parsing fails completely.
1475pub fn parse_legacy(s: Span) -> Lang {
1476    parse(s).ast
1477}
1478
1479pub fn parse2(s: Span) -> Result<Lang, String> {
1480    let res = base_parse(s.clone());
1481    // Drain any parse errors collected during parsing (e.g. ForgottenSemicolon, FunctionTypeSyntax)
1482    let _ = take_parse_errors();
1483    match res {
1484        Ok((_, v)) => Ok(v[0].clone()),
1485        Err(_) => Err(format!("Can't parse string {}", s)),
1486    }
1487}
1488
1489/// Parse source code from a string with a filename for error reporting
1490///
1491/// This is the main entry point for parsing source code in the public API.
1492/// It registers the source for error display and returns the AST.
1493pub fn parse_from_string(source: &str, filename: &str) -> Lang {
1494    use crate::components::error_message::help_data::register_source;
1495
1496    // Register source for error display
1497    register_source(filename, source);
1498
1499    // Create a span with the filename as extra data
1500    let span: Span = LocatedSpan::new_extra(source, filename.to_string());
1501
1502    // Parse and return the AST
1503    parse(span).ast
1504}
1505
1506/// Parse source code from a string with a filename, returning full ParseResult
1507pub fn parse_from_string_with_errors(source: &str, filename: &str) -> ParseResult {
1508    use crate::components::error_message::help_data::register_source;
1509
1510    // Register source for error display
1511    register_source(filename, source);
1512
1513    // Create a span with the filename as extra data
1514    let span: Span = LocatedSpan::new_extra(source, filename.to_string());
1515
1516    // Parse and return full result
1517    parse(span)
1518}
1519
1520// main test
1521#[cfg(test)]
1522mod tesus {
1523    use super::*;
1524
1525    #[test]
1526    fn test_semicolon1() {
1527        let res = parse("let a <- 5".into());
1528        // This should now collect a ForgottenSemicolon error
1529        assert!(res.has_errors(), "Missing semicolon should produce a syntax error");
1530        assert_eq!(res.errors.len(), 1, "Should have exactly one error");
1531        match &res.errors[0] {
1532            SyntaxError::ForgottenSemicolon(_) => (),
1533            _ => panic!("Expected ForgottenSemicolon error"),
1534        }
1535    }
1536
1537    #[test]
1538    fn test_semicolon_standalone_exp() {
1539        let res = parse("f(x)".into());
1540        // Case 2: a single expression without ";" is valid — no error
1541        assert!(
1542            !res.has_errors(),
1543            "A standalone expression without semicolon is valid (case 2)"
1544        );
1545    }
1546
1547    #[test]
1548    fn test_semicolon_assign() {
1549        let res = parse("a <- 12".into());
1550        assert!(
1551            res.has_errors(),
1552            "An assign expression without semicolon should produce a syntax error"
1553        );
1554        match &res.errors[0] {
1555            SyntaxError::ForgottenSemicolon(_) => (),
1556            _ => panic!("Expected ForgottenSemicolon error"),
1557        }
1558    }
1559
1560    #[test]
1561    fn test_assign1() {
1562        let res = assign("a <- 12;".into()).unwrap().1;
1563        assert_eq!(
1564            "Assign",
1565            res[0].simple_print(),
1566            "The expression 'a <- 12;' should be identified as an assignation"
1567        );
1568    }
1569
1570    // ==================== Let vs Type Alias Tests ====================
1571
1572    #[test]
1573    fn test_let_instead_of_type() {
1574        let res = parse("let MyType <- int;".into());
1575        assert!(res.has_errors(), "let with PascalCase should produce a syntax error");
1576        assert_eq!(res.errors.len(), 1, "Should have exactly one error");
1577        match &res.errors[0] {
1578            SyntaxError::LetInsteadOfType { name, .. } => {
1579                assert_eq!(name, "MyType", "Error should reference the type name");
1580            }
1581            _ => panic!("Expected LetInsteadOfType error"),
1582        }
1583    }
1584
1585    #[test]
1586    fn test_let_instead_of_type_with_pub() {
1587        let res = parse("@pub let MyAlias <- num;".into());
1588        assert!(
1589            res.has_errors(),
1590            "@pub let with PascalCase should produce a syntax error"
1591        );
1592        assert!(res
1593            .errors
1594            .iter()
1595            .any(|e| matches!(e, SyntaxError::LetInsteadOfType { .. })));
1596    }
1597
1598    #[test]
1599    fn test_let_with_lowercase_is_fine() {
1600        let res = parse("let my_var <- 42;".into());
1601        assert!(
1602            !res.has_errors(),
1603            "let with snake_case should not produce a syntax error"
1604        );
1605    }
1606
1607    #[test]
1608    fn test_type_instead_of_let() {
1609        let res = parse("type my_var <- 42;".into());
1610        assert!(res.has_errors(), "type with snake_case should produce a syntax error");
1611        assert_eq!(res.errors.len(), 1, "Should have exactly one error");
1612        match &res.errors[0] {
1613            SyntaxError::TypeInsteadOfLet { name, .. } => {
1614                assert_eq!(name, "my_var", "Error should reference the variable name");
1615            }
1616            _ => panic!("Expected TypeInsteadOfLet error"),
1617        }
1618    }
1619
1620    #[test]
1621    fn test_type_instead_of_let_with_pub() {
1622        let res = parse("@pub type my_binding <- num;".into());
1623        assert!(
1624            res.has_errors(),
1625            "@pub type with snake_case should produce a syntax error"
1626        );
1627        assert!(res
1628            .errors
1629            .iter()
1630            .any(|e| matches!(e, SyntaxError::TypeInsteadOfLet { .. })));
1631    }
1632
1633    #[test]
1634    fn test_type_with_pascalcase_is_fine() {
1635        let res = parse("type MyAlias <- int;".into());
1636        assert!(
1637            !res.has_errors(),
1638            "type with PascalCase should not produce a syntax error"
1639        );
1640    }
1641
1642    // ============= Single-Letter Type Alias Name Tests (P2) =============
1643
1644    #[test]
1645    fn test_single_letter_type_name_is_an_error() {
1646        let res = parse("type A <- int;".into());
1647        assert!(
1648            res.has_errors(),
1649            "single uppercase letter alias name should produce a syntax error"
1650        );
1651        match &res.errors[0] {
1652            SyntaxError::SingleLetterTypeName { name, .. } => {
1653                assert_eq!(name, "A");
1654            }
1655            other => panic!("Expected SingleLetterTypeName error, got {:?}", other),
1656        }
1657        // Unlike the pre-fix bug, the statement is no longer silently dropped —
1658        // it recovers a full Lang::Alias.
1659        match &res.ast {
1660            Lang::Lines { value, .. } => {
1661                assert_eq!(value.len(), 1);
1662                assert!(matches!(value[0], Lang::Alias { .. }));
1663            }
1664            other => panic!("Expected Lang::Lines, got {:?}", other),
1665        }
1666    }
1667
1668    #[test]
1669    fn test_single_letter_opaque_name_is_an_error() {
1670        let res = parse("opaque Z <- num;".into());
1671        assert!(
1672            res.has_errors(),
1673            "single uppercase letter opaque name should produce a syntax error"
1674        );
1675        match &res.errors[0] {
1676            SyntaxError::SingleLetterTypeName { name, .. } => {
1677                assert_eq!(name, "Z");
1678            }
1679            other => panic!("Expected SingleLetterTypeName error, got {:?}", other),
1680        }
1681    }
1682
1683    #[test]
1684    fn test_single_letter_type_name_with_pub() {
1685        let res = parse("@pub type T <- int;".into());
1686        assert!(
1687            res.has_errors(),
1688            "@pub single-letter alias name should still produce a syntax error"
1689        );
1690        assert!(res
1691            .errors
1692            .iter()
1693            .any(|e| matches!(e, SyntaxError::SingleLetterTypeName { .. })));
1694    }
1695
1696    #[test]
1697    fn test_two_letter_type_name_is_fine() {
1698        let res = parse("type Ab <- int;".into());
1699        assert!(
1700            !res.has_errors(),
1701            "a two-letter alias name should not produce a syntax error"
1702        );
1703    }
1704
1705    // ========= Keyword Positional Record/Tuple Tests (P3) =========
1706
1707    fn extract_let_expression(ast: &Lang) -> &Lang {
1708        match ast {
1709            Lang::Lines { value, .. } => match &value[0] {
1710                Lang::Let { expression, .. } => expression,
1711                other => panic!("Expected Lang::Let, got {:?}", other),
1712            },
1713            other => panic!("Expected Lang::Lines, got {:?}", other),
1714        }
1715    }
1716
1717    #[test]
1718    fn test_list_brace_positional_is_an_error() {
1719        let res = parse("let x <- list{1, 2, 3};".into());
1720        assert!(
1721            res.has_errors(),
1722            "list{{...}} with positional elements should produce a syntax error"
1723        );
1724        match &res.errors[0] {
1725            SyntaxError::KeywordRecordPositionalElements { keyword, .. } => {
1726                assert_eq!(keyword, "list");
1727            }
1728            other => panic!("Expected KeywordRecordPositionalElements error, got {:?}", other),
1729        }
1730        // Recovers a full Lang::Tuple — nothing lost from the AST.
1731        match extract_let_expression(&res.ast) {
1732            Lang::Tuple { value, .. } => assert_eq!(value.len(), 3),
1733            other => panic!("Expected Lang::Tuple, got {:?}", other),
1734        }
1735    }
1736
1737    #[test]
1738    fn test_record_brace_positional_is_an_error() {
1739        let res = parse("let x <- record{1, 2, 3};".into());
1740        assert!(
1741            res.has_errors(),
1742            "record{{...}} with positional elements should produce a syntax error"
1743        );
1744        match &res.errors[0] {
1745            SyntaxError::KeywordRecordPositionalElements { keyword, .. } => {
1746                assert_eq!(keyword, "record");
1747            }
1748            other => panic!("Expected KeywordRecordPositionalElements error, got {:?}", other),
1749        }
1750        match extract_let_expression(&res.ast) {
1751            Lang::Tuple { value, .. } => assert_eq!(value.len(), 3),
1752            other => panic!("Expected Lang::Tuple, got {:?}", other),
1753        }
1754    }
1755
1756    #[test]
1757    fn test_object_brace_positional_is_an_error() {
1758        let res = parse("let x <- object{1, 2, 3};".into());
1759        assert!(
1760            res.has_errors(),
1761            "object{{...}} with positional elements should produce a syntax error"
1762        );
1763        match &res.errors[0] {
1764            SyntaxError::KeywordRecordPositionalElements { keyword, .. } => {
1765                assert_eq!(keyword, "object");
1766            }
1767            other => panic!("Expected KeywordRecordPositionalElements error, got {:?}", other),
1768        }
1769        match extract_let_expression(&res.ast) {
1770            Lang::Tuple { value, .. } => assert_eq!(value.len(), 3),
1771            other => panic!("Expected Lang::Tuple, got {:?}", other),
1772        }
1773    }
1774
1775    #[test]
1776    fn test_list_brace_named_fields_still_fine() {
1777        let res = parse("let x <- list{ a = 1, b = 2 };".into());
1778        assert!(
1779            !res.has_errors(),
1780            "list{{...}} with named fields should keep working (real record literal)"
1781        );
1782        match extract_let_expression(&res.ast) {
1783            Lang::List { value, .. } => assert_eq!(value.len(), 2),
1784            other => panic!("Expected Lang::List, got {:?}", other),
1785        }
1786    }
1787
1788    #[test]
1789    fn test_list_paren_positional_still_fine() {
1790        // `list(...)` (parens) is the long-established positional-tuple syntax
1791        // and must not be affected by the brace-only P3 restriction.
1792        let res = parse("let x <- list(1, 2, 3);".into());
1793        assert!(
1794            !res.has_errors(),
1795            "list(...) with positional elements should keep working"
1796        );
1797        match extract_let_expression(&res.ast) {
1798            Lang::Tuple { value, .. } => assert_eq!(value.len(), 3),
1799            other => panic!("Expected Lang::Tuple, got {:?}", other),
1800        }
1801    }
1802
1803    #[test]
1804    fn test_colon_brace_positional_still_fine() {
1805        // `:{...}` is the sanctioned neutral positional-tuple syntax and must
1806        // keep working unaffected.
1807        let res = parse("let x <- :{1, 2, 3};".into());
1808        assert!(
1809            !res.has_errors(),
1810            ":{{...}} with positional elements should keep working"
1811        );
1812        match extract_let_expression(&res.ast) {
1813            Lang::Tuple { value, .. } => assert_eq!(value.len(), 3),
1814            other => panic!("Expected Lang::Tuple, got {:?}", other),
1815        }
1816    }
1817
1818    // ============ Single `=` Comparison Recovery Tests (P7) ============
1819
1820    #[test]
1821    fn test_single_equals_in_if_condition_recovers_as_eq() {
1822        let res = parse("if (a = b) { 1 } else { 0 };".into());
1823        assert!(
1824            res.has_errors(),
1825            "a bare `=` in expression position should produce a syntax warning"
1826        );
1827        match &res.errors[0] {
1828            SyntaxError::SingleEqualsComparison(_) => (),
1829            other => panic!("Expected SingleEqualsComparison error, got {:?}", other),
1830        }
1831        // The statement is fully recovered, not dropped: `a = b` becomes `a == b`.
1832        match &res.ast {
1833            Lang::Lines { value, .. } => match &value[0] {
1834                Lang::If { condition, .. } => match condition.as_ref() {
1835                    Lang::Operator { operator, .. } => {
1836                        assert!(matches!(operator, Op::Eq(_)));
1837                    }
1838                    other => panic!("Expected Lang::Operator, got {:?}", other),
1839                },
1840                other => panic!("Expected Lang::If, got {:?}", other),
1841            },
1842            other => panic!("Expected Lang::Lines, got {:?}", other),
1843        }
1844    }
1845
1846    #[test]
1847    fn test_double_equals_in_if_condition_no_warning() {
1848        // Regression guard: a real `==` must not be affected by the new
1849        // recovery — it's already a full operator via `op()`'s `bool_op`.
1850        let res = parse("if (a == b) { 1 } else { 0 };".into());
1851        assert!(
1852            !res.has_errors(),
1853            "a real `==` comparison should not produce any syntax error"
1854        );
1855    }
1856
1857    #[test]
1858    fn test_match_arrow_not_affected_by_equals_recovery() {
1859        // The `=>` match-arm separator must not be mistaken for the bare `=`
1860        // recovery (guarded by `not(char('>'))` in `single_equals_recovery_token`).
1861        let res = parse("let x <- 5; match x { _ => 1 };".into());
1862        assert!(
1863            !res.has_errors(),
1864            "match arm `=>` should not trigger SingleEqualsComparison"
1865        );
1866    }
1867
1868    #[test]
1869    fn test_default_param_equals_not_affected_by_equals_recovery() {
1870        // Regression guard for the exact conflict that sank the first
1871        // attempt at this fix (see the `op()` doc comment in operators.rs):
1872        // default parameter values (`name: T = value`) consume their own
1873        // `=` before recursing into the value expression, so they must never
1874        // see the new recovery trigger.
1875        let res = parse("let greet <- fn(name: char, greeting: char = \"Hello\"): char { greeting };".into());
1876        assert!(
1877            !res.has_errors(),
1878            "default parameter `=` should not trigger SingleEqualsComparison"
1879        );
1880    }
1881
1882    // ==================== Let Tuple Destructuring Tests ====================
1883
1884    #[test]
1885    fn test_let_tuple_basic() {
1886        let res = let_tuple_exp("let :{a, b, c} <- :{1, 2, 3};".into()).unwrap().1;
1887        // Should produce 4 Let statements: 1 tmp + 3 bindings
1888        assert_eq!(res.len(), 4, "Should produce 4 Let statements (1 tmp + 3 bindings)");
1889        for item in &res {
1890            assert!(
1891                item.simple_print().starts_with("let"),
1892                "Each item should be a Let, got: {}",
1893                item.simple_print()
1894            );
1895        }
1896    }
1897
1898    #[test]
1899    fn test_let_tuple_tmp_variable() {
1900        let res = let_tuple_exp("let :{a, b} <- :{1, 2};".into()).unwrap().1;
1901        // First Let should bind __tuple_tmp__
1902        if let Lang::Let { variable: var, .. } = &res[0] {
1903            if let Lang::Variable { name, .. } = var.as_ref() {
1904                assert_eq!(name, "__tuple_tmp__");
1905            } else {
1906                panic!("Expected Variable in first Let");
1907            }
1908        } else {
1909            panic!("Expected Let");
1910        }
1911    }
1912
1913    #[test]
1914    fn test_let_tuple_bindings() {
1915        let res = let_tuple_exp("let :{x, y} <- :{10, 20};".into()).unwrap().1;
1916        // Second Let should bind 'x' using Dot access
1917        if let Lang::Let {
1918            variable: var,
1919            expression: body,
1920            ..
1921        } = &res[1]
1922        {
1923            if let Lang::Variable { name, .. } = var.as_ref() {
1924                assert_eq!(name, "x");
1925            } else {
1926                panic!("Expected Variable 'x'");
1927            }
1928            assert_eq!(body.simple_print(), "Operator", "Body should be a Dot operator");
1929        } else {
1930            panic!("Expected Let");
1931        }
1932        // Third Let should bind 'y'
1933        if let Lang::Let { variable: var, .. } = &res[2] {
1934            if let Lang::Variable { name, .. } = var.as_ref() {
1935                assert_eq!(name, "y");
1936            } else {
1937                panic!("Expected Variable 'y'");
1938            }
1939        } else {
1940            panic!("Expected Let");
1941        }
1942    }
1943
1944    #[test]
1945    fn test_let_tuple_wildcard() {
1946        let res = let_tuple_exp("let :{a, _, c} <- :{1, 2, 3};".into()).unwrap().1;
1947        // Should produce 3 Let statements: 1 tmp + 2 bindings (wildcard skipped)
1948        assert_eq!(res.len(), 3, "Should produce 3 Let statements (wildcard skipped)");
1949        // Second Let should bind 'a'
1950        if let Lang::Let { variable: var, .. } = &res[1] {
1951            if let Lang::Variable { name, .. } = var.as_ref() {
1952                assert_eq!(name, "a");
1953            } else {
1954                panic!("Expected Variable 'a'");
1955            }
1956        } else {
1957            panic!("Expected Let");
1958        }
1959        // Third Let should bind 'c'
1960        if let Lang::Let { variable: var, .. } = &res[2] {
1961            if let Lang::Variable { name, .. } = var.as_ref() {
1962                assert_eq!(name, "c");
1963            } else {
1964                panic!("Expected Variable 'c'");
1965            }
1966        } else {
1967            panic!("Expected Let");
1968        }
1969    }
1970
1971    #[test]
1972    fn test_let_tuple_under_binding_is_arity_error() {
1973        let _ = take_parse_errors();
1974        let _ = let_tuple_exp("let :{a, b} <- :{1, 2, 3};".into()).unwrap().1;
1975        let errors = take_parse_errors();
1976        assert_eq!(errors.len(), 1, "under-binding must raise exactly one error");
1977        match &errors[0] {
1978            SyntaxError::TupleDestructureArityMismatch { expected, found, .. } => {
1979                assert_eq!(*expected, 2);
1980                assert_eq!(*found, 3);
1981            }
1982            other => panic!("Expected TupleDestructureArityMismatch, got {:?}", other),
1983        }
1984    }
1985
1986    #[test]
1987    fn test_let_tuple_over_binding_is_arity_error() {
1988        let _ = take_parse_errors();
1989        let _ = let_tuple_exp("let :{a, b, c, d} <- :{1, 2, 3};".into()).unwrap().1;
1990        let errors = take_parse_errors();
1991        assert_eq!(errors.len(), 1, "over-binding must raise exactly one error");
1992        match &errors[0] {
1993            SyntaxError::TupleDestructureArityMismatch { expected, found, .. } => {
1994                assert_eq!(*expected, 4);
1995                assert_eq!(*found, 3);
1996            }
1997            other => panic!("Expected TupleDestructureArityMismatch, got {:?}", other),
1998        }
1999    }
2000
2001    #[test]
2002    fn test_let_tuple_matching_arity_no_error() {
2003        let _ = take_parse_errors();
2004        let _ = let_tuple_exp("let :{a, b, c} <- :{1, 2, 3};".into()).unwrap().1;
2005        let errors = take_parse_errors();
2006        assert!(errors.is_empty(), "matching arity must not raise an error");
2007    }
2008
2009    #[test]
2010    fn test_let_tuple_wildcard_counts_toward_arity() {
2011        let _ = take_parse_errors();
2012        let _ = let_tuple_exp("let :{a, _} <- :{1, 2, 3};".into()).unwrap().1;
2013        let errors = take_parse_errors();
2014        assert_eq!(
2015            errors.len(),
2016            1,
2017            "`_` still occupies a slot, so this is a 2-vs-3 mismatch"
2018        );
2019    }
2020
2021    #[test]
2022    fn test_let_tuple_dynamic_source_no_arity_check() {
2023        // Source is not a syntactic tuple literal (a function call) — arity is
2024        // unknown at parse time, so no error should be raised here.
2025        let _ = take_parse_errors();
2026        let _ = let_tuple_exp("let :{a, b} <- get_pair();".into()).unwrap().1;
2027        let errors = take_parse_errors();
2028        assert!(
2029            errors.is_empty(),
2030            "dynamic (non-literal) tuple source must not be flagged at parse time"
2031        );
2032    }
2033
2034    #[test]
2035    fn test_let_tuple_arity_mismatch_full_parse_has_errors() {
2036        let res = parse("let :{a, b} <- :{1, 2, 3};".into());
2037        assert!(
2038            res.has_errors(),
2039            "arity mismatch should surface through the full parse() entry point"
2040        );
2041    }
2042
2043    #[test]
2044    fn test_let_tuple_in_full_parse() {
2045        let res = parse("let :{a, b, c} <- :{1, 2, 3};".into());
2046        assert!(!res.has_errors(), "Let tuple destructuring should parse without errors");
2047    }
2048
2049    #[test]
2050    fn test_let_tuple_with_equals() {
2051        let res = let_tuple_exp("let :{a, b} = :{1, 2};".into()).unwrap().1;
2052        assert_eq!(res.len(), 3, "Should work with '=' operator too");
2053    }
2054
2055    #[test]
2056    fn test_let_tuple_type_check() {
2057        use crate::components::context::Context;
2058        use crate::processes::type_checking::typing;
2059        use crate::utils::builder;
2060
2061        let ast = parse("let :{a, b, c} <- :{1, 2, 3};".into()).ast;
2062        let context = Context::empty();
2063        let tc = typing(&context, &ast);
2064
2065        // After type-checking, 'a', 'b', 'c' should be in the context as integers
2066        let ty_a = tc
2067            .context
2068            .get_type_from_existing_variable(crate::components::language::var::Var::from_name("a"));
2069        let ty_b = tc
2070            .context
2071            .get_type_from_existing_variable(crate::components::language::var::Var::from_name("b"));
2072        let ty_c = tc
2073            .context
2074            .get_type_from_existing_variable(crate::components::language::var::Var::from_name("c"));
2075
2076        assert_eq!(ty_a, builder::integer_type(1), "Variable 'a' should be Integer(1)");
2077        assert_eq!(ty_b, builder::integer_type(2), "Variable 'b' should be Integer(2)");
2078        assert_eq!(ty_c, builder::integer_type(3), "Variable 'c' should be Integer(3)");
2079    }
2080
2081    #[test]
2082    fn test_let_tuple_type_check_mixed_types() {
2083        use crate::components::context::Context;
2084        use crate::processes::type_checking::typing;
2085        use crate::utils::builder;
2086
2087        let ast = parse("let :{x, y} <- :{1, 'hello'};".into()).ast;
2088        let context = Context::empty();
2089        let tc = typing(&context, &ast);
2090
2091        let ty_x = tc
2092            .context
2093            .get_type_from_existing_variable(crate::components::language::var::Var::from_name("x"));
2094        let ty_y = tc
2095            .context
2096            .get_type_from_existing_variable(crate::components::language::var::Var::from_name("y"));
2097
2098        assert_eq!(ty_x, builder::integer_type(1), "Variable 'x' should be Integer(1)");
2099        assert_eq!(
2100            ty_y,
2101            builder::character_type("hello"),
2102            "Variable 'y' should be Character('hello')"
2103        );
2104    }
2105
2106    #[test]
2107    fn test_fn_function_type_syntax_error() {
2108        let res = parse("let f: fn(a: int) -> int <- fn(x: int): int { x };".into());
2109        assert!(
2110            res.has_errors(),
2111            "Using 'fn(...)' in type position should produce a syntax error"
2112        );
2113        let fn_type_error = res
2114            .errors
2115            .iter()
2116            .any(|e| matches!(e, SyntaxError::FunctionTypeSyntax(_)));
2117        assert!(
2118            fn_type_error,
2119            "Expected FunctionTypeSyntax error, got: {:?}",
2120            res.errors
2121        );
2122    }
2123
2124    #[test]
2125    fn test_use_wildcard_parses() {
2126        use crate::components::language::use_lang::UseSelector;
2127        let lang = parse2("use Math::*;".into()).expect("parse failed");
2128        match lang {
2129            Lang::UseModule {
2130                module_path, selector, ..
2131            } => {
2132                assert_eq!(module_path, vec!["Math".to_string()]);
2133                assert_eq!(selector, UseSelector::Wildcard);
2134            }
2135            other => panic!("Expected UseModule, got {:?}", other),
2136        }
2137    }
2138
2139    #[test]
2140    fn test_use_items_parses() {
2141        use crate::components::language::use_lang::{UseItem, UseSelector};
2142        let lang = parse2("use Math::{pi, sin as s};".into()).expect("parse failed");
2143        match lang {
2144            Lang::UseModule {
2145                module_path, selector, ..
2146            } => {
2147                assert_eq!(module_path, vec!["Math".to_string()]);
2148                assert_eq!(
2149                    selector,
2150                    UseSelector::Items(vec![
2151                        UseItem {
2152                            name: "pi".to_string(),
2153                            alias: None
2154                        },
2155                        UseItem {
2156                            name: "sin".to_string(),
2157                            alias: Some("s".to_string())
2158                        },
2159                    ])
2160                );
2161            }
2162            other => panic!("Expected UseModule, got {:?}", other),
2163        }
2164    }
2165
2166    #[test]
2167    fn test_use_nested_path_parses() {
2168        use crate::components::language::use_lang::UseSelector;
2169        let lang = parse2("use Aa::Bb::Cc::*;".into()).expect("parse failed");
2170        match lang {
2171            Lang::UseModule {
2172                module_path, selector, ..
2173            } => {
2174                assert_eq!(module_path, vec!["Aa".to_string(), "Bb".to_string(), "Cc".to_string()]);
2175                assert_eq!(selector, UseSelector::Wildcard);
2176            }
2177            other => panic!("Expected UseModule, got {:?}", other),
2178        }
2179    }
2180
2181    #[test]
2182    fn test_implicit_mutate_simple_variable() {
2183        let res = implicit_mutate("x!;".into()).unwrap().1;
2184        assert_eq!(res.len(), 1);
2185        match &res[0] {
2186            Lang::Assign {
2187                identifier, expression, ..
2188            } => {
2189                assert!(matches!(identifier.as_ref(), Lang::Variable { .. }));
2190                assert!(matches!(expression.as_ref(), Lang::Variable { .. }));
2191            }
2192            other => panic!("Expected Assign, got {:?}", other),
2193        }
2194    }
2195
2196    #[test]
2197    fn test_implicit_mutate_pipeline() {
2198        let res = implicit_mutate("x |> f()!;".into()).unwrap().1;
2199        assert_eq!(res.len(), 1);
2200        match &res[0] {
2201            Lang::Assign {
2202                identifier, expression, ..
2203            } => {
2204                assert!(matches!(identifier.as_ref(), Lang::Variable { .. }));
2205                assert!(matches!(expression.as_ref(), Lang::Operator { .. }));
2206            }
2207            other => panic!("Expected Assign, got {:?}", other),
2208        }
2209    }
2210
2211    #[test]
2212    fn test_implicit_mutate_ufc() {
2213        let res = implicit_mutate("obj.method()!;".into()).unwrap().1;
2214        assert_eq!(res.len(), 1);
2215        match &res[0] {
2216            Lang::Assign { identifier, .. } => match identifier.as_ref() {
2217                Lang::Variable { name, .. } => assert_eq!(name, "obj"),
2218                other => panic!("Expected Variable identifier, got {:?}", other),
2219            },
2220            other => panic!("Expected Assign, got {:?}", other),
2221        }
2222    }
2223
2224    #[test]
2225    fn test_implicit_mutate_pipeline_chained() {
2226        let res = implicit_mutate("x |> f() |> g()!;".into()).unwrap().1;
2227        assert_eq!(res.len(), 1);
2228        match &res[0] {
2229            Lang::Assign { identifier, .. } => match identifier.as_ref() {
2230                Lang::Variable { name, .. } => assert_eq!(name, "x"),
2231                other => panic!("Expected Variable identifier, got {:?}", other),
2232            },
2233            other => panic!("Expected Assign, got {:?}", other),
2234        }
2235    }
2236
2237    #[test]
2238    fn test_implicit_mutate_ufc_pipeline() {
2239        let res = implicit_mutate("shape.scale(2) |> rotate(90)!;".into()).unwrap().1;
2240        assert_eq!(res.len(), 1);
2241        match &res[0] {
2242            Lang::Assign { identifier, .. } => match identifier.as_ref() {
2243                Lang::Variable { name, .. } => assert_eq!(name, "shape"),
2244                other => panic!("Expected Variable identifier, got {:?}", other),
2245            },
2246            other => panic!("Expected Assign, got {:?}", other),
2247        }
2248    }
2249
2250    #[test]
2251    fn test_implicit_mutate_invalid_literal_fails() {
2252        // 3!; — literal, not assignable: parser should fail (return Err)
2253        let res = implicit_mutate("3!;".into());
2254        assert!(res.is_err(), "Literal mutation should fail to parse");
2255    }
2256
2257    // A1 (audit_type_checking.md): `head_lang` only accepts `Lang::Variable`
2258    // or a `Dot`/`Pipe` chain terminating in one, so `Lang::Assign::identifier`
2259    // is guaranteed to always be a `Lang::Variable` by construction — the
2260    // silent fallback in `eval()`'s `Lang::Assign` arm (`Var::from_language`
2261    // failing) is unreachable dead code, not a live gap. These two cases —
2262    // `$` field access and array indexing — are the ones the audit called
2263    // out as *not* reducible to a plain variable target.
2264    #[test]
2265    fn test_implicit_mutate_dollar_target_fails() {
2266        // p$x!; — `Dollar` isn't one of `head_lang`'s accepted chain links.
2267        let res = implicit_mutate("p$x!;".into());
2268        assert!(
2269            res.is_err(),
2270            "Mutating a `$`-field target should fail to parse, not silently drop"
2271        );
2272    }
2273
2274    #[test]
2275    fn test_implicit_mutate_array_indexing_target_fails() {
2276        // v[1]!; — indexing isn't a `head_lang`-accepted chain link either.
2277        let res = implicit_mutate("v[1]!;".into());
2278        assert!(
2279            res.is_err(),
2280            "Mutating an indexed target should fail to parse, not silently drop"
2281        );
2282    }
2283}