@@ -42,6 +42,15 @@ pub const Language = enum(c_int) {
4242 custom = 99 ,
4343};
4444
45+ /// Dialect mode: pure grammar or JtV-injected.
46+ /// Julia-the-Viper (JtV) is injectable into any other language,
47+ /// augmenting it with JtV's syntax extensions. Each language can
48+ /// be requested as pure (original grammar) or jtv (JtV-injected).
49+ pub const DialectMode = enum (c_int ) {
50+ pure = 0 ,
51+ jtv = 1 ,
52+ };
53+
4554// ═══════════════════════════════════════════════════════════════════════
4655// Session State Machine
4756// ═══════════════════════════════════════════════════════════════════════
@@ -54,6 +63,7 @@ const LangSession = struct {
5463 active : bool ,
5564 state : LangState ,
5665 language : Language ,
66+ dialect : DialectMode ,
5767 url_buf : [URL_BUF_SIZE ]u8 ,
5868 url_len : usize ,
5969 name_buf : [NAME_BUF_SIZE ]u8 ,
@@ -64,6 +74,7 @@ var sessions: [MAX_SESSIONS]LangSession = [_]LangSession{.{
6474 .active = false ,
6575 .state = .idle ,
6676 .language = .custom ,
77+ .dialect = .pure ,
6778 .url_buf = [_ ]u8 {0 } ** URL_BUF_SIZE ,
6879 .url_len = 0 ,
6980 .name_buf = [_ ]u8 {0 } ** NAME_BUF_SIZE ,
@@ -84,7 +95,16 @@ fn isValidTransition(from: LangState, to: LangState) bool {
8495}
8596
8697/// Start a language session. Returns session index or -1.
98+ /// dialect_mode: 0 = pure grammar, 1 = JtV-injected.
8799pub export fn lang_session_start (lang_id : c_int , name_ptr : [* ]const u8 , name_len : usize ) c_int {
100+ return lang_session_start_dialect (lang_id , 0 , name_ptr , name_len );
101+ }
102+
103+ /// Start a language session with explicit dialect mode.
104+ /// dialect_mode: 0 = pure grammar, 1 = JtV-injected.
105+ /// When JtV mode is active, the language service URL path gains a /jtv suffix
106+ /// (e.g. /typecheck/jtv, /eval/jtv) so the backend can apply JtV grammar injection.
107+ pub export fn lang_session_start_dialect (lang_id : c_int , dialect_mode : c_int , name_ptr : [* ]const u8 , name_len : usize ) c_int {
88108 mutex .lock ();
89109 defer mutex .unlock ();
90110
@@ -95,6 +115,7 @@ pub export fn lang_session_start(lang_id: c_int, name_ptr: [*]const u8, name_len
95115 sess .active = true ;
96116 sess .state = .idle ;
97117 sess .language = @enumFromInt (lang_id );
118+ sess .dialect = if (dialect_mode == 1 ) .jtv else .pure ;
98119 sess .url_len = 0 ;
99120 @memcpy (sess .name_buf [0.. name_len ], name_ptr [0.. name_len ]);
100121 sess .name_len = name_len ;
@@ -104,6 +125,17 @@ pub export fn lang_session_start(lang_id: c_int, name_ptr: [*]const u8, name_len
104125 return -1 ; // No sessions available
105126}
106127
128+ /// Get the dialect mode of a session (0 = pure, 1 = jtv).
129+ pub export fn lang_session_dialect (sess_idx : c_int ) c_int {
130+ mutex .lock ();
131+ defer mutex .unlock ();
132+
133+ if (sess_idx < 0 or sess_idx >= MAX_SESSIONS ) return -1 ;
134+ const idx : usize = @intCast (sess_idx );
135+ if (! sessions [idx ].active ) return -1 ;
136+ return @intFromEnum (sessions [idx ].dialect );
137+ }
138+
107139/// Set the language service URL for a session (for remote compilation/eval).
108140pub export fn lang_session_set_url (sess_idx : c_int , url_ptr : [* ]const u8 , url_len : usize ) c_int {
109141 mutex .lock ();
@@ -176,7 +208,7 @@ pub export fn lang_typecheck(sess_idx: c_int, src_ptr: [*]const u8, src_len: usi
176208 if (sessions [idx ].url_len == 0 ) return -6 ;
177209
178210 const url_slice = sessions [idx ].url_buf [0.. sessions [idx ].url_len ];
179- const suffix = "/typecheck" ;
211+ const suffix = if ( sessions [ idx ]. dialect == .jtv ) "/typecheck/jtv" else "/typecheck" ;
180212 if (url_slice .len + suffix .len >= endpoint_buf .len ) return -6 ;
181213 @memcpy (endpoint_buf [0.. url_slice .len ], url_slice );
182214 @memcpy (endpoint_buf [url_slice .len .. ][0.. suffix .len ], suffix );
@@ -236,7 +268,7 @@ pub export fn lang_eval(sess_idx: c_int, src_ptr: [*]const u8, src_len: usize, o
236268 if (sessions [idx ].url_len == 0 ) return -6 ;
237269
238270 const url_slice = sessions [idx ].url_buf [0.. sessions [idx ].url_len ];
239- const suffix = "/eval" ;
271+ const suffix = if ( sessions [ idx ]. dialect == .jtv ) "/eval/jtv" else "/eval" ;
240272 if (url_slice .len + suffix .len >= endpoint_buf .len ) return -6 ;
241273 @memcpy (endpoint_buf [0.. url_slice .len ], url_slice );
242274 @memcpy (endpoint_buf [url_slice .len .. ][0.. suffix .len ], suffix );
@@ -391,6 +423,33 @@ test "all 12 languages can start sessions" {
391423 try std .testing .expectEqual (@as (c_int , -1 ), overflow );
392424}
393425
426+ test "jtv dialect mode on session" {
427+ lang_reset ();
428+ const name = "jtv-test" ;
429+ // Pure mode (default)
430+ const pure_sess = lang_session_start (@intFromEnum (Language .eclexia ), name , name .len );
431+ try std .testing .expect (pure_sess >= 0 );
432+ try std .testing .expectEqual (@as (c_int , 0 ), lang_session_dialect (pure_sess ));
433+ _ = lang_session_end (pure_sess );
434+
435+ // JtV-injected mode
436+ const jtv_sess = lang_session_start_dialect (@intFromEnum (Language .eclexia ), 1 , name , name .len );
437+ try std .testing .expect (jtv_sess >= 0 );
438+ try std .testing .expectEqual (@as (c_int , 1 ), lang_session_dialect (jtv_sess ));
439+ _ = lang_session_end (jtv_sess );
440+ }
441+
442+ test "jtv mode works for all languages" {
443+ lang_reset ();
444+ const name = "jtv-all" ;
445+ // Start eclexia+jtv
446+ const sess = lang_session_start_dialect (@intFromEnum (Language .affinescript ), 1 , name , name .len );
447+ try std .testing .expect (sess >= 0 );
448+ try std .testing .expectEqual (@as (c_int , 1 ), lang_session_dialect (sess ));
449+ try std .testing .expectEqual (@as (c_int , @intFromEnum (Language .affinescript )), lang_session_language (sess ));
450+ _ = lang_session_end (sess );
451+ }
452+
394453test "session URL rejects empty and overlong" {
395454 lang_reset ();
396455 const name = "url-reject" ;
0 commit comments