Esistono linguaggi di programmazione che consentono di utilizzare altre lingue senza limitazioni all'interno di esse?

0

Questa potrebbe essere una domanda stupida e richiederebbe sicuramente un inferno di un lexer, ma qualsiasi linguaggio di programmazione esistente ti consente di fare qualcosa del tipo:

c# (1.2) {
    // c# code
}
Perl (5) {
    # perl code
    c# (2.0) {
        // some more c# code
    }
}

Per utilizzare più lingue in singoli file sorgente? Sto lavorando a un progetto per un nuovo linguaggio e voglio permettere qualcosa di simile in esso (fondamentalmente per pura pigrizia, non volendo frammenti di porta copiati e incollati dal web, progetti più vecchi, o solo per cazzate e risate).

Nella mia ricerca fino ad ora sembra che l'hacking della lingua intermedia di gcc sia un buon modo per inserirlo in un codice macchina ottimizzato con il minimo sforzo, mentre il lexer sarà una sfida (I ' ho giocato con un plug-in IntelliJ per il linguaggio [di nuovo, la pigrizia, dato che preferirei non dover creare un IDE completo per mettere insieme un mockup dell'editor per la lingua,] ma ho già raggiunto un limite la natura priva di contesto della forma di notazione grammaticale Backus-Naur, quindi sembra che questo dovrà prendere un altro formato semplicemente per le caratteristiche che voglio nella mia lingua senza riguardo per gli altri - facendo la maggior parte dei tutorial di JetBrains sull'argomento per lo più inutile come risultato.) Questo a sua volta potrebbe essere un po 'una benedizione poiché è una possibilità di astrarre la definizione della lingua in una laurea con una sorta di bootloader grammaticale nel lexer e nelle specifiche del linguaggio incluse lungo il percorso - es. per lo pseudocodice sopra qualcosa come:

#require c#-1.2
#require Perl-5
#require c#-2.0

Nella parte superiore di un dato file sorgente o da qualche parte all'interno della catena di inclusione (con la mia nuova lingua come predefinita e la grammatica per creare nuovi metodi di grammatica e analisi incorporati per caricare tutto il processo in modo lessicale.

Comunque, tornando al punto: esiste già qualcosa di simile che potrei usare come pietra di paragone? Soprattutto in relazione a parser e lessici dinamici (in cui il codice già analizzato può modificare in modo significativo l'interpretazione del codice futuro - simile alla ridefinizione dell'intero linguaggio per un dato contesto [blocco di codice.])

    
posta CoryG 30.05.2018 - 16:14
fonte

1 risposta

3

Non proprio. L'interoperabilità linguistica è un problema difficile e l'inclusione del linguaggio ancora di più.

Molte lingue hanno costrutti di sintassi non banali che non possono essere facilmente analizzati da un parser generico. Perl 5 ne è l'esempio principale: per analizzare un programma Perl 5, è necessario eseguirlo. Per esempio. ecco un programma Perl 5 che si lamenterà di una parentesi di chiusura extra il 50% delle volte:

BEGIN {
  print "Running arbitrary code during parsing :)\n";
  *f = rand() > 0.5 ? sub () {} : sub ($) {};
}
for ("the lulz") {
  f /7 } # /
}

Come funziona? Definiamo una funzione f con un prototipo. Il prototipo influisce sull'analisi degli argomenti. Il prototipo ($) analizza un argomento, che sarà il risultato dell'applicazione della regex /7 } # / rispetto a $_ . Ma il prototipo () non analizza alcun argomento, quindi f /7 è una divisione e # / è un commento.

Considera anche che alcuni linguaggi come la famiglia Lisp hanno un concetto completamente diverso di "sintassi" rispetto ai linguaggi di parentesi graffa simili a C. I linguaggi sensibili all'indentazione come Python o Haskell non hanno rappresentazione in BNF. Quindi l'incorporamento sintattico è davvero difficile. Invece, l'incorporamento userà tipicamente qualche tipo di stringa o sintassi di here-doc.

Il problema più difficile è l'interoperabilità semantica. Questo non è fondamentalmente un problema risolvibile, perché le lingue sono follemente diverse. Ci sono semplici considerazioni come la gestione della memoria. Se si passa un oggetto raccolto in una lingua con gestione manuale della memoria, come verrà smaltito? L'oggetto potrebbe ora essere referenziato dal codice in entrambe le lingue e il garbage collector potrebbe non essere in grado di vedere alcuni riferimenti.

I sistemi oggetto sono un'altra area di grandi differenze. In JavaScript e in molti altri linguaggi dinamici, posso applicare i metodi al runtime. Come può funzionare in C ++ dove le chiamate al metodo possono essere risolte al momento della compilazione? In Java, l'ordine di inizializzazione degli oggetti è effettivamente genitore-prima-figlio. In che modo le classi Java esistenti possono funzionare quando fanno parte di un progetto di ereditarietà multipla in Python? C non ha nemmeno un sistema di oggetti di prima classe.

Ci sono anche alcune piccole differenze di sistema di tipo gazillion. Una lingua ha stringhe immutabili, un'altra no. In C o C ++, non è possibile chiamare determinate funzioni a meno che non si disponga di un oggetto non const. Il supporto per le funzioni variadic è incoerente, variando tra "l'unico tipo di funzione" (Perl) su "sintassi speciale per un argomento di matrice" (Java) su "espansione del modello in fase di compilazione" (C ++). Alcune lingue hanno una sorta di namespace, altre no. Alcune lingue supportano la semantica del valore per i tipi definiti dall'utente. I generici differiscono selvaggiamente. Funzioni sovraccaricate. E così via.

Il risultato di queste incompatibilità è che deve esserci un qualche livello di interfaccia. Molto spesso, questo deve essere esteso per i tipi definiti dall'utente. O gli oggetti vengono convertiti in una rappresentazione diversa oppure sono rappresentati da una sorta di oggetto wrapper nella lingua di destinazione. Chiamare una funzione non tipizzata, variadica da un linguaggio statico spesso finisce per essere piuttosto scomodo. Non sembrerà mai nativo.

Ci sono esempi specifici in cui un qualche tipo di interoperabilità funziona bene. Ad esempio, la generazione automatica di binding di lingua da una definizione di origine come SWIG. Alcune piattaforme linguistiche come CLR e JVM definiscono un modello semantico comune che consente una facile interoperabilità per tutte le lingue che utilizzano questa piattaforma. Esistono anche alcuni approcci all'interoperabilità che non sono invisibili, ma funzionano comunque bene. Ad esempio gli FFI o il modello a oggetti componenti. Ma questi generalmente hanno severe restrizioni semantiche.

Pertanto ti esorto ad abbandonare la tua ricerca di perfetta interoperabilità linguistica tra una vasta gamma di lingue. Invece, potresti avere più fortuna nel migliorare l'interoperabilità tra due lingue specifiche, ad es. "R in Python" o somesuch. In generale, sarà molto più difficile interfacciare due lingue quando ognuna di esse definisce la propria VM e più semplice se le lingue possono comunicare attraverso un'interfaccia semplice come la convenzione di chiamata C.

In pratica, l'interoperabilità linguistica è così terribilmente difficile che l'interoperabilità in-process è spesso evitata del tutto (a meno che non condividano una piattaforma comune, o una delle lingue sia C). Invece, la comunicazione tra processi su un socket o pipe risulta molto più semplice. Vedi anche: filosofia Unix, microservizi.

    
risposta data 30.05.2018 - 21:49
fonte