Autore Topic: Idee e spunti per una calcolatrice - Parte Software  (Letto 13413 volte)

Offline TheKaneB

  • Human Debugger
  • *****
  • Post: 5292
  • Karma: +20/-23
    • Mostra profilo
    • http://www.antoniobarba.org
Re:Idee e spunti per una calcolatrice - Parte Software
« Risposta #15 il: 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

Offline Nonefonow

  • Guru
  • *****
  • Post: 1979
  • Karma: +36/-3
    • Mostra profilo
Re:Idee e spunti per una calcolatrice - Parte Software
« Risposta #16 il: 01 Aprile 2015, 23:42:53 »
Magari puoi prendere uno spunto da Norbert.
 
http://members.aon.at/nkehrer/a800_c64_hp_emu.html

Offline TheKaneB

  • Human Debugger
  • *****
  • Post: 5292
  • Karma: +20/-23
    • Mostra profilo
    • http://www.antoniobarba.org
Re:Idee e spunti per una calcolatrice - Parte Software
« Risposta #17 il: 02 Aprile 2015, 06:51:08 »
Magari puoi prendere uno spunto da Norbert.
 
http://members.aon.at/nkehrer/a800_c64_hp_emu.html

Ma ho già alcune HP, a che mi serve un emulatore per c64? :D

Offline TheKaneB

  • Human Debugger
  • *****
  • Post: 5292
  • Karma: +20/-23
    • Mostra profilo
    • http://www.antoniobarba.org
Re:Idee e spunti per una calcolatrice - Parte Software
« Risposta #18 il: 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 ;)

Offline TheKaneB

  • Human Debugger
  • *****
  • Post: 5292
  • Karma: +20/-23
    • Mostra profilo
    • http://www.antoniobarba.org
Re:Idee e spunti per una calcolatrice - Parte Software
« Risposta #19 il: 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

Offline TheKaneB

  • Human Debugger
  • *****
  • Post: 5292
  • Karma: +20/-23
    • Mostra profilo
    • http://www.antoniobarba.org
Re:Idee e spunti per una calcolatrice - Parte Software
« Risposta #20 il: 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 :)

Offline TheKaneB

  • Human Debugger
  • *****
  • Post: 5292
  • Karma: +20/-23
    • Mostra profilo
    • http://www.antoniobarba.org
Re:Idee e spunti per una calcolatrice - Parte Software
« Risposta #21 il: 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.

Offline TheKaneB

  • Human Debugger
  • *****
  • Post: 5292
  • Karma: +20/-23
    • Mostra profilo
    • http://www.antoniobarba.org
Re:Idee e spunti per una calcolatrice - Parte Software
« Risposta #22 il: 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?

Offline TheKaneB

  • Human Debugger
  • *****
  • Post: 5292
  • Karma: +20/-23
    • Mostra profilo
    • http://www.antoniobarba.org
Re:Idee e spunti per una calcolatrice - Parte Software
« Risposta #23 il: 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.

Offline Z80Fan

  • Administrator
  • Guru
  • *****
  • Post: 1671
  • Karma: +13/-2
    • Mostra profilo
    • http://z80fan.altervista.org
Re:Idee e spunti per una calcolatrice - Parte Software
« Risposta #24 il: 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; 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.

Offline TheKaneB

  • Human Debugger
  • *****
  • Post: 5292
  • Karma: +20/-23
    • Mostra profilo
    • http://www.antoniobarba.org
Re:Idee e spunti per una calcolatrice - Parte Software
« Risposta #25 il: 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 :)

Offline Z80Fan

  • Administrator
  • Guru
  • *****
  • Post: 1671
  • Karma: +13/-2
    • Mostra profilo
    • http://z80fan.altervista.org
Re:Idee e spunti per una calcolatrice - Parte Software
« Risposta #26 il: 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, 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<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.

Offline TheKaneB

  • Human Debugger
  • *****
  • Post: 5292
  • Karma: +20/-23
    • Mostra profilo
    • http://www.antoniobarba.org
Re:Idee e spunti per una calcolatrice - Parte Software
« Risposta #27 il: 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

Offline TheKaneB

  • Human Debugger
  • *****
  • Post: 5292
  • Karma: +20/-23
    • Mostra profilo
    • http://www.antoniobarba.org
Re:Idee e spunti per una calcolatrice - Parte Software
« Risposta #28 il: 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 :)

Offline TheKaneB

  • Human Debugger
  • *****
  • Post: 5292
  • Karma: +20/-23
    • Mostra profilo
    • http://www.antoniobarba.org
Re:Idee e spunti per una calcolatrice - Parte Software
« Risposta #29 il: 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.

Tags: