Forth: come funzionano esattamente CREATE e DOES?

0

Sono in procinto di creare il mio linguaggio concatenativo, basato pesantemente su Forth.

Ho qualche problema a capire come funzionano le parole di compilazione CREATE e DOES> , e come sono implementate (come lo stato dell'ambiente di esecuzione di Forth cambia esattamente quando vengono eseguite).

Ho letto le seguenti risorse che danno una visione generale, ma solo su come usarle e non su come un sistema le implementa:

Le seguenti cose sul comportamento di queste due parole non mi sono chiare:

  • CREATE prende la parola successiva (delimitata dallo spazio) dal flusso di input e crea un nuovo elemento di dizionario per esso.
    • Cosa succede allora?
    • CREATE completa qualcosa nel nuovo elemento del dizionario o no?
    • Che cosa restituisce CREATE (in pila?)?
    • C'è qualcosa di speciale nelle parole tra CREATE e DOES> ?
  • DOES> 'compila' il comportamento del tempo di esecuzione della parola creata.
    • Quanto consuma DOES> come input?
    • Come altera la voce del dizionario della parola CREATE?
    • Nei frammenti di codice come 17 CREATE SEVENTEEN , , non viene utilizzato DOES> . Esiste una sorta di "comportamento predefinito" che DOES> sovrascrive?

Ovviamente questi diversi non-problemi nascono tutti dal problema centrale, che ho difficoltà a capire cosa sta succedendo e come questi concetti, che sembrano piuttosto complessi, possono essere / sono implementati in modo semplice in un linguaggio di basso livello come Assembly.

In che modo CREATE e DOES> funzionano esattamente?

    
posta Qqwy 02.01.2017 - 13:46
fonte

3 risposte

3

Quindi, sono un po 'in ritardo sul gioco, ma queste domande (in particolare su DOES >) mi stavano anche mistificando, essendo nuove di Forth. Ecco cosa ho imparato e come l'ho implementato:

[ TL; DR: "CREATE" crea una parola con un comportamento semplice e predefinito. "SE >" non ritorna al suo chiamante. Invece, usa l'indirizzo di ritorno per inserire un "goto" nella definizione più recente.]

CREATE non prende nulla dalla pila o non restituisce nulla. Analizza una parola dall'input e crea una voce di dizionario per esso. Riempie il codice per la parola appena creata con un codice standardplanplan che spinge un indirizzo allineato nello stack e restituisce semplicemente (lo stesso indirizzo allineato che le chiamate successive "," (virgola) si riempirebbero con i dati). Nel mio sistema, il codice generato per qualcosa come CREATE NewVar sarebbe simile a questo:

NewVar:    push_data  next_addr
           return
next_addr:

Pertanto, potremmo definire (inizializzato) VARIABILE come:

: VARIABLE CREATE 0 , ;

o, nel codice pseudo-macchina:

VARIABLE:  call       CREATE
           push_data  0
           call       comma
           return

Dire qualcosa come VARIABLE NewVar renderebbe quindi NewVar come una parola che "push_data / return". Il 0 , memorizza quindi uno zero all'indirizzo che NewVar mette nello stack - il "next_addr" mostrato nel frammento di codice. Fare cose come NewVar @ o 42 NewVar ! quindi legge e scrive quella posizione.

Non c'è niente di speciale (almeno nel mio sistema) sulle parole tra CREATE e DOES > o anche le parole dopo DOES > in termini di compilazione. Una parola la cui definizione usa CREATE e DOES > è compilato normalmente, assicurandosi che DOES > è "chiama" nel codice compilato. La cosa speciale che fa > fa come segue: Trova la posizione del codice dell'ultima parola creata e poi sovrascrive l'istruzione "return" con un'istruzione "jump", la cui destinazione è l'indirizzo nello stack di ritorno del DOES > routine. Questo indirizzo viene estratto dallo stack di ritorno ogni volta che DOES > è chiamato, usato per fare istruzioni di "salto". Quando DO > poi cerca di tornare al suo chiamante, in realtà sta tornando a chi ha chiamato la parola che aveva DO > in esso ... non al resto del codice. La mia implementazione di DOES > sembra un po 'come questo:

DOES>:  [find second opcode of latest definition]
        popr      ; like "R>"
        [overwrite opcode with a "jump" to TOS value]
        return

Quindi, quando definiamo VALUE in questo modo:

: VALUE VARIABLE DOES> @ ;

Quello che otteniamo è qualcosa del genere:

VALUE: call   VARIABLE
       call   DOES>
       call   fetch  <-- return address of call to DOES>
       return

Il codice chiamerà la nostra definizione di VARIABLE, data sopra, che a sua volta chiama CREATE per creare la nuova voce. Ma quando chiama DOES & gt ;, DOES > apparirà l'indirizzo di ritorno puntato sopra e regolerà la definizione di NewVar per passare a quella posizione, rendendo così NewVar in modo che spinga "next_addr" nello stack di dati come prima, ma ora salta e chiama fetch. Ciò rende anche l'esecuzione di VALUE tale che termina con la chiamata a DOES & gt ;. Quando DO > restituisce, restituisce al chiamante di VALUE, non al resto della definizione di VALUE ( @ ; ).

Si noti che CREATE non è una parola immediata. La nostra definizione di VARIABLE era CREATE 0 , , ma ciò non crea una parola denominata "0", poiché CREATE viene rilevato durante la definizione di VARIABLE ... viene appena inserito nella definizione. Invece, quando VARIABLE viene effettivamente eseguito, CREATE tenterà di recuperare la parola successiva dall'input e ne creerà una nuova.

Nota anche che DOES > si basa molto sulla parola più recentemente definita. Avrei potuto cercarlo diligentemente per l'opcode "return", ma invece, sapendo come CREATE crea una nuova parola, ha semplicemente usato un offset fisso in quella definizione. Mi sto appoggiando allo spec. che dice "Esiste una condizione ambigua se [la definizione più recente] non è stata definita con CREATE ...". Nel mio sistema, quella "condizione ambigua" è che alcune parole ottengono un opcode "salta" come seconda istruzione.

What does DOES> consume as input?

Consuma il proprio indirizzo di ritorno e utilizza anche una variabile globale che punta alla definizione più recente.

Is there some kind of 'default behaviour' that DOES> overrides?

Sì. È il comportamento predefinito ("push_data / return") creato da CREATE.

    
risposta data 11.05.2018 - 15:58
fonte
3

Rispondere alle tue domande in ordine.

CREATE può allocare un nuovo spazio dati vuoto. Quindi imposta il "campo dati" della nuova voce del dizionario su HERE e la semantica di esecuzione per spingere il valore di quel "campo dati". ' foo >BODY restituirà il "campo dati" di foo assumendo che foo sia stato creato con CREATE . CREATE non restituisce (o consuma) nulla nello stack. Non succede nulla di speciale con le parole tra CREATE e DOES> . Mentre ANS Forth definisce solo ciò che DOES> fa in un contesto di compilazione e consente solo di operare su una definizione creata con CREATE , non richiede che si verifichino all'interno della stessa definizione .

Una conseguenza di quanto sopra è HERE CREATE foo foo = potrebbe o non essere vera, ma CREATE foo foo HERE = dovrebbe sempre essere true.

Rispondere alle tue domande per DOES> : DOES> , in ANS Forth, viene dato un significato solo in un contesto di compilazione. DOES> si comporta in modo simile a ; :NONAME tranne che non restituirà un token di esecuzione e aggiornerà invece l'ultima parola che è stata definita (supponendo che sia stata definita con CREATE ). Consuma (al momento della compilazione) un "colon-sys" che è fondamentalmente una rappresentazione opaca e definita dall'implementazione del codice che viene definito. Termina questa definizione e ne crea una nuova. Citando dallo standard, DOES> "[r] eplace [s] la semantica di esecuzione della definizione più recente [...] con la semantica di esecuzione del nome fornita di seguito." Il "comportamento predefinito" sta spingendo il valore del "campo dati". Detto questo, la semantica data inizia con la spinta del valore del "campo dati", quindi, concettualmente, è più come DOES> che apre la semantica di esecuzione per l'estensione. Dico "si apre per l'estensione" perché è la semantica della compilazione delle parole che seguono DOES> che effettivamente estendono la definizione.

Quindi c'è una cosa principale da capire su Forth: non ci sono strutture di "controllo" estese; la semantica di un programma Forth può essere compresa elaborandola una parola alla volta. Invece, le parole interagiscono tra loro modificando alcuni stati (effettivamente globali). Potrebbe trattarsi della modalità interprete (ad esempio passaggio alla modalità di compilazione), del dizionario, del buffer di input o di uno dei vari stack.

Una semantica di base per la compilazione Forth dovrebbe avere il "colon-sys" come un puntatore alla fine di un codice. La semantica della compilazione di un letterale è di aggiungere un'istruzione push a quel blocco e spostare il puntatore in avanti. La semantica della compilazione di una parola normale è di aggiungere un'istruzione di chiamata al blocco e spostare il puntatore in avanti. ; quindi aggiunge un'istruzione return nel blocco e fa uscire i "colon-sys" dallo stack. Una voce di dizionario è solo un puntatore a un blocco di codice. La semantica di interpretazione di CREATE è di creare una nuova voce di dizionario e aggiungere il codice per spingere HERE e restituire. DOES> cerca semplicemente il blocco di codice della voce del dizionario più recente e punta il puntatore per il blocco di codice, cioè "colon-sys" che spinge, all'istruzione return (in modo che venga sovrascritta). La semantica di compilazione delle seguenti parole aggiornerà quindi il blocco di codice come al solito. Molte delle restrizioni imposte da ANS Forth sono di consentire l'utilizzo di un semplice allocatore di bump (presupponendo codice separato e spazi dati). Ad esempio, la compilazione annidata non è consentita poiché il codice dalla definizione nidificata finirebbe incorporato nel codice della definizione esterna. Allo stesso modo, DOES> opera solo sulla definizione definita più recentemente poiché il suo codice sarà alla fine dello spazio di codice allocato.

    
risposta data 03.01.2017 - 02:44
fonte

Leggi altre domande sui tag