|
| 1 | +// SPDX-License-Identifier: PMPL-1.0-or-later |
| 2 | +// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk> |
| 3 | +// |
| 4 | +// Lang-MCP Cartridge — V-lang adapter layer. |
| 5 | +// |
| 6 | +// Bridges the Zig FFI (lang_ffi.zig) to REST/gRPC/GraphQL endpoints. |
| 7 | +// Provides language session management, type-checking, and evaluation |
| 8 | +// for the hyperpolymath nextgen-languages family. |
| 9 | + |
| 10 | +module lang_adapter |
| 11 | + |
| 12 | +import json |
| 13 | + |
| 14 | +// ═══════════════════════════════════════════════════════════════════════ |
| 15 | +// C FFI declarations (link against lang_ffi built from Zig) |
| 16 | +// ═══════════════════════════════════════════════════════════════════════ |
| 17 | + |
| 18 | +fn C.lang_session_start(lang_id int, name_ptr &u8, name_len usize) int |
| 19 | +fn C.lang_session_set_url(sess_idx int, url_ptr &u8, url_len usize) int |
| 20 | +fn C.lang_session_end(sess_idx int) int |
| 21 | +fn C.lang_session_state(sess_idx int) int |
| 22 | +fn C.lang_session_language(sess_idx int) int |
| 23 | +fn C.lang_typecheck(sess_idx int, src_ptr &u8, src_len usize, out_ptr &u8, out_len usize) int |
| 24 | +fn C.lang_eval(sess_idx int, src_ptr &u8, src_len usize, out_ptr &u8, out_len usize) int |
| 25 | +fn C.lang_reset() |
| 26 | + |
| 27 | +// ═══════════════════════════════════════════════════════════════════════ |
| 28 | +// Types |
| 29 | +// ═══════════════════════════════════════════════════════════════════════ |
| 30 | + |
| 31 | +enum LangState { |
| 32 | + idle = 0 |
| 33 | + compiling = 1 |
| 34 | + checked = 2 |
| 35 | + evaluating = 3 |
| 36 | + err = 4 |
| 37 | +} |
| 38 | + |
| 39 | +enum Language { |
| 40 | + eclexia = 1 |
| 41 | + affinescript = 2 |
| 42 | + betlang = 3 |
| 43 | + ephapax = 4 |
| 44 | + mylang = 5 |
| 45 | + wokelang = 6 |
| 46 | + anvomidav = 7 |
| 47 | + phronesis = 8 |
| 48 | + error_lang = 9 |
| 49 | + julia_the_viper = 10 |
| 50 | + me_dialect = 11 |
| 51 | + oblibeny = 12 |
| 52 | + custom = 99 |
| 53 | +} |
| 54 | + |
| 55 | +fn state_label(s int) string { |
| 56 | + return match s { |
| 57 | + 0 { 'idle' } |
| 58 | + 1 { 'compiling' } |
| 59 | + 2 { 'checked' } |
| 60 | + 3 { 'evaluating' } |
| 61 | + 4 { 'error' } |
| 62 | + else { 'unknown' } |
| 63 | + } |
| 64 | +} |
| 65 | + |
| 66 | +fn language_label(lang string) !int { |
| 67 | + return match lang { |
| 68 | + 'eclexia' { 1 } |
| 69 | + 'affinescript' { 2 } |
| 70 | + 'betlang' { 3 } |
| 71 | + 'ephapax' { 4 } |
| 72 | + 'mylang' { 5 } |
| 73 | + 'wokelang' { 6 } |
| 74 | + 'anvomidav' { 7 } |
| 75 | + 'phronesis' { 8 } |
| 76 | + 'error-lang' { 9 } |
| 77 | + 'julia-the-viper' { 10 } |
| 78 | + 'me-dialect' { 11 } |
| 79 | + 'oblibeny' { 12 } |
| 80 | + else { return error('unknown language: ${lang}') } |
| 81 | + } |
| 82 | +} |
| 83 | + |
| 84 | +// ═══════════════════════════════════════════════════════════════════════ |
| 85 | +// REST API Responses |
| 86 | +// ═══════════════════════════════════════════════════════════════════════ |
| 87 | + |
| 88 | +struct SessionResponse { |
| 89 | + session int |
| 90 | + language string |
| 91 | + state string |
| 92 | +} |
| 93 | + |
| 94 | +// ═══════════════════════════════════════════════════════════════════════ |
| 95 | +// Adapter Functions (called by main adapter router) |
| 96 | +// ═══════════════════════════════════════════════════════════════════════ |
| 97 | + |
| 98 | +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)) |
| 101 | + if sess < 0 { |
| 102 | + return match sess { |
| 103 | + -1 { error('no session slots available (max 8)') } |
| 104 | + -2 { error('invalid session name') } |
| 105 | + else { error('unknown error (code ${sess})') } |
| 106 | + } |
| 107 | + } |
| 108 | + return SessionResponse{ |
| 109 | + session: sess |
| 110 | + language: language_name |
| 111 | + state: 'idle' |
| 112 | + } |
| 113 | +} |
| 114 | + |
| 115 | +pub fn set_url(session int, url string) !string { |
| 116 | + result := C.lang_session_set_url(session, url.str, usize(url.len)) |
| 117 | + return match result { |
| 118 | + 0 { 'URL set for session ${session}' } |
| 119 | + -1 { return error('invalid or inactive session') } |
| 120 | + -6 { return error('URL empty or too long') } |
| 121 | + else { return error('unknown error (code ${result})') } |
| 122 | + } |
| 123 | +} |
| 124 | + |
| 125 | +pub fn end_session(session int) !string { |
| 126 | + result := C.lang_session_end(session) |
| 127 | + return match result { |
| 128 | + 0 { 'session ${session} ended' } |
| 129 | + -1 { return error('invalid or inactive session') } |
| 130 | + else { return error('unknown error (code ${result})') } |
| 131 | + } |
| 132 | +} |
| 133 | + |
| 134 | +pub fn get_state(session int) string { |
| 135 | + s := C.lang_session_state(session) |
| 136 | + return state_label(s) |
| 137 | +} |
| 138 | + |
| 139 | +pub fn typecheck(session int, source string) !string { |
| 140 | + if session < 0 || session > 7 { |
| 141 | + return error('invalid session index: ${session}') |
| 142 | + } |
| 143 | + mut out_buf := []u8{len: 65536} |
| 144 | + result := C.lang_typecheck(session, source.str, usize(source.len), out_buf.data, usize(out_buf.len)) |
| 145 | + if result < 0 { |
| 146 | + return match result { |
| 147 | + -1 { error('invalid or inactive session') } |
| 148 | + -2 { error('session not in valid state for type-checking') } |
| 149 | + -5 { error('output buffer too small') } |
| 150 | + -6 { error('no service URL set for this session') } |
| 151 | + -7 { error('type-check service call failed') } |
| 152 | + else { error('unknown error (code ${result})') } |
| 153 | + } |
| 154 | + } |
| 155 | + return out_buf[..result].bytestr() |
| 156 | +} |
| 157 | + |
| 158 | +pub fn evaluate(session int, source string) !string { |
| 159 | + if session < 0 || session > 7 { |
| 160 | + return error('invalid session index: ${session}') |
| 161 | + } |
| 162 | + mut out_buf := []u8{len: 65536} |
| 163 | + result := C.lang_eval(session, source.str, usize(source.len), out_buf.data, usize(out_buf.len)) |
| 164 | + if result < 0 { |
| 165 | + return match result { |
| 166 | + -1 { error('invalid or inactive session') } |
| 167 | + -2 { error('session not in valid state for evaluation') } |
| 168 | + -5 { error('output buffer too small') } |
| 169 | + -6 { error('no service URL set for this session') } |
| 170 | + -7 { error('evaluation service call failed') } |
| 171 | + else { error('unknown error (code ${result})') } |
| 172 | + } |
| 173 | + } |
| 174 | + return out_buf[..result].bytestr() |
| 175 | +} |
| 176 | + |
| 177 | +pub fn list_languages() string { |
| 178 | + return '["eclexia","affinescript","betlang","ephapax","mylang","wokelang","anvomidav","phronesis","error-lang","julia-the-viper","me-dialect","oblibeny"]' |
| 179 | +} |
| 180 | + |
| 181 | +pub fn reset() { |
| 182 | + C.lang_reset() |
| 183 | +} |
0 commit comments