NSA - Non Solo Amiga

CREATIVITA' => Altri Hobby & Passioni => Topic aperto da: TheKaneB - 31 Marzo 2015, 11:21:15

Titolo: Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 31 Marzo 2015, 11:21:15
Per discutere la parte HARDWARE il topic è questo: http://www.nonsoloamiga.com/index.php?topic=3115


Ciao ragazzacci,

butto giù un po' di spunti e idee per la calcolatrice che sto sviluppando nel tempo libero. Discussione aperta a suggerimenti di ogni tipo!
Per facilitare l'implementazione su sistemi embedded ho iniziato lo sviluppo in due moduli principali: MathLang e un client testuale.
MathLang è una libreria statica in C++ puro (variante C++11), mentre Text Client è solo un aggancio lato console per inviare comandi (sotto forma di script) e ricevere risposte (come stringhe).

Una volta che l'engine sarà a buon punto sarà possibile creare un client diverso, magari con una bella grafica, adatto al dispositivo di destinazione.
MathLang è composto a sua volta da due parti principali: ScriptEngine per parsare ed eseguire gli script e MathEngine per l'esecuzione delle operazioni matematiche elementari.

A partire da uno script viene generato un albero ordinato (a parità di parent, i figli sono ordinati da sinistra a destra), dove ogni nodo è uno Statement e ogni statement a sua volta può essere un albero o una foglia.

BlockStatement, CallFunctionStatement, ReturnStatement, LoopStatement e ConditionalStatement sono alberi, mentre DefinitionStatement, AssignmentStatement sono foglie.
ScriptEntity rappresenta le entità più semplici (StringEntity, FunctionEntity, RealEntity, MatrixEntity, BooleanEntity, ListEntity, ecc...) e viene usata come classe base per gli argomenti e i tipi di ritorno delle Function.

Lo Script minimale è costituito da uno Statement, uno ScriptEnvironment, una lista di ScriptEntity (gli argomenti) e almeno un ReturnStatement. Lo script quindi rappresenta il concetto matematico di funzione.

Statement è la classe base che rappresenta i comandi elementari.
ScriptEnvironment è una mappa key-value di stringhe vs ScriptEntity. Contiene le variabili locali e gli argomenti della chiamata a funzione. Non esistono variabili globali.
DefinitionStatement rappresenta la definizione di un simbolo all'interno dello ScriptEnvironment () e la sua inizializzazione
AssignmentStatement rappresenta l'assegnazione di una nuova Entity dello stesso tipo o di un tipo compatibile rispetto a quello con cui è stata definita nell'Environment (se definisco una stringa e poi assegno un intero si incazza).
BlockStatement è un array di Statement.
CallFunctionStatement prende 2 parametri: FunctionEntity da chiamare e ListEntity per gli argomenti (possono essere a loro volta delle FunctionEntity). La FunctionEntity fa riferimento ad un nuovo script
ReturnStatement prende 1 parametro: ScriptEntity (classe base) da restituire al chiamante dello script
LoopStatement prende come parametri 3 oggetti: ScriptEntity contatore, ListEntity con i possibili valori da assegnare al contatore, Statement come corpo del loop
ConditionalStatement prende 3 parametri: BooleanEntity che è la condizione, Statement per il ramo IF e un altro Statement per il ramo ELSE

I vari tipi di ScriptEntity sono:
StringEntity: incapsula il concetto di stringa e le operazioni su di essa
FunctionEntity: incapsula il concetto di funzione, fa riferimento ad un nuovo Script, che possiede quindi un proprio Environment privato.
RealEntity: incapsula i numeri reali (double precision)
IntegerEntity: incapsula i numeri interi a precisione arbitraria (forse userò una lib già pronta per questi algoritmi)
BooleanEntity: incapsula il concetto di boolean
ListEntity: incapsula il concetto di lista eterogenea ordinata (la posizione è preservata)
SetEntity: incapsula il concetto di insieme eterogeneo non ordinato
MatrixEntity: incapsula il concetto di vettore multidimensionale omogeneo (tutte le Entity devono essere dello stesso tipo)

Ora.... alcune delle cose di cui sopra sono già state implementate, altre sono solo nella mia testa. Non ho ancora pensato ad una sintassi precisa per gli script, per cui accetto suggerimenti. Pensavo di basarmi su alcuni spunti presi da TI Basic e Python.
MathEngine espone al pubblico sia un'interfaccia di scripting executeScript(std::string text), sia un'inferfaccia a oggetti executeScript( Script* script ), per cui la prima parte dello sviluppo avverrà tramite la costruzione manuale dell'albero dello script e successivamente, quando avrò definito con precisione la grammatica, metterò mano ad un parser per gli script testuali.

Questo mi consente comunque la possibilità di avere in futuro una GUI furba che consenta di editare gli elementi in modo visuale, senza passare dallo script testuale, semplicemente manipolando gli oggetti dello Script.

Il progetto NON vuole essere un clone di Matlab o Mathematica (sarebbe pura e semplice follia), ma un esperimento ludico molto più modesto incentrato sul calcolo numerico. Mi piacerebbe in futuro aggiungere anche capacità di calcolo simbolico, ma è un argomento molto complesso e non credo di poterci arrivare con le mie capacità :D

Bon... vi aggiornerò con i progressi!
Fatevi sotto con le vostre idee, commenti e spunti :)
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 31 Marzo 2015, 13:06:45
I numeri di riga non hanno senso di esistere nel 2015 :D
Sembra simile al Pascal perchè mi piace la tipizzazione forte (e mi piace il Pascal) :)

Però, ripeto, al momento non ho ancora pensato alla sintassi, solo alla semantica dei tipi.
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 31 Marzo 2015, 13:16:59
Appena metterò mano sulla grammatica e relativo parser vedrò di studiare bene la faccenda. Al momento voglio focalizzarmi sulla semantica e l'engine di calcolo :)
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 31 Marzo 2015, 13:25:24
Se alla Human interface hai un Line Editor non sei ancora nel 2015, ritenta tra qualche secolo ;D ;D ;D

Uhm... non mi piace il fatto che Little legge il testo man mano che lo interpreta (https://code.google.com/p/little-interpreter/source/browse/little.cpp).
Un po' troppo naive come approccio. Preferisco avere un AST di mezzo su cui poter fare delle operazioni prima della reale esecuzione, altrimenti ti leghi troppo alla sintassi ( e ripeto, che voglio offrire l'accesso all'albero rappresentato dalla classe Script, in modo da consentire al client un'interazione più libera possibile, anche tramite strumenti grafici e non solo testo).
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 31 Marzo 2015, 14:05:35
infatti, quella roba puzzava di università lontano kilometri :D
Io ho sviluppato una forte repulsione per i metodi di scrittura del codice che insegnano all'Uni. Roba veramente distaccata dal mondo reale del software engineering.
Scrivere un interprete in quel modo ti porta via molto più tempo che usando un AST e la mantenibilità futura è praticamente nulla.

Un bel sistema a oggetti, con un AST ben definito si scrive molto facilmente e i singoli pezzi sono debuggabili come entità chiuse a se stanti. Puoi applicare facilmente centinaia di Unit Test e implementare roba futura in modo additivo senza spaccare le robe precedenti. Se invece ti appoggi pesantemente alla struttura sintattica, ogni modifica o aggiunta diventa "write only", auguri a debuggare ed espandere :D

Sarà una mia deformazione professionale, ma anche quando scrivo roba per hobby cerco di usare le tecniche più pulite che conosco, perchè so che mi aiuteranno a produrre di più in meno tempo e con meno sbattimenti :)
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 31 Marzo 2015, 20:24:26
grazie mille!

Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 31 Marzo 2015, 20:28:49
@legacy: il C lo evito il più possibile. In c++ è molto semplice implementare alberi e altre strutture complesse, non semplice come Java o C# ovviamente, ma comunque è un buon compromesso tra facilità di sviluppo e uso di risorse su sistemi embedded.
comunque considera che il mio target hardware sarà qualcosa tipo (minimo) ARM9 da 400MHz a salire con minimo 128MB di Ram. Embedded si, ma con un briciolo di dignità e possibilmente con un OS sotto al culo :D
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: Z80Fan - 31 Marzo 2015, 20:48:30
Fatevi sotto con le vostre idee, commenti e spunti :)

Domande:
   - Hai già detto che il linguaggio sarà fortemente tipizzato, ma tipizzazione dinamica o statica?
   - Se uno "Script" è pari a una funzione, come è composto un programma con più funzioni? (un'idea la ho, ma voglio prima sentire la tua)
   - Il linguaggio supporta la ricorsione?
   - I vettori li fai con delle matrici che hanno righe/colonne == 1?
   - Implementerai una serie di passate per ottimizzare la AST prima di passare all'esecuzione vera e propria?
   - L'esecuzione la fai solo visitando la AST oppure generi un bytecode per ogni funzione? La prima è più semplice, ma la seconda può essere più veloce (e occupare meno memoria).

Comunque è un bel progettino; sopratutto è divertente di tanto in tanto iniziare qualche progettino di programmazione "pura" dove puoi decidere tutti i vincoli, senza dover stare dietro a nessuno. Io in questo periodo sono dietro a un semi-wrapper OpenGL (più che wrapper, sono utilità), e la mia produttività e voglia sono ai minimi storici in quanto sono totalmente vincolato da OpenGL e la sua maledetta state-machine interna, e quindi devo studiare attentamente sia quali funzionalità mettere, sia come strutturare l'interfaccia in modo che a OpenGL vada bene la sequenza di comandi che gli genero...

il mio target hardware sarà qualcosa tipo (minimo) ARM9 da 400MHz a salire con minimo 128MB di Ram. Embedded si, ma con un briciolo di dignità e possibilmente con un OS sotto al culo :D

Il mio gusto di embedded preferito! ;D

L'OS te lo fornisco io. 8) (Più che altro, potrebbe essere una scusa per riprendere in mano la seconda versione C++ che avevo iniziato; il blocco principale era il decidere alcuni dettagli della struttura del codice, ma ormai è da tanto che ho deciso e rimane solo da implementare).
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 31 Marzo 2015, 21:13:58
Fatevi sotto con le vostre idee, commenti e spunti :)

Domande:
   - Hai già detto che il linguaggio sarà fortemente tipizzato, ma tipizzazione dinamica o statica?
Dinamica, così risulta più facile per i non programmatori.
Voglio poter fare roba così:
def A = 19.6 <--- Definizione con inizializzazione contestuale (obbligatoria?). Il tipo viene "dedotto" automaticamente dalla RValue
A = "banana" <--- qui si incazza, A è stato già definito come tipo Real

stessa cosa per le liste [1, 2, 3, "44beta", 11.787, altraVariabile, "stringazza"] e per i vettori { 2.3, 11.0, "beta" } <-- Errore: Tipi misti

Considera che NON consentirò la definizione di nuovi tipi. Infatti non è un linguaggio di programmazione vero e proprio ma solo un metodo per definire calcoli matematici in "batch". Quindi farò in modo che sia sempre possibile dedurre il tipo di una espressione adottando opportune convenzioni per ciascun tipo di literal.

Avevo pensato a fare così:
integer: 1234
real: 1234.0
string: "1234"
boolean: true, false ( sono keyword riservate )
lista: [1,2,3,4]
matrice: {{1,2}{3,4}}
funzione: def f = function(param1 : String, param2 : List, param3 : Real) : Boolean

Devo ancora capire se tipizzare fortemente il tipo funzione (quindi prendendo la lista degli argomenti e il tipo di ritorno come parte integrante del tipo) oppure se lasciare le funzioni weakly typed stile Javascript.
Forse meglio non complicarsi troppo la vita ed evitare la possibilità di passare in giro puntatori a funzione?


Citazione
   - Se uno "Script" è pari a una funzione, come è composto un programma con più funzioni? (un'idea la ho, ma voglio prima sentire la tua)

La mia idea è che il tuo ambiente di lavoro resta sempre la Home della calcolatrice e le funzioni che sviluppi siano di appoggio al tuo lavoro. Perciò non farò distinzione tra "programma" e "funzione". Per me sono tutte funzioni, e ne puoi definire quante ne vuoi nello stesso file.
Quello che cambia sarà solo lo scope di visibilità. Una funzione può chiamarne altre nello stesso file e può chiamare quelle importate con la keywork "import". Dal punto di vista del MathEngine, nel momento in cui carichi uno script, questo si comporta in modo simile a qualsiasi linguaggio di scripting:
Codice: [Seleziona]
-- il file si chiama Banana.script
def f = function(....) : Real
...
...
...
end

def A = f("banana") <--- questo viene eseguito
Quando butto questo file in pasto al MathEngine, lui assume che ci sia una function senza nome che contiene una serie di statement, tra cui un def function e una function call. La function senza nome non prende parametri e non dà output.

Comunque questa è solo un'idea fumosa. La migliorerò appena mi applicherò a pensare la grammatica del linguaggio. Ci sono alcune cose che non mi convincono molto, per cui sono aperto a suggerimenti di ogni tipo :)
Citazione
   - Il linguaggio supporta la ricorsione?
Yep

Citazione
   - I vettori li fai con delle matrici che hanno righe/colonne == 1?
Yep. Non molto efficiente, ma molto più facile da gestire lato codice. Eventuali funzioni potranno imporre delle restrizioni sulle dimensioni (ad esempio det(A) dovrà imporre che A abbia 2 dimensioni, e che abbiano entrambe la stessa quantità di elementi)
Citazione
   - Implementerai una serie di passate per ottimizzare la AST prima di passare all'esecuzione vera e propria?
Non so quanto sia complicato farlo. Penso di no, anche perchè non ne sono capace.
Citazione
   - L'esecuzione la fai solo visitando la AST oppure generi un bytecode per ogni funzione? La prima è più semplice, ma la seconda può essere più veloce (e occupare meno memoria).
Quando lavoravo nei VG mi è capitato di sviluppare un sistema di scripting a bytecode. Non è difficile, potrei farlo, ma magari lo lascio come ottimizzazione successiva.

Citazione
Comunque è un bel progettino; sopratutto è divertente di tanto in tanto iniziare qualche progettino di programmazione "pura" dove puoi decidere tutti i vincoli, senza dover stare dietro a nessuno. Io in questo periodo sono dietro a un semi-wrapper OpenGL (più che wrapper, sono utilità), e la mia produttività e voglia sono ai minimi storici in quanto sono totalmente vincolato da OpenGL e la sua maledetta state-machine interna, e quindi devo studiare attentamente sia quali funzionalità mettere, sia come strutturare l'interfaccia in modo che a OpenGL vada bene la sequenza di comandi che gli genero...

il mio target hardware sarà qualcosa tipo (minimo) ARM9 da 400MHz a salire con minimo 128MB di Ram. Embedded si, ma con un briciolo di dignità e possibilmente con un OS sotto al culo :D

Il mio gusto di embedded preferito! ;D

L'OS te lo fornisco io. 8) (Più che altro, potrebbe essere una scusa per riprendere in mano la seconda versione C++ che avevo iniziato; il blocco principale era il decidere alcuni dettagli della struttura del codice, ma ormai è da tanto che ho deciso e rimane solo da implementare).

eheh ok!
La parte Engine sarà certamente multiplatform (uso C++ puro, nessun aggancio a funzioni di sistema, solo un po' di cout per il logging degli errori), per cui mi toccherà fare la parte client ad hoc per il tuo OS. Magari faccio solo un sistema di input testuale interattivo (sempre che tu non decida di supportare roba ... tipo Qt ahah :D ).
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: Z80Fan - 01 Aprile 2015, 01:24:21
Dinamica, così risulta più facile per i non programmatori.

Non so: parlo per sensazione ovviamente, ma forse avere il tipo di dato specificato esplicitamente può essere più semplice da capire per un novizio rispetto a regole di derivazione implicita.
Poi, certe volte è comodo poter specificare il tipo di dato senza ambiguità: se ad esempio voglio una lista di numeri reali, e la voglio caricare con 1, 2, 3, 4, dovrei scrivere:
def l = [1.0, 2.0, 3.0, 4.0]
Però è un po' scomodo dover sempre mettere .0 (sopratutto se magari lo metti in una calcolatrice con tasti piccoli o tastiera limitata), vorrei poter fare
def l = [1, 2, 3, 4]
però diventa una lista di interi, e se provo a fare operazioni con altre cose float darebbe errore (a meno che non consideri cast impliciti, però se vuoi interi a precisione arbitraria un cast può perdere informazione e potrebbe non essere quello che vuoi).
Oppure più in generale stai facendo un programmino veloce sulla tua calcolatrice, e vuoi provare diversi numeri nella lista: anche qui dover specificare sempre .0 nel caso il numero sia anche intero diventa fastidioso, sopratutto se come nel caso sopra la lista viene dedotta a lista di interi, e ti capita di passare la variabile a una funzione che si aspetta una lista di interi.

Puoi lasciare la deduzione automatica del tipo (che aiuta sopratutto nel fatto che obbliga a mettere un'inizializzazione; le variabili non inizializzate diventano errori di sintassi), e aggiungere un modo per specificare il tipo di dato prescelto, tipo
def l : ListReal = [1, 2, 3, 4]
def l = ListReal([1, 2, 3, 4])    // questo è più stile "cast"

e il compilatore sa che quei literal li deve interpretare come reali e non interi; ovviamente nel caso ci sia qualcosa tipo [1, 2, x, 4] dove x è una variabile di un tipo diverso da real, si avrà l'errore di conversione come al solito.


Da questo esempio viene fuori anche un altro "problema": come differenzi le Liste in base al tipo di dato contenuto? Nella specifica dei parametri delle funzioni devi poter indicare che vuoi una lista solo di un certo tipo, perchè ad esempio non avrebbe senso passare una lista di stringhe a una funzione che calcola la media, e questo errore dovrebbe essere catturato a compile time.
Dovresti o introdurre vari tipi di dato ListInteger, ListReal etc, oppure trovare una qualche sintassi List(Boolean), List.String.
È possibile fare liste di liste? Devi decidere la sintassi anche per quelle.

Devo ancora capire se tipizzare fortemente il tipo funzione (quindi prendendo la lista degli argomenti e il tipo di ritorno come parte integrante del tipo) oppure se lasciare le funzioni weakly typed stile Javascript.
Forse meglio non complicarsi troppo la vita ed evitare la possibilità di passare in giro puntatori a funzione?

Io credo di si, meglio che ogni funzione sia tipizzata secondo i suoi parametri; puoi sempre fare che, se una funzione funziona bene con qualsiasi input, puoi fare a meno di indicare il tipo di dato per quel parametro e il compilatore accetterà qualsiasi input.
I modi in cui puoi trattarlo lato interprete sono molteplici: o segui la strada dei template C++, ovvero rigeneri il corpo della funzione quando ne incontri un uso, il che ti permette di ottimizzare più aggressivamente il codice ma ti occupa più spazio nell'AST, oppure puoi lasciare il tipo "indefinito" e eseguire i controlli a runtime (e questo implica che non potrai a compile time trovare tutti i possibili errori di tipo di dato).

Secondo me è troppo lungo dover tener conto di queste possibilità, quindi io lascerei che ogni funzione è fortemente tipizzata e ogni parametro deve avere tipo definito.

Il passaggio di funzioni come parametro non dovrebbe essere troppo faticoso, ci sono però due problemi (o meglio, decisioni) da risolvere:
prima di tutto, visto che le funzioni sono fortemente tipizzate, devi decidere una sintassi per specificare un parametro di tipo funzione; una proposta può essere qualcosa tipo:
def f = function(param1 : String, param2 : List, param3 : function(Integer, Real) : String) : Boolean
o una funzione che ritorna funzioni che ritornano matrici:
def f2 = function(param1 : String) : function(List, Integer, String) : Matrix
Questa sintassi dovrebbe essere totalmente disambigua (mi sembra), ma potrebbe non essere totalmente leggibile.

Un'altra strada potebbe essere di indicare solo che si vuole che quel parametro sia una funzione, e lasciare a runtime il compito di verificare che la chiamata (eseguita tramite il riferimento) venga eseguita con il giusto numero e tipo di parametri della funzione che viene riferita dalla variabile. Questo causa un problema simile a quello sopra dove non si può catturare ogni problema di tipo a compile time.
Per facilitare la scrittura di grandi programmi che usano molte callback si può introdurre un costrutto di "alias" in stile:
alias callback = function(Integer, Real) : String
(essendo un "alias" e non un "def", questa riga non fa partire il blocco della funzione ma lo statement termina alla fine della riga)
Sarebbe circa come un "typedef", non so se rientra nella tua idea di "creare nuovi tipi di dato".

Secondo, visto anche che il linguaggio dovrebbe essere usato per problemi matematici, e per come è costruito, vorrai sicuramente avere chiusure:
def gen = function(x : Integer) : function(Integer) : Integer
    return function(y : Integer) : Integer
        return x + y
    end
end

def f5 = gen(5)
def z = f5(7)     // z = 12


Qui devi assicurarti che ogni funzione abbia accesso allo ScriptEnvironment della funzione padre, e se ne usa un valore, devi copiarlo (perchè potrebbe variare in futuro in altri punti del programma ma non si vuole che la chiusura venga influenzata).

Citazione
   - Il linguaggio supporta la ricorsione?
Yep

Per questo punto preciso, ho due domande/dubbi/osservazioni: la prima, molto semplice, è che una funzione ricorsiva crea un ciclo nell'AST, che quindi non è più un albero ma un grafo.
La seconda riguarda la sintassi, e in particolare gli scope:

def fattoriale = function(i : Integer) : Integer
    if i <= 1 then
        return 1
    else
        return i * fattoriale(i-1)
    end
end


In base a come esegui il parse del codice, e di come inserisci i valori nella tabella dei simboli, quando il compilatore arriva a leggere la riga con la chiamata ricorsiva, il simbolo "fattoriale" non è ancora definito (aspetti il parsing di function prima di definirlo), oppure è definito ma contiene un valore "vuoto" (se per esempio lo hai già inserito nel ScriptEnvironment).
Se vogliamo rimanere coerenti con il discorso delle chiusure di prima, quando una funzione incontra un simbolo definito nel padre, dovrebbe copiarlo, però in questo caso il simbolo o non esiste o contiene un valore "vuoto", quindi non è possibile in un solo passo abbinare la chiamata ricorsiva.

Mi sono chiesto come fa Lua a risolvere questo problema, visto che la sua sintassi e comportamento è estremamente simile a questo che stiamo inventando: il trucco è che le variabili in Lua sono di default globali, quindi nel caso simile al nostro, "fattoriale" nel return semplicemente sarebbe un riferimento al simbolo "fattoriale" che è definito (perchè l'abbiamo parsato), e quando la funzione sarà effettivamente eseguita avrà il giusto valore (perchè lo statement di creazione del simbolo "fattoriale" sarà già concluso).

Nel nostro linguaggio, visto che assumiamo che i simboli siano di default locali, questo sistema crolla, e crolla pure in Lua se dichiariamo la funzione come locale, simulando quindi il comportamento di default del nostro linguaggio:
local fact = function (n)
    if n == 0 then return 1
    else return n*fact(n-1)   -- buggy
    end
end


When Lua compiles the call fact(n-1), in the function body, the local fact is not yet defined. Therefore, that expression calls a global fact, not the local one.
(http://www.lua.org/pil/6.2.html)
La soluzione, in Lua, è dichiarare prima il simbolo come locale (quindi creandolo), e poi assegnarci la funzione, oppure usare la sintassi alternativa che è syntactic sugar per la stessa tecnica.

A meno di non mettere eccezioni nel parser, o cambiare il comportamento dello scope come in Lua, io direi di modificare un po' la sintassi per la creazione di funzioni:
function nome(..params..) : ritorno
    ...
end


Tutte le altre sintassi che abbiamo visto sopra rimangono valide (alla fine non sono altro che funzioni anonime assegnate poi a una variabile), e questa stessa sintassi è come un synctatic sugar delle precedenti, con la differenza che (circa come in Lua), il parser che analizza la funzione sa più direttamente che "nome" sarà il nome della funzione, quindi se si trova nella condizione di ricorsione:
function fattoriale(i : Integer) : Integer
    if i <= 1 then return 1
    else return i * fattoriale(i-1)
    end
end


Sa che per risolvere il "fattoriale" del return può usare direttamente la funzione stessa che sta parsando (semprechè la lista di parametri sia la stessa, altrimenti deve guardare in un contesto più ampio fuori dalla funzione stessa).

Yep. Non molto efficiente, ma molto più facile da gestire lato codice. Eventuali funzioni potranno imporre delle restrizioni sulle dimensioni (ad esempio det(A) dovrà imporre che A abbia 2 dimensioni, e che abbiano entrambe la stessa quantità di elementi)

Questo mi fa venire in mente un argomento parzialmente scorrelato: come gestisci gli errori di runtine? Immagino tu non voglia andare a programmare un sistema di eccezioni; semplicemente uccidi l'interprete quando c'è qualcosa che non va?

Sempre su questo tema, prevedi che le funzioni possano tornare più valori in uscita, ad esempio per tornare il risultato e un valore booleano di errore?

Citazione
   - Implementerai una serie di passate per ottimizzare la AST prima di passare all'esecuzione vera e propria?
Non so quanto sia complicato farlo. Penso di no, anche perchè non ne sono capace.

Alcune ottimizzazioni sono semplici : puoi fare una variante molto semplice di costant folding (http://en.wikipedia.org/wiki/Constant_folding) semplicemente "elaborando" l'AST ricorsivamente e sostituendo tutti i blocchi che tornano un risultato (che quindi non ritornano un "can't compute" come errore) con il loro valore elaborato; in qualcosa tipo 2*3*x, provando a elaborare l'AST si troverà un blocco * con operandi 2 e 3, che quindi è completamente calcolabile e può essere sostituito con la costante 6. Successivamente ci sarà un blocco * con input 6 e x che non sarà elaborabile (a meno che x non abbia un valore costante).
Ovviamente questo è il modo più banale nel vedere il constant folding: in molti casi (in base a come costruisci l'AST e alla sofisticatezza della visita), situazioni come 2*x + 3 + 4 potrebbero non essere elaborate (perchè prima si trova il blocco * che non è elaborabile, quindi +3 non è elaborabile, quindi +4 non è elaborabile), però potrà essere interessante in futuro provare a trovare qualche algoritmino.

La parte Engine sarà certamente multiplatform (uso C++ puro, nessun aggancio a funzioni di sistema, solo un po' di cout per il logging degli errori), per cui mi toccherà fare la parte client ad hoc per il tuo OS. Magari faccio solo un sistema di input testuale interattivo (sempre che tu non decida di supportare roba ... tipo Qt ahah :D ).

Qt decisamente no... ho solo una vita. :D E comunque è un problema di software utente, non di kernel. :P
Bisognerà vedere un po' su che piattaforma vorrai metterlo; su ARM già volevo far qualcosa (sulla Udoo, sulla quale ho già trovato il modo per far partire un mio binario personalizzato da U-Boot), quindi qualcosa dovrei riuscire a fornirti. Se pensavi qualcosa sulle linee dell'Arduino Due, meglio ancora, perchè la Udoo ha proprio il SAM3X on board.
Al minimo ti servirà sicuramente un gestore della memoria (un minimo di allocatore di pagine, per cose più granulari si trovano tanti allocatori in rete), giusto un minimo di scheduling ('che altrimenti che ci sta a fare un kernel? :P), e ovviamente un I/O testuale; tutte cose non assurde da fare, più che altro in ambito ARM bisogna conoscere la mappa di memoria del determinato SoC (ognuno è diverso), e alcuni dettagli delle periferiche.
In teoria con il nuovo sistema che ho imbastito nella riscrittura, gestire differenze tra piattaforme che usano la stessa CPU (nel senso di "ISA", come ARM o x86) è molto semplice, perchè il processore è separato dalla piattaforma, che è separata dai device driver (la "platform" è quella che detta legge, indicando al suo interno che processore vuole e quali device driver usare; un esempio di platform è "ibmpc" che usa cpu "x86" e ha vari device driver per il PIC, timer etc.).
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 01 Aprile 2015, 14:44:38
@Z80Fan e @dsar:

grazie mille per i suggerimenti.
Come dicevo, questo qui non è il mio campo e non voglio farla fuori dal vasino :D
Ho implementato in passato la parte "esecutiva" di una virtual machine, quindi ho molto ben chiaro il funzionamento lato esecuzione di un AST. Quello che per me è totalmente nuovo è l'aspetto di language design e le problematiche che ciò si porta dietro relativamente al parsing.

Mi studierò volentieri tutto il materiale che mi gettate addosso e tornerò alla carica quando avrò capito meglio cosa voglio fare e come farlo.
Per quanto riguarda la gestione di funzioni e tipi, il mio obiettivo primario è quello di fare una calcolatrice, quindi un'applicativo dotato di due principali caratteristiche:
- Modalità interattiva come principale fonte di input
- Avere lato linguaggio TUTTI e SOLI gli strumenti utili a scopo matematico (numerico per il momento)

Quindi sarà importantissimo gestire liste, insiemi e matrici lato linguaggio, ma dev'essere anche intuitivo e veloce da usare in modalità interattiva.
Ad esempio, su obiezione di Z80Fan, penso sia ragionevole applicare conversioni automatiche Integer -> Double con perdita di precisione usando la stessa regola che usano le calcolatrici TI e HP : in una espressione, gli argomenti Double infettano gli altri operandi, castando SEMPRE verso double.

Quindi se faccio def mioArray = {1, 2, 3, 4.0} allora tutto l'array verrà convertito in Double in maniera silenziosa.
Analogamente se faccio def mioArray2 = {7, 8, 9, 10} sarà un array di soli interi, ma la somma mioArray + mioArray2 sarà un array di Double (il Double è infettivo).

Mi aspetto che l'utente sia abituato ad Hp e TI da questo punto di vista, inoltre, mi aspetto che l'utilizzo degli interi a precisione arbitraria sia ragionevolmente limitato a sole operazioni intere (principalmente matematica combinatoria), mentre se uso un misto di Integer e Double mi aspetto che l'utente sia interessato all'applicazione di metodi numerici approssimati.

Come vi sembra l'approccio? Può funzionare?
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 01 Aprile 2015, 14:47:52
ma il target cosa e' ? la RPI ?

Si, pensavo una roba tipo la RPI, ma non necessariamente quella macchina specifica. Comunque sì, vorrei avere a disposizione una macchina relativamente potente sotto le chiappe come requisito minimo. In questo momento il mio "gold standard" è la HP Prime. Vorrei poter fare qualcosa che sia non dico nella sua categoria (è troppo avanzata per le mie capacità), ma che sia quantomeno ispirata a questa macchina (ARM9 400Mhz, Ram mi pare 64 o forse 128MB, schermo a colori ad "alta" risoluzione, possibilità di avere un OS non banale sotto i piedi).
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 01 Aprile 2015, 15:00:52
l'AST rappresenta il codice già sbrogliato e pronto per la successiva fase. Nel mio caso la fase successiva era l'interpretazione ed esecuzione del codice direttamente sull'AST.

In parole povere, se piazzi un metodo virtuale execute() sulla classe base del Nodo dell'albero AST, e specializzi l'implementazione in base al tipo di nodo, per eseguire tutto l'albero ti basta alla fine fare un semplice AST * a = getAST(); a->execute(environment);

Ho lavorato anche su una VM basata su bytecode. In quel caso l'AST non me lo passavano, avevo già gli opcode finali e la VM era molto più complessa, perchè si comportava come una vera macchina, con i suoi cicli di fetch, execute e memory.
Era molto più efficiente ovviamente, ma la prima è più facile da implementare e la VM, di fatto, non esiste fisicamente ma è astratta dalla capacità stessa dell'AST di eseguire se stesso.
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 01 Aprile 2015, 15:07:49
sisi, grandissimi quelli di Wolfram :)
RPI in UK ha preso il posto del vecchio BBC con 6502. E' diventato il punto di riferimento per l'educazione informatica nelle scuole, pertanto stanno sfornando una nuova generazione a colpi di Python e Mathematica, evviva :D
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: Z80Fan - 01 Aprile 2015, 15:38:01
Mi aspetto che l'utente sia abituato ad Hp e TI da questo punto di vista, inoltre, mi aspetto che l'utilizzo degli interi a precisione arbitraria sia ragionevolmente limitato a sole operazioni intere (principalmente matematica combinatoria), mentre se uso un misto di Integer e Double mi aspetto che l'utente sia interessato all'applicazione di metodi numerici approssimati.

Come vi sembra l'approccio? Può funzionare?

L'unico problema che vedo ora su due piedi è che se per sbaglio da qualche parte dell'elaborazione viene iniettato un Double, tutti i calcoli successivi vengono "infettati" e ridotti al double, cosa che magari l'utente che voleva un calcolo strettamente intero non voleva fare, però per risolverlo bisognerebbe stringere ancora di più la tipizzazione, con tutta la complessità di programmazione che non va bene per il target che si è stabilito...

Si, pensavo una roba tipo la RPI, ma non necessariamente quella macchina specifica. Comunque sì, vorrei avere a disposizione una macchina relativamente potente sotto le chiappe come requisito minimo. In questo momento il mio "gold standard" è la HP Prime. Vorrei poter fare qualcosa che sia non dico nella sua categoria (è troppo avanzata per le mie capacità), ma che sia quantomeno ispirata a questa macchina (ARM9 400Mhz, Ram mi pare 64 o forse 128MB, schermo a colori ad "alta" risoluzione, possibilità di avere un OS non banale sotto i piedi).

Magari la creazione di una calcolatrice custom potrebbe essere un successivo Non-Solo-Project. :P
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 01 Aprile 2015, 15:48:47
@Z80Fan: sulla HP ogni tanto mi capita. In quel caso premo il tasto SU, modifico l'expression e rifaccio Enter. In ottica di uso interattivo mi sembra accettabile, ma vorrei sentire più opinioni per capire se è solo una mia impressione o se è una roba largamente accettata.

Per la parte hardware sono dolori, con quelle frequenze in gioco io non saprei da dove cominciare. Mi limiterò a fare un case custom per una qualche devboard famosa. Poi se qualcuno esperto vuole tirare fuori una board custom ben venga :D
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: Nonefonow - 01 Aprile 2015, 23:42:53
Magari puoi prendere uno spunto da Norbert.
 
http://members.aon.at/nkehrer/a800_c64_hp_emu.html (http://members.aon.at/nkehrer/a800_c64_hp_emu.html)
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 02 Aprile 2015, 06:51:08
Magari puoi prendere uno spunto da Norbert.
 
http://members.aon.at/nkehrer/a800_c64_hp_emu.html (http://members.aon.at/nkehrer/a800_c64_hp_emu.html)

Ma ho già alcune HP, a che mi serve un emulatore per c64? :D
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 02 Aprile 2015, 13:05:12
Onestamente non ci vedo tutta questa difficoltà. Devi usare meno il C e studiare un po' di roba object-oriented ;)
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 02 Aprile 2015, 15:30:44
Ok, ma il Dragon Book è orientato a studenti universitari. Io ho superato quella fase da un bel pezzo zio :D
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 08 Aprile 2015, 08:16:03
@dsar:

sto continuando lo studio del libro che mi hai passato ma ho qualche appunto da fare:
1- il libro fa riferimento ad un C++ molto vecchio, per cui le tecniche usate non sono più applicabili. Sono da considerare solo a livello concettuale
2- L'autore schiaffa pesantemente roba C mista a roba C++. Non so se è per colpa della "gioventù" di quella versione di C++, ma comunque è da rivedere complemtamente (usa bitfield invece di boolean vectors, usa char * invece di std::string, fa tipi opachi con typedef di puntatori, ecc...).
3- La struttura che propone non è molto object oriented, fa pesante uso di singleton e strutture globali. Questo non è un limite del C++ ma proprio della forma mentis dell'autore.

Per cui, continuo a leggere il libro giusto per vedere se posso estrarre qualche buona idea, ma a livello strettamente tecnico non è assolutamente un esempio da seguire :)
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 08 Aprile 2015, 11:40:25
Io invece spingo molto sulla completa implementazione OOP perchè semplifica molto la manutenzione e la struttura del codice risulta enormemente più leggibile e facilmente estendibile.
Buttare roba in campi globali complica il data path e chi deve modificare / estendere il progetto sputa sangue a ettolitri :)

Non è solo una questione di "avere senso" o meno, ma di applicazione di un paradigma che risulta molto comodo in generale, anche quando il progetto che stai sviluppando non ne sfrutta efficacemente tutte le caratteristiche.
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 08 Aprile 2015, 15:32:20
Per me, e per il C++, un container, uno stream e un modulo sono comunque degli oggetti (in senso allargato).
Dove bisogna interfacciarsi con codice puramente procedurale, interpongo una classe wrapper che esponga un'interfaccia comune ad altre sezioni procedurali.

Esempio, sta roba che ho tirato fuori oggi per l'integrazione di funzioni built-in:

FunctionEntity è un nodo dell'AST che astrae il concetto di "funzione". E' una classe virtuale pura, cioè non può essere istanziata.
Codice: [Seleziona]
class FunctionEntity : public ScriptEntity
{
public:
    FunctionEntity();
    virtual ~FunctionEntity();

    virtual bool canCastTo(ScriptEntity * other) const;
    virtual std::string getTypeName() const;
    virtual std::string toString() const;

    virtual void setArgs(std::vector<ScriptEntity *> args);

    // The return value is inside the special scratch register defined in env
    virtual Result* call(ExecutionEnvironment * env, std::vector<ScriptEntity *> args) = 0;

protected:

    // This is the ordered set of input arguments the function expects.
    // Type checking is done at runtime
    std::vector<ScriptEntity *> args;

};

BuiltinFunctionEntity è una classe concreta che interfaccia la struttura OOP del mio AST con una chiamata ad un pezzo di codice puramente procedurale:
Codice: [Seleziona]
typedef Result * (*BuiltinFunction)(ExecutionEnvironment *, std::vector<ScriptEntity *>);

class BuiltinFunctionEntity : public FunctionEntity
{
public:
    BuiltinFunctionEntity(BuiltinFunction hook);
    virtual ~BuiltinFunctionEntity();

    virtual Result* call(ExecutionEnvironment * env, std::vector<ScriptEntity *> args);

private:
    BuiltinFunction hook;
};

Questo è parte del codice che inizializza l'execution environment, istruendolo della presenza di diverse built-in functions. In questo caso la classe BuiltFunctions funziona come mero modulo di procedure, ma è comunque un oggetto dal punto di vista strettamente tecnico (mi aiuta a mantenere più facilmente il codice).
Codice: [Seleziona]
void BuiltinFunctions::registerFunctions(MathEngine *engine)
{
    ExecutionEnvironment * env = engine->getEnvironment();
    env->getGlobalEnvironment()->defineEntity("print", new BuiltinFunctionEntity(&print));
    env->getGlobalEnvironment()->defineEntity("concat", new BuiltinFunctionEntity(&concat));
}

// Prints the given arguments as strings
Result * BuiltinFunctions::print(ExecutionEnvironment *env, std::vector<ScriptEntity *> args)
{
    std::string result = "";
    for (const auto& arg : args)
    {
        result += arg->toString();
    }

    return new StringResult(result);
}

Result * BuiltinFunctions::concat(ExecutionEnvironment * env, std::vector<ScriptEntity*> args)
{
    std::string result = "";
    for (const auto& arg : args)
    {
        result += arg->toString();
    }

    env->setReturnValue(new StringEntity(result));
    return new OkResult();
}

print e concat sono due "static" function. Equivalgono in tutto e per tutto a funzioni C pure (non possono accedere a "this"), ma sono tecnicamente isolate nel loro modulo. Non è OOP puro, ma offre un aiuto pratico nell'organizzazione e nella manutenzione del codice.

Quest'ultima, invece, è la classe che incorpora il concetto di chiamata a funzione, anche questa diventerà un nodo dell'AST e precisamente sarà il nodo padre di FunctionEntity. Il mio ragionamento è che lo statement di chiamata a funzione, è un concetto composto dall'azione della chiamata e dagli oggetti "funzione da chiamare" e "lista di argomenti".
Quindi nel mio AST avrò: FunctionCallStatement -> FunctionEntity, vector<ScriptEntity>

(vector è una semplice lista dinamica).

Codice: [Seleziona]

class FunctionCallStatement : public Statement
{
public:
    FunctionCallStatement(FunctionEntity * function, std::vector<ScriptEntity *> args);
    virtual ~FunctionCallStatement();

    virtual Result* execute(ExecutionEnvironment *env);

private:
    FunctionEntity * function;
    std::vector<ScriptEntity *> args;
};

Nella mia idea di AST auto-eseguibile, i valori di ritorno di ogni statement ( classe astratta Result ) servono come codici di controllo e come output verso l'utente. Invece i valori di ritorno delle function call viaggiano dentro l'execution environment (una classe che ingloba la symbol table, l'ultimo return value e altra roba che inserirò più avanti per gestire lo scope dei simboli).

Come vi sembra l'approccio?
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 08 Aprile 2015, 15:52:57
Il mio ragionamento è che ogni Statement, nella sua operazione di "execution" ritorna un valore che ne indica lo stato. Solitamente è un OkResult, oppure un ErrorResult. Se voglio un output verso l'utente, ritorno uno StringResult.

Ogni statement ha il suo Result, compreso un Definition Statement ( def A = 15 ritorna OkResult ).

Quando invece metterò mano al concetto di Expression (al momento non c'è ancora) e quindi anche di Assignment, mi servirà far circolare i valori di ritorno "dentro" lo script. Questo meccanismo è assolto dall'Execution Environment, che è un contenitore in cui trovi la Symbol table, lo stack delle chiamate a funzione, il meccanismo di gestione dello scope e i valori di ritorno delle funzioni.

Nella mia testa, questo execution environment dovrebbe comportarsi da Virtual Machine, con i suoi registri, il suo heap e lo stack, ma astratti da costrutti di più alto livello.

Momentaneamente ho deciso dedicare un field di ExecutionEnvironment come "lastReturnValue", che sarebbe l'equivalente di R0 su Arm, o del registro EAX su Intel per capirci (assumento una calling convention che metta sul primo registro i valori di ritorno delle funzioni).

Onestamente non so ancora se sia una buona idea o meno, o se abbia senso trattare i valori di ritorno in modo diverso, ma è una roba che ho messo in piedi oggi in un paio d'ore e quindi è ancora in piena fase di sviluppo / modifiche / miglioramenti.
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: Z80Fan - 08 Aprile 2015, 17:08:30
Come vi sembra l'approccio?

Non so, non mi da proprio un buon feeling, anche se so che non si può fare tanto diversamente.

Queste sono un po' di cose che mi girano in testa:

- Se cambi FunctionEntity (o ScriptEntity) per avere un operator() invece di (o insieme a) call(), puoi cambiare il prototipo di defineEntity in modo da prendere un std::function (http://en.cppreference.com/w/cpp/utility/functional/function); in questo modo puoi buttare via tutto il BuiltinFunctionEntity e passare direttamente il print e il concat, e molte altre cose (lambda expression etc).

- Ci sono tanti puntatori liberi in giro, gestione manuale della memoria. Un veloce cambiamento che potresti fare è usare un unique_ptr invece che uno "normale" per il ritorno di Result. In realtà sto cercando di pensare un modo più generale per rimuovere la necesità di gestire il polimorfismo tramite puntatori (quello classico, insomma), e fare qualcosa mediante delle classi o dei wrapper per fare una specie di type erasure dei poveri, dove quindi invece di dover passare in giro puntatori e riferimenti, si può fare tutto tramite simpatiche classi che supportano value semantics (e senza dover usare eccessivamente i template, in modo da non avere code bloat). Al momento devo scappare quindi non riesco a spiegarti meglio, e sopratutto devo fare un paio di esperimenti per vedere se veramente si può fare; se riesci, mi servirebbe un po' una listina generale degli oggetti che vuoi creare e la relazione tra di loro.
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 08 Aprile 2015, 21:43:11
Il progetto è ancora molto fluido, per cui non ho fissato nulla sulla pietra.
Devo studiarmi meglio std::function, che per me è roba totalmente nuova :)
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: Z80Fan - 09 Aprile 2015, 00:29:51
Il progetto è ancora molto fluido, per cui non ho fissato nulla sulla pietra.
Devo studiarmi meglio std::function, che per me è roba totalmente nuova :)

È una bella figatina: praticamente, ogni volta che useresti un puntatore a funzione, usa una std::function e vai sul sicuro. :D

Riguardo quello che dicevo prima: stavo provando a implementare le idee che avevo in mente, solo che così su due piedi stavo re-implementando un puntatore intelligente; io direi che potresti passare a usare degli std::shared_ptr (http://en.cppreference.com/w/cpp/memory/shared_ptr), che praticamente sono dei puntatori intelligenti con reference counting (quindi ti liberano l'oggetto automaticamente quando l'ultimo riferimento muore); per evitare di scrivere sempre il tipo di dato puoi usare un alias, così:

using BasePtr = std::shared_ptr<LaBase>;

La cosa buona è che funzionano come puntatori normali, quindi se passi shared_ptr<Derivata> a una funzione che accetta shared_ptr<Base>, la conversione è automatica (e controllata a compile time)*.

Per creare un oggetto direttamente in shared_ptr, puoi usare la funzione std::make_shared (http://en.cppreference.com/w/cpp/memory/shared_ptr/make_shared)<tipo>(param...), che, a differenza di fare std::shared_ptr<tipo>(new tipo(param...)), ti permette di velocizzare leggermente la creazione (make_shared alloca e inizializza l'oggetto direttamente insieme al suo blocchetto di informazioni per il reference count, così riduci le allocazioni di memoria e i due elementi sono vicini in cache tra di loro).
Senza dover sempre scrivere make_shared ovunque, puoi fare un piccolo "alias" per la funzione in questo modo:

const auto& makeDerivata = std::make_shared<LaDerivata>;
constexpr auto makeDerivata = std::make_shared<LaDerivata>;    // Clang 3.5 e GCC 4.8 lo prendono, VS2013 no perchè non ha il constexpr (che cmq è una feature di C++11).


Altrimenti potresti provvedere in ogni classe a dei metodi statici in stile factory per costruire l'oggetto e tornare il relativo shared_ptr.


* C'è solo un dettaglio prestazionale a usare questa tecnica in modo massiccio, causato dal fatto che a ogni copia dello shared_ptr bisogna aggiornare il reference count; per alleviarlo (ovviamente prima si fa profiling! Però considerando che dovrai quasi esclusivamente maneggiare puntatori, potrebbe essere un dettaglio significante), si può passare l'intero shared_ptr per riferimento, ma in tal caso viene a mancare la conversione automatica, i.e. non puoi convertire shared_ptr<Derivata> in shared_ptr<Base>&. Per farlo funzionare, devi usare const shared_ptr<Base>& come tipo del parametro.
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 09 Aprile 2015, 06:54:04
Ok mi studio anche questo e vediamo cosa ne viene fuori :D
È evidente che le mie conoscenze di c++ si sono fermate a Visual Studio 2003.net e gcc 3.x :p
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 09 Aprile 2015, 07:45:30
Dato che è un progetto personale e non hai vincoli decisionali da parte di terzi, hai pensato ad un linguaggio migliore, e con complexity ridotto, del C++?

Si, ci ho pensato, volevo iniziarlo in Python, ma poi ho pensato che farlo girare su una piattaforma "bare metal" sarebbe stato più facile con il C++. Più che altro è la disponibilità di tools di debug avanzati che mi ha spinto a usare C++.
Altro linguaggio che conosco molto bene è il Java, ma è troppo pesante per il mio progetto (dovrei tirarmi dietro Linux o simile).

Forse potevo scegliere Object Pascal, ma lo conosco poco per cui credo che sarei andato molto lentamente. Anche python lo conosco poco, ma si scrive comunque che è un piacere :)
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 10 Aprile 2015, 06:25:11
Vista :)
Il parser per le expression lo farò molto simile al tuo, al netto ovviamente delle differenze di linguaggio e altri dettagli.
Tempo fa ne avevo scritto uno anch'io in pascal per l'uni, la tecnica alla fine è sempre quella.
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: TheKaneB - 26 Maggio 2015, 18:43:16
Nope, lo riprenderò appena mi sarò liberato di un paio di questioni che mi trascino dietro da mesi e ho un cliente che mi vorrebbe staccare la testa per questo :)
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: Allanon - 21 Giugno 2015, 16:41:41
Credo tu debba per forza analizzare tutti gli elementi e vedere se hanno il punto al loro interno e decifrarli

numero . numero -> decimale
non-numero . numero -> boh
numero . non-numero -> possibile notazione scientifica?
non-numero . non-numero -> possibile struct member?

Insomma, robaccia grezza e bruta tipo questa :P

ocio però a
.245
p.p_this.p_that

X_X
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: Z80Fan - 21 Giugno 2015, 17:10:14
beh semplice: un nome di variabile non può iniziare con un numero, quindi se stavi parsando un numero e trovi un punto, sarà un numero in virgola mobile. Anche se vuoi parsare espressioni tipo ".25", di sicuro non può essere il punto inteso come indicizzazione in struttura.

Esempi:

12.48 -- Vede 1 e inizia a leggere un "numero"; quando arriva al . capisce che non è "intero" ma è "float", e quindi finisce le ultime due cifre.
.88     -- il punto è seguito da un numero: è per forza un numero float nella forma 0.xxx (e di sicuro non può essere una struttura, perchè non hai il nome della struttura su cui applicare il punto).
p123.z4 -- parte da "p", deve essere un identificatore. Continua leggendo "p123" come identificatore. Quando incontra il punto, è ovvio che non può essere un numero, e quindi viene emesso il token di indicizzazione in struttura. Continua leggendo "z4" che è un identificatore valido e termina.
456.0abc7 -- Errore: inizia leggendo 456, vede il punto (=> è un float in forma 456.xxx), legge 0 e gli va ancora bene, legge "a" e si ferma in errore in quanto "a" non è una cifra valida.
98.abc77  -- inizia leggendo "98", al punto capisce che è float, si ferma al "a" in quanto si aspettava la parte decimale fatta solo da cifre (nota che, in se, "abc77" è un identificatore valido, però avendolo trovato mentre il lexer era in modalità "numero float" non ci va bene).
kk_a8.9087 -- vede "k" e parte in modalità identificatore; legge "kk_a8" che è un identificatore valido, al punto capisce che deve essere una struttura (ed emette il token adeguato), però dopo incontra un "9" che non va bene come carattere iniziale di un identificatore, e quindi si ferma in errore (nota che di per se "9087" è un numero valido).

Insomma, il lexer deve avere uno stato su cui può lavorare, non puoi parsare solo guardando un carattere alla volta.

Per i prefissi scientifici, la situazione è la stessa: sei nel quarto caso (456.0abc7), solo che in questo caso (mettiamo 456.01M), quando incontri M, puoi vedere nella tabellina di prefissi validi, scopri che lo è, e quindi lo includi nel numero (o magari scali direttamente il numero della potenza adeguata), solo che poi devi star attento a portare il lexer via dalla modalità "numero", perchè non puoi avere 123.23M214, quindi qualsiasi cifra successiva va segnata come errore.

Per l'esponente, stesso discorso di sopra, solo che una volta che incontri "e" o "E", invece di portare il lexer in modalità "niente cifre", lo porti in modalità "lettura esponente", e procedi come per qualsiasi altro numero.

TL;DR: un numero lo discrimini da un identificatore perchè un numero inizia con una cifra, mentre un identificatore con una lettera (o "_").
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: Z80Fan - 21 Giugno 2015, 22:18:41
non ragiona un carattere alla volta, ragiona per win look ahead
finestre in cui identifica
- separator (che possono essere anche + lunghi di 1 carattere)
- keyword

ora, un separator e' il "." punto, se lo escludo dalla lista dei separator qualsiasi cosa contenga un punto viene vista come un monoblocco

Beh, si potrebbe dire che abbiamo trovato il limite di questo approccio al lexer. :D

A meno di non rifare tutto il lexer con un approccio più "tradizionale"* (potenzialmente più complesso nella sua implementazione), la soluzione con la funzione alternativa che ricontrolla il blocco per discriminare ulteriormente può essere una buona soluzione.
Stai solo attento mentre vai avanti nello sviluppo e tieni un occhio su questi casi speciali in modo che non esplodano e rendano tutto più difficile da seguire. In caso sia necessario, è conveniente a quel punto ripensare al linguaggio ed eventualmente ripartire da capo (se il parser e il lexer sono abbastanza isolati, questo processo dovrebbe essere relativamente indolore).


* Con "tradizionale" intendo un lexer che elabora l'input con un singolo carattere di look-ahead, e a ogni iterazione aggiorna il suo stato interno per seguire l'input. In questo modo, casi "speciali" come questo non sono per niente speciali (semplicemente un altro stato nella state-machine), e puoi scrivere il codice più o meno come ho descritto a parole prima.
Da quanto ho capito, il tuo lexer funziona prima prendendo la massima stringa di caratteri che non contiene un separatore, e successivamente cerca di associarci un significato come blocco unico. Come ti è capitato proprio ora, per alcune situazioni particolari, dove ogni carattere è una specie di separatore, o quantomeno un carattere in una certa posizione può avere significati diversi (es. un alfanumerico che non sia "e" dopo un punto è quasi sicuramente l'inizio di un identificatore, mentre se trovi un numero è la parte decimale di un float), questo approccio ha limitazioni che possono essere gestite solo attraverso un caso speciale che rompe l'"ortogonalità" del lexer.
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: Allanon - 16 Agosto 2015, 08:03:09
Saranno tutti in ferie!
Cmq io questo thread lo sto seguendo ma non intervengo perchè la faccenda è un po' complessa :P
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: Allanon - 21 ſettembre 2015, 23:52:26
O_O'
Ma stai progettando un tokener/parser o Skynet??
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: Allanon - 22 ſettembre 2015, 08:11:18
O_O'
Mi riservo di rileggere gli ultimi post con più calma  ;D
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: Allanon - 22 ſettembre 2015, 15:45:59
summoniamolo!!  ;D
Titolo: Re:Idee e spunti per una calcolatrice - Parte Software
Inserito da: saimon69 - 07 Luglio 2016, 19:04:47
chi si vede \o bentornato!