feat(minga): multi-lenguaje en parser — Python, TypeScript, JavaScript, Go
Minga deja de ser Rust-only. Cualquiera de los cinco dialectos
(Rust + 4 nuevos) se ingresa al CAS por su AST normalizado, hashea
estructuralmente, sincroniza por DHT como cualquier nodo. La
auto-deteccion por extension hace que minga ingest archivo.{py,ts,js,go}
"simplemente funcione".
API nueva en minga_core::parse:
- Funciones por dialecto: python, typescript, javascript, go (~6 LOC
c/u sobre el parse_with comun). Mas la rust existente.
- Enum Dialect con parse(source) y name() para logging.
- detect_by_extension(ext) -> Option<Dialect>: rs/py/pyi/ts/js/mjs/
cjs/go (case-insensitive). None para extensiones desconocidas.
Wire en minga-cli:
- cmd_ingest deja de hardcodear parse::rust — usa
detect_dialect(file)?.parse(...).
- initial_scan + cmd_watch cambian is_rs_file -> is_supported_source.
- CliError::UnsupportedLanguage { path, extension } nuevo, lista las
extensiones reconocidas en el mensaje.
Notas sobre hashing:
- Hashing estructural (cas::hash_node) funciona para todos. NO es
alpha-equivalente.
- Hashing alpha-equivalente (alpha::hash_node_alpha) sigue siendo
Rust-only — cada lenguaje tiene reglas distintas para binder vs
constructor; implementacion per-language queda como work futuro
(requiere conocimiento profundo de cada gramatica).
- Sanity test structural_hash_distinguishes_languages verifica que
"x = 1" parseado como Python != JS — las gramaticas no comparten
kinds, hashes salen distintos. Importante para evitar colisiones.
Deps nuevas (workspace + minga-core):
- tree-sitter-python 0.23, tree-sitter-typescript 0.23 (modo
LANGUAGE_TYPESCRIPT, no TSX), tree-sitter-javascript 0.23,
tree-sitter-go 0.23.
Tests: 9 nuevos en parse::tests (parse basico para 5 dialectos +
detect_by_extension canonical/case-insensitive + name() +
structural_hash_distinguishes_languages). 108 verdes en minga-core,
10 en minga-cli, sin regresion.
Pendientes: alpha-hashing per-language; alpha-Rust documentados en
alpha.rs (if let, while let, let-else, let-chains, or_pattern con
bindings).
This commit is contained in:
@@ -83,7 +83,8 @@ pub fn cmd_ingest(
|
||||
let repo = PersistentRepo::open(repo_path.join(REPO_DIRNAME))?;
|
||||
|
||||
let source = fs::read_to_string(file)?;
|
||||
let node = parse::rust(&source)?;
|
||||
let dialect = detect_dialect(file)?;
|
||||
let node = dialect.parse(&source)?;
|
||||
let hash = repo.nodes.put(&node)?;
|
||||
repo.mst.insert(hash)?;
|
||||
repo.attestations
|
||||
@@ -96,6 +97,21 @@ pub fn cmd_ingest(
|
||||
})
|
||||
}
|
||||
|
||||
/// Detecta el dialecto desde la extensión del archivo. Error si la
|
||||
/// extensión no corresponde a un lenguaje soportado.
|
||||
fn detect_dialect(file: &Path) -> Result<parse::Dialect, CliError> {
|
||||
let ext = file
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.unwrap_or("");
|
||||
parse::detect_by_extension(ext).ok_or_else(|| {
|
||||
CliError::UnsupportedLanguage {
|
||||
path: file.to_path_buf(),
|
||||
extension: ext.to_string(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// `minga listen <addr>`: arranca el peer, escucha en `addr`, y
|
||||
/// acepta sincronizaciones entrantes hasta que el proceso se cierre.
|
||||
pub async fn cmd_listen(
|
||||
@@ -191,7 +207,7 @@ pub async fn cmd_watch(
|
||||
continue;
|
||||
}
|
||||
for path in &event.paths {
|
||||
if is_rs_file(path) {
|
||||
if is_supported_source(path) {
|
||||
match ingest_into_repo(&repo, &keypair, path) {
|
||||
Ok(hash) => {
|
||||
eprintln!("ingerido: {} → {}", path.display(), hash);
|
||||
@@ -213,7 +229,7 @@ fn initial_scan(repo: &PersistentRepo, keypair: &Keypair, dir: &Path) {
|
||||
};
|
||||
for entry in entries.flatten() {
|
||||
let p = entry.path();
|
||||
if is_rs_file(&p) {
|
||||
if is_supported_source(&p) {
|
||||
let _ = ingest_into_repo(repo, keypair, &p);
|
||||
}
|
||||
}
|
||||
@@ -225,7 +241,8 @@ fn ingest_into_repo(
|
||||
file: &Path,
|
||||
) -> Result<ContentHash, CliError> {
|
||||
let source = fs::read_to_string(file)?;
|
||||
let node = parse::rust(&source)?;
|
||||
let dialect = detect_dialect(file)?;
|
||||
let node = dialect.parse(&source)?;
|
||||
let hash = repo.nodes.put(&node)?;
|
||||
repo.mst.insert(hash)?;
|
||||
repo.attestations
|
||||
@@ -234,8 +251,14 @@ fn ingest_into_repo(
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
fn is_rs_file(path: &Path) -> bool {
|
||||
path.extension().and_then(|e| e.to_str()) == Some("rs") && path.is_file()
|
||||
/// Detecta si un archivo debe ingerirse: existe, es regular, y su
|
||||
/// extensión corresponde a un dialecto soportado.
|
||||
fn is_supported_source(path: &Path) -> bool {
|
||||
if !path.is_file() {
|
||||
return false;
|
||||
}
|
||||
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
|
||||
parse::detect_by_extension(ext).is_some()
|
||||
}
|
||||
|
||||
fn is_relevant_event(event: ¬ify::Event) -> bool {
|
||||
|
||||
Reference in New Issue
Block a user