@@ -19,8 +19,10 @@ use winnow::{self};
1919
2020use crate :: CompilationIssue ;
2121use crate :: ModuleId ;
22+ use crate :: RuntimeFlag ;
2223use crate :: SourceRange ;
2324use crate :: errors:: KclError ;
25+ use crate :: kcl_runtime_flags;
2426use crate :: parsing:: ast:: types:: ItemVisibility ;
2527use crate :: parsing:: ast:: types:: VariableKind ;
2628
@@ -601,8 +603,8 @@ pub(crate) const KCL_LEXER_ENV_VAR: &str = "KCL_LEXER";
601603/// `KCL_LEXER` environment variable, so a process can pick either lexer without a
602604/// rebuild.
603605///
604- /// Precedence: test override > `KCL_LEXER` > [`LexerMode::DEFAULT`]. This mirrors
605- /// the existing `KCL_MEMORY_IMPL` selector in `execution::memory` .
606+ /// Precedence: runtime flags > test override > `KCL_LEXER` >
607+ /// [`LexerMode::DEFAULT`] .
606608#[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
607609pub ( crate ) enum LexerMode {
608610 Old ,
@@ -615,14 +617,9 @@ impl LexerMode {
615617
616618 /// Resolve the active lexer mode (see precedence on [`LexerMode`]).
617619 pub ( crate ) fn resolve ( ) -> Self {
618- #[ cfg( test) ]
619- if let Some ( mode) = Self :: test_override ( ) {
620- return mode;
621- }
622-
623- match env:: var ( KCL_LEXER_ENV_VAR ) {
624- Ok ( value) => Self :: parse ( & value) ,
625- Err ( env:: VarError :: NotPresent ) => Self :: DEFAULT ,
620+ let env_value = match env:: var ( KCL_LEXER_ENV_VAR ) {
621+ Ok ( value) => Some ( value) ,
622+ Err ( env:: VarError :: NotPresent ) => None ,
626623 Err ( env:: VarError :: NotUnicode ( value) ) => {
627624 // Invalid-unicode env var: warn and fall back rather than crash.
628625 Self :: warn_once ( || {
@@ -631,9 +628,39 @@ impl LexerMode {
631628 value. to_string_lossy( )
632629 )
633630 } ) ;
634- Self :: Old
631+ None
635632 }
633+ } ;
634+
635+ Self :: resolve_from_sources (
636+ kcl_runtime_flags ( ) . use_new_lexer_parser ,
637+ Self :: test_override_for_resolve ( ) ,
638+ env_value. as_deref ( ) ,
639+ )
640+ }
641+
642+ fn resolve_from_sources ( runtime_flag : RuntimeFlag , test_override : Option < Self > , env_value : Option < & str > ) -> Self {
643+ match runtime_flag {
644+ RuntimeFlag :: On => return Self :: New ,
645+ RuntimeFlag :: Off => return Self :: Old ,
646+ RuntimeFlag :: Unset => { }
647+ }
648+
649+ if let Some ( mode) = test_override {
650+ return mode;
636651 }
652+
653+ env_value. map ( Self :: parse) . unwrap_or ( Self :: DEFAULT )
654+ }
655+
656+ #[ cfg( test) ]
657+ fn test_override_for_resolve ( ) -> Option < Self > {
658+ Self :: test_override ( )
659+ }
660+
661+ #[ cfg( not( test) ) ]
662+ fn test_override_for_resolve ( ) -> Option < Self > {
663+ None
637664 }
638665
639666 fn parse ( value : & str ) -> Self {
@@ -759,10 +786,23 @@ fn lex_legacy(s: &str, module_id: ModuleId) -> Result<TokenStream, KclError> {
759786mod lexer_mode_tests {
760787 use super :: LexerMode ;
761788 use super :: lex;
789+ use crate :: KclRuntimeFlags ;
762790 use crate :: ModuleId ;
791+ use crate :: RuntimeFlag ;
792+
793+ fn set_runtime_lexer_flag ( flag : RuntimeFlag ) {
794+ crate :: set_kcl_runtime_flags ( KclRuntimeFlags {
795+ use_new_lexer_parser : flag,
796+ } ) ;
797+ }
798+
799+ fn reset_runtime_lexer_flags ( ) {
800+ crate :: set_kcl_runtime_flags ( KclRuntimeFlags :: DEFAULT ) ;
801+ }
763802
764803 #[ test]
765804 fn default_mode_is_old ( ) {
805+ reset_runtime_lexer_flags ( ) ;
766806 assert_eq ! ( LexerMode :: DEFAULT , LexerMode :: Old ) ;
767807 }
768808
@@ -780,6 +820,7 @@ mod lexer_mode_tests {
780820
781821 #[ test]
782822 fn override_guard_sets_and_restores_mode ( ) {
823+ reset_runtime_lexer_flags ( ) ;
783824 // Reserved for dispatch/integration tests; relies on the process-global
784825 // atomic, which is race-free under nextest's process isolation.
785826 {
@@ -790,6 +831,75 @@ mod lexer_mode_tests {
790831 assert_eq ! ( LexerMode :: resolve( ) , LexerMode :: Old ) ;
791832 }
792833
834+ #[ test]
835+ fn runtime_flags_default_to_unset ( ) {
836+ reset_runtime_lexer_flags ( ) ;
837+ assert_eq ! (
838+ crate :: kcl_runtime_flags( ) ,
839+ KclRuntimeFlags {
840+ use_new_lexer_parser: RuntimeFlag :: Unset ,
841+ }
842+ ) ;
843+ }
844+
845+ #[ test]
846+ fn runtime_flag_on_selects_new_lexer ( ) {
847+ reset_runtime_lexer_flags ( ) ;
848+ set_runtime_lexer_flag ( RuntimeFlag :: On ) ;
849+ assert_eq ! ( LexerMode :: resolve( ) , LexerMode :: New ) ;
850+ }
851+
852+ #[ test]
853+ fn runtime_flag_off_selects_old_lexer ( ) {
854+ reset_runtime_lexer_flags ( ) ;
855+ set_runtime_lexer_flag ( RuntimeFlag :: Off ) ;
856+ assert_eq ! ( LexerMode :: resolve( ) , LexerMode :: Old ) ;
857+ }
858+
859+ #[ test]
860+ fn runtime_flag_takes_priority_over_test_override_and_env ( ) {
861+ assert_eq ! (
862+ LexerMode :: resolve_from_sources( RuntimeFlag :: Off , Some ( LexerMode :: New ) , Some ( "new" ) ) ,
863+ LexerMode :: Old
864+ ) ;
865+ assert_eq ! (
866+ LexerMode :: resolve_from_sources( RuntimeFlag :: On , Some ( LexerMode :: Old ) , Some ( "old" ) ) ,
867+ LexerMode :: New
868+ ) ;
869+ }
870+
871+ #[ test]
872+ fn unset_runtime_flag_allows_env_to_select_lexer ( ) {
873+ assert_eq ! (
874+ LexerMode :: resolve_from_sources( RuntimeFlag :: Unset , None , Some ( "new" ) ) ,
875+ LexerMode :: New
876+ ) ;
877+ assert_eq ! (
878+ LexerMode :: resolve_from_sources( RuntimeFlag :: Unset , None , Some ( "old" ) ) ,
879+ LexerMode :: Old
880+ ) ;
881+ }
882+
883+ #[ test]
884+ fn unset_runtime_flag_and_missing_env_selects_default_lexer ( ) {
885+ assert_eq ! (
886+ LexerMode :: resolve_from_sources( RuntimeFlag :: Unset , None , None ) ,
887+ LexerMode :: DEFAULT
888+ ) ;
889+ }
890+
891+ #[ test]
892+ fn test_override_takes_priority_over_env ( ) {
893+ assert_eq ! (
894+ LexerMode :: resolve_from_sources( RuntimeFlag :: Unset , Some ( LexerMode :: Old ) , Some ( "new" ) ) ,
895+ LexerMode :: Old
896+ ) ;
897+ assert_eq ! (
898+ LexerMode :: resolve_from_sources( RuntimeFlag :: Unset , Some ( LexerMode :: New ) , Some ( "old" ) ) ,
899+ LexerMode :: New
900+ ) ;
901+ }
902+
793903 /// Exercises the `New` arm of `lex` in default CI: no `KCL_LEXER` env var is
794904 /// set; the new lexer is selected via the process-global test override (which
795905 /// is race-free under nextest's process-per-test isolation).
@@ -801,6 +911,7 @@ mod lexer_mode_tests {
801911 /// not merely that some lexer ran.
802912 #[ test]
803913 fn lex_dispatches_to_new_lexer ( ) {
914+ reset_runtime_lexer_flags ( ) ;
804915 let _guard = LexerMode :: override_for_test ( LexerMode :: New ) ;
805916 assert_eq ! ( LexerMode :: resolve( ) , LexerMode :: New ) ;
806917
0 commit comments