Skip to content

Commit 57ecedb

Browse files
hyperpolymathclaude
andcommitted
feat(lang-mcp): add JtV dialect injection mode
Julia-the-Viper (JtV) is injectable into any nextgen-language, augmenting its grammar with JtV syntax extensions. Each language can now be requested in two modes: - Pure: original grammar (e.g. "mylang", "eclexia") - JtV: JtV-injected grammar (e.g. "mylang+jtv", "eclexia+jtv") Implementation: - DialectMode enum (pure=0, jtv=1) in Zig FFI - lang_session_start_dialect() with explicit mode parameter - Typecheck/eval endpoints gain /jtv suffix in JtV mode (e.g. POST /typecheck/jtv, POST /eval/jtv) - V-lang adapter parses "lang+jtv" shorthand automatically - list_languages() now returns both languages and dialect options - 2 new JtV-specific tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 02740b4 commit 57ecedb

2 files changed

Lines changed: 82 additions & 6 deletions

File tree

cartridges/lang-mcp/adapter/lang_adapter.v

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import json
1616
// ═══════════════════════════════════════════════════════════════════════
1717

1818
fn C.lang_session_start(lang_id int, name_ptr &u8, name_len usize) int
19+
fn C.lang_session_start_dialect(lang_id int, dialect_mode int, name_ptr &u8, name_len usize) int
20+
fn C.lang_session_dialect(sess_idx int) int
1921
fn C.lang_session_set_url(sess_idx int, url_ptr &u8, url_len usize) int
2022
fn C.lang_session_end(sess_idx int) int
2123
fn C.lang_session_state(sess_idx int) int
@@ -95,9 +97,24 @@ struct SessionResponse {
9597
// Adapter Functions (called by main adapter router)
9698
// ═══════════════════════════════════════════════════════════════════════
9799

100+
// Start a language session. dialect: "pure" (default) or "jtv" (JtV-injected).
101+
// JtV (Julia-the-Viper) is injectable into any other language, augmenting its
102+
// grammar with JtV syntax extensions. Request "eclexia+jtv" or pass dialect="jtv".
98103
pub fn start_session(language_name string, session_name string) !SessionResponse {
99-
lang_id := language_label(language_name)!
100-
sess := C.lang_session_start(lang_id, session_name.str, usize(session_name.len))
104+
return start_session_dialect(language_name, 'pure', session_name)
105+
}
106+
107+
pub fn start_session_dialect(language_name string, dialect string, session_name string) !SessionResponse {
108+
// Parse "eclexia+jtv" shorthand
109+
mut lang := language_name
110+
mut dial := dialect
111+
if language_name.contains('+jtv') {
112+
lang = language_name.replace('+jtv', '')
113+
dial = 'jtv'
114+
}
115+
lang_id := language_label(lang)!
116+
dialect_mode := if dial == 'jtv' { 1 } else { 0 }
117+
sess := C.lang_session_start_dialect(lang_id, dialect_mode, session_name.str, usize(session_name.len))
101118
if sess < 0 {
102119
return match sess {
103120
-1 { error('no session slots available (max 8)') }
@@ -107,7 +124,7 @@ pub fn start_session(language_name string, session_name string) !SessionResponse
107124
}
108125
return SessionResponse{
109126
session: sess
110-
language: language_name
127+
language: if dial == 'jtv' { '${lang}+jtv' } else { lang }
111128
state: 'idle'
112129
}
113130
}
@@ -175,7 +192,7 @@ pub fn evaluate(session int, source string) !string {
175192
}
176193

177194
pub fn list_languages() string {
178-
return '["eclexia","affinescript","betlang","ephapax","mylang","wokelang","anvomidav","phronesis","error-lang","julia-the-viper","me-dialect","oblibeny"]'
195+
return '{"languages":["eclexia","affinescript","betlang","ephapax","mylang","wokelang","anvomidav","phronesis","error-lang","julia-the-viper","me-dialect","oblibeny"],"dialects":["pure","jtv"],"note":"JtV (Julia-the-Viper) is injectable into any language. Request \'eclexia+jtv\' or pass dialect=jtv to get JtV-augmented grammar alongside the base language."}'
179196
}
180197

181198
pub fn reset() {

cartridges/lang-mcp/ffi/lang_ffi.zig

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
8799
pub 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).
108140
pub 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+
394453
test "session URL rejects empty and overlong" {
395454
lang_reset();
396455
const name = "url-reject";

0 commit comments

Comments
 (0)