Utilizzo del linguaggio di scripting CSCS per lo sviluppo multipiattaforma
Pubblicato: 2022-03-10Il nostro obiettivo non è costruire una piattaforma; è da attraversarli tutti.
— Mark Zuckerberg
CSCS (Customized Scripting in C#) è un linguaggio di scripting open source implementato in C#. Sintatticamente è molto simile a JavaScript, ma ha anche alcune somiglianze con Python. Alcune di queste somiglianze sono le parole chiave nel noto costrutto if…elif…else
, e hanno anche la stessa definizione di ambito di variabile come in Python (ad es. una variabile definita all'interno di un blocco if
o all'interno di un ciclo sarà visibile anche all'esterno) .
A differenza di JavaScript e Python, le variabili e le funzioni in CSCS non fanno distinzione tra maiuscole e minuscole. L'obiettivo principale di CSCS è consentire allo sviluppatore di scrivere il minor numero di codice possibile . Inoltre, lo stesso codice viene utilizzato sia per lo sviluppo iOS che Android. Inoltre, CSCS può essere utilizzato per lo sviluppo di Windows, Mac e Unity.
Nota : puoi leggere di più su come Microsoft utilizza CSCS nel loro prodotto Maquette (basato su Unity) qui.
CSCS può essere aggiunto al progetto incorporando il codice sorgente C# in un progetto di Visual Studio Xamarin. A differenza della maggior parte delle altre lingue, hai la piena proprietà del codice sorgente CSCS e puoi facilmente aggiungere o modificare la sua funzionalità. Condividerò un esempio di questo più avanti nell'articolo.
Inoltre, impareremo come iniziare con CSCS e utilizzare alcune funzionalità più avanzate che sono state trattate in altri articoli. Tra queste funzionalità, accederemo a un servizio Web tramite Richieste Web con analisi di stringhe JSON e utilizzeremo SQLite anche su iOS e Android.
Il modo più semplice per iniziare è scaricare un esempio di un progetto utilizzando CSCS e iniziare a giocare con il file start.cscs . Questo è ciò che faremo nella prossima sezione: creare un'app iOS/Android con GUI ed eventi di base.
"Ciao mondo!" In CSCS
Iniziamo con un esempio relativamente semplice di codice CSCS che costruisce uno schermo con alcuni widget:
AutoScale(); SetBackgroundColor("light_green"); locLabelText = GetLocation("ROOT", "CENTER", "ROOT", "TOP"); AddLabel(locLabelText, "labelText", "Welcome " + _DEVICE_INFO_ + " " + _VERSION_INFO_ + " User!", 600, 100); locTextEdit = GetLocation("ROOT", "LEFT", labelText, "BOTTOM"); AddTextEdit(locTextEdit, "textEdit", "Your name", 320, 80); locButton = GetLocation(textEdit,"RIGHT",textEdit, "CENTER"); AddButton(locButton, "buttonHi", "Hello", 160, 80); function buttonHi_click(sender, arg) { name = getText(textEdit); msg = name != "" ? "Hello, "+ name + "!" : "Hello, World!"; AlertDialog("My Great App", msg); }
L'immagine seguente mostra l'interfaccia utente risultante su un iPhone e un dispositivo Android dopo aver fatto clic sul pulsante "Ciao" e non aver digitato nulla nel campo "Modifica testo":
Esaminiamo brevemente il codice sopra. Inizia con la chiamata alla funzione AutoScale()
, e ciò che fa è dire al parser che le dimensioni del widget sono relative alla dimensione dello schermo, cioè verranno ridimensionate automaticamente (il widget apparirà più grande su schermi più grandi e più piccolo su schermi più piccoli schermi). Questa impostazione potrebbe anche essere sovrascritta per widget.
Si noti che non è necessario creare un gestore speciale su un clic del pulsante. Se definisci una funzione con nome widgetName_click()
, verrà utilizzata come gestore quando l'utente fa clic su un widget chiamato widgetName
(non deve essere un pulsante, può effettivamente essere qualsiasi widget). Ecco perché la funzione buttonHi_click()
verrà attivata non appena l'utente fa clic sul pulsante.
Potresti aver notato che la GUI è costruita completamente nel codice. Questo viene fatto fornendo una posizione relativa del widget quando lo si aggiunge. Il formato generale di un comando di posizione è il seguente:
location = GetLocation(WidgetX, HorizontalPlacement, WidgetY, VerticalPlacement, deltaX=0, deltaY=0, autoResize=true);
Quindi, puoi posizionare un widget relativo ad altri widget sullo schermo. Un caso speciale di un widget è un widget "ROOT", ovvero la schermata principale.
Dopo aver creato una posizione, è necessario fornirla come argomento per una delle seguenti funzioni:
-
AddLabel
, -
AddButton
, -
AddCombobox
, -
AddStepper
, -
AddListView
, -
AddTextView
, -
AddStepper
, -
AddImageView
, -
AddSlider
, -
AddPickerView
, - e così via.
Tutto quanto sopra ha la stessa struttura:
AddButton(location, newWidgetname, initialValue, width, height);
La larghezza e l'altezza del widget saranno relative alla dimensione dello schermo se il comando CSCS AutoScale()
è stato eseguito in precedenza. Inoltre, il valore iniziale (nel caso di un pulsante) è il testo visualizzato su di esso. Questo può essere modificato in qualsiasi momento invocando SetText(widgetName, newText)
.
Utilizzo del codice di Visual Studio per eseguire il debug di CSCS
Possiamo anche usare Visual Studio Code per eseguire il debug degli script CSCS. Se vuoi sviluppare app sia per Android che per iOS, devi utilizzare un Mac. Dopo aver installato Visual Studio Code, installare CSCS Debugger e l'estensione REPL.
Per utilizzare l'estensione, aggiungi questa riga di codice ovunque nello script CSCS start.cscs
:
StartDebugger();
L'immagine seguente mostra come utilizzare Visual Studio Code per eseguire il debug e modificare la funzionalità di "Hello, World!" app che abbiamo sviluppato nella sezione precedente. Nel prossimo esempio, aggiungeremo un'etichetta e un pulsante al volo al layout esistente.
Per fare ciò, selezioniamo semplicemente il codice che deve essere eseguito dal parser e premiamo Ctrl + 8 . Di conseguenza, verranno aggiunti un'etichetta e un pulsante al centro dello schermo. Aggiungiamo anche un gestore di pulsanti che aggiornerà la nuova etichetta con l'ora corrente ad ogni clic del pulsante.
Utilizzo di SQLite in CSCS
SQLite è un tipo di database relazionale ACID (Atomicity, Consistency, Isolation, Durability) ed è stato sviluppato da Richard Hipp (la prima versione è stata rilasciata nel 2000). A differenza di altri database relazionali, come Microsoft SQL Server o Oracle Database, è incorporato. (Incorporato non solo nel dispositivo, ma anche nel programma finale.) È incluso nel programma come una libreria molto compatta, che ha una dimensione inferiore a 500 KB. Ma due app (rilasciate dallo stesso sviluppatore) possono leggere lo stesso DB SQLite se il percorso del file DB è noto a entrambe le app.
Il vantaggio di SQLite è che può essere utilizzato senza un'installazione aggiuntiva su un dispositivo iOS o Android. Lo svantaggio è che ovviamente non può contenere tanti dati come un DB "normale" e anche che è tipizzato debolmente (cioè puoi inserire una stringa invece di un intero — verrà quindi convertito in un intero o 0 in caso di errore). D'altra parte, anche quest'ultimo può essere visto come un vantaggio.
SQLite può essere facilmente utilizzato da CSCS senza istruzioni di importazione aggiuntive. Ecco una tabella che ti aiuterà a ottenere una panoramica delle principali funzioni SQLite utilizzate in CSCS:
Comando | Descrizione |
---|---|
SQLInit(DBName) | Inizializza un database o imposta un database da utilizzare con le conseguenti istruzioni DB. |
SQLDBExists(DBName) | Verifica se il DB è stato inizializzato. Imposta anche il database da utilizzare con le conseguenti istruzioni DB. |
SQLQuery(query) | Esegue una query SQL (un'istruzione select). Restituisce una tabella con record. |
SQLNonQuery(nonQuery) | Esegue un'istruzione SQL non-query, ad esempio un'istruzione di aggiornamento, creazione o eliminazione. Restituisce il numero di record interessati. |
SQLInsert(tableName, columnList, data) | Inserisce la tabella dei dati dei record passata nella tabella DB specificata. L'argomento columnList ha la struttura seguente: colName1,colName2,…,colNameN |
Tabella 1: comandi SQLite in CSCS
Ecco come vengono generalmente utilizzate le funzioni SQLInit()
e SQLDBExists()
:
DBName = "myDB.db1"; if (!SQLDBExists(DBName)) { create = "CREATE TABLE [Data] (Symbol ntext, Low real, High real, Close real, Volume real, Stamp text DEFAULT CURRENT_TIMESTAMP)"; SQLNonQuery(create); } SQLInit(DBName);
Vedremo più esempi di come selezionare e inserire dati in un database SQLite in seguito. Ti mostrerò un esempio di come scrivere dati di stock estratti da un servizio Web in un database SQLite locale.
Aggiunta di funzionalità personalizzate a CSCS
In questa sezione, vedremo come estendere la funzionalità CSCS. A titolo di esempio, vedremo di seguito l'implementazione esistente della funzione CSCS Sleep.
Per aggiungere funzionalità personalizzate, tutto ciò che devi fare è creare una nuova classe derivando dalla classe ParserFunction
, sovrascrivendo il suo metodo Evaluate()
e registrando questa classe con il parser. Ecco una versione breve (senza controllo degli errori):
class SleepFunction : ParserFunction { protected override Variable Evaluate(ParsingScript script) { List args = script.GetFunctionArgs(); int sleepms = Utils.GetSafeInt(args, 0); Thread.Sleep(sleepms); return Variable.EmptyInstance; } }
class SleepFunction : ParserFunction { protected override Variable Evaluate(ParsingScript script) { List args = script.GetFunctionArgs(); int sleepms = Utils.GetSafeInt(args, 0); Thread.Sleep(sleepms); return Variable.EmptyInstance; } }
La registrazione di una classe con il parser può essere eseguita ovunque nella fase di inizializzazione tramite il seguente comando:
ParserFunction.RegisterFunction("Sleep", new SleepFunction());
Questo è tutto! Ora il metodo Evaluate()
della classe SleepFunction
verrà invocato non appena un token "Sleep" viene estratto dal parser.
Si noti che CSCS non fa distinzione tra maiuscole e minuscole (tranne le istruzioni di flusso del controllo di base: if
, elif
, else
, for
, while
, function
, include
, new
, class
, return
, try
, throw
, catch
, break
, continue
). Ciò significa che puoi digitare "sleep(100)" o "Sleep(100)": entrambe le chiamate sospenderanno il thread in esecuzione per 100 millisecondi.
Elaborazione JSON in CSCS
JSON (JavaScript Object Notation) è un formato di interscambio dati leggero, costituito da coppie attributo-valore e coppie di tipo array. È stato sviluppato da Douglas Crockford all'inizio degli anni 2000 (più o meno nello stesso periodo in cui è apparso anche SQLite).
In questa sezione impareremo come analizzare JSON usando CSCS.
La funzione CSCS per analizzare una stringa JSON è GetVariableFromJSON(jsonText)
. Questa funzione restituisce una tabella hash in cui le chiavi sono gli attributi della stringa JSON.
Considera il seguente esempio di una stringa JSON:
jsonString = '{ "eins" : 1, "zwei" : "zweiString", "mehr" : { "uno": "dos" }, "arrayValue" : [ "une", "deux" ] }';
Dopo aver invocato:
a = GetVariableFromJSON();
La variabile a
sarà una tabella hash con i seguenti valori:
a["eins"] = 1 a["zwei"] = "zweiString" a["mehr"]["uno"] = "dos" a["arrayValue"][0] = "une" a["arrayValue"][1] = "deux"
Nella prossima sezione, vedremo un altro esempio di analisi di una stringa JSON da un servizio Web.
Un esempio di app con SQLite, richieste Web e JSON
Per un'app che utilizza SQLite, un servizio Web e un'analisi JSON, utilizzeremo Alpha Vantage Web Service. Puoi ottenere una chiave API gratuitamente ma la versione gratuita consente di accedere al loro servizio web non più di 5 volte al minuto.
Utilizzando Alpha Vantage, puoi estrarre vari set di dati finanziari, inclusi i prezzi delle azioni. Questo è ciò che faremo nella nostra app di esempio.
L'immagine seguente mostra l'aspetto delle app di Azioni su un dispositivo iOS e su un dispositivo Android.
Il codice CSCS per costruire la GUI è il seguente:
locLabel = GetLocation("ROOT","CENTER", "ROOT","TOP", 0,30); AddLabel(locLabel, "labelRefresh", "", 480, 60); locSFWidget = GetLocation("ROOT","CENTER", labelRefresh,"BOTTOM"); AddSfDataGrid(locSFWidget, "DataGrid", "", graphWidth, graphHeight); listCols = {"Symbol","string", "Low","number", "High", "number", "Close","number", "Volume","number"}; AddWidgetData(DataGrid, listCols, "columns"); colWidth = {17, 19, 19, 19, 26}; AddWidgetData(DataGrid, colWidth, "columnWidth"); locButton = GetLocation("ROOT","CENTER",DataGrid,"BOTTOM"); AddButton(locButton, "buttonRefresh", "Refresh", 160, 80); locLabelError = GetLocation("ROOT","CENTER","ROOT","BOTTOM"); AddLabel(locLabelError, "labelError", "", 600, 160); SetFontColor(labelError, "red"); AlignText(labelError, "center"); getDataFromDB();
Il metodo getDataFromDB()
estrarrà tutti i dati dal database SQLite. Utilizza la query SQL definita come segue:
query = "SELECT Symbol, Low, High, Close, Volume, DATETIME(Stamp, 'localtime') as Stamp FROM Data ORDER BY Stamp DESC LIMIT 5;";
Dai un'occhiata al codice seguente per l'implementazione getDataFromDB()
.
function getDataFromDB() { results = SQLQuery(query); for (i = 1; i < results.Size; i++) { vals = results[i]; stock = vals[0]; low = Round(vals[1], 2); high = Round(vals[2], 2); close = Round(vals[3], 2); volume = Round(vals[4], 2); refresh = vals[5]; stockData = {stock, low, high, close, volume}; AddWidgetData(DataGrid, stockData, "item"); } SetText(labelRefresh, "DB Last Refresh: " + refresh); lockGui(false); }
Ora vediamo come otteniamo i dati da Alpha Vantage Web Service. Per prima cosa, inizializziamo i dati:
baseURL = "https://www.alphavantage.co/query? " + "function=TIME_SERIES_DAILY&symbol="; apikey = "Y12T0TY5EUS6BC5F"; stocks = {"MSFT", "AAPL", "GOOG", "FB", "AMZN"}; totalStocks = stocks.Size;
Successivamente, carichiamo le azioni una per una non appena l'utente fa clic sul pulsante "Aggiorna":
function buttonRefresh_click(object, arg) { lockGui(); SetText(labelRefresh, "Loading ..."); SetText(labelError, ""); ClearWidget(DataGrid); loadedStocks = 0; getData(stocks[loadedStocks]); } function getData(symbol) { stockUrl = baseURL + symbol + "&apikey=" + apikey; WebRequest("GET", stockUrl, "", symbol, "OnSuccess", "OnFailure"); }
Ecco la principale funzione CSCS da utilizzare per ottenere dati da un Web Service:
WebRequest("GET", stockUrl, "", symbol, "OnSuccess", "OnFailure");
Gli ultimi due parametri sono funzioni da richiamare al completamento della richiesta web. Ad esempio, in caso di guasto, verrà richiamata la seguente funzione CSCS:
function OnFailure(object, errorCode, text) { SetText(labelError, text); lockGui(false); }
Di conseguenza, l'utente riceverà un messaggio di errore come mostrato di seguito:
Ma, se tutto va bene, analizzeremo la stringa JSON e inseriremo il suo contenuto nel DB SQLite.
function OnSuccess(object, errorCode, text) { jsonFromText = GetVariableFromJSON(text); metaData = jsonFromText[0]; result = jsonFromText[1]; symbol = metaData["2. Symbol"]; lastRefreshed = metaData["3. Last Refreshed"]; allDates = result.keys; dateData = result[allDates[0]]; high = Round(dateData["2. high"], 2); low = Round(dateData["3. low"], 2); close = Round(dateData["4. close"], 2); volume = dateData["5. volume"]; stockData = {symbol, low, high, close, volume}; SQLInsert("Data","Symbol,Low,High,Close,Volume",stockData); if (++loadedStocks >= totalStocks) { getDataFromDB(); } else { getData(stocks[loadedStocks]); } }
Per capire come accediamo ai diversi campi della tabella hash sopra, diamo un'occhiata alla stringa effettiva ricevuta dalla richiesta web di Alpha Vantage:
{ "Meta Data": { "1. Information": "Daily Prices (open, high, low, close) and Volumes", "2. Symbol": "MSFT", "3. Last Refreshed": "2019-10-02 14:23:20", "4. Output Size": "Compact", "5. Time Zone": "US/Eastern" }, "Time Series (Daily)": { "2019-10-02": { "1. open": "136.3400", "2. high": "136.3700", "3. low": "133.5799", "4. close": "134.4100", "5. volume": "11213086" }, … } }
Come puoi vedere, otteniamo l'ultima data come primo elemento dell'array allDates
che consiste in tutte le date estratte.
Conclusione
Aggiungere CSCS al tuo progetto è facile. Tutto quello che devi fare è semplicemente incorporare il codice sorgente di CSCS come modulo nel tuo progetto, proprio come è stato fatto in un progetto Xamarin di esempio.
Utilizzi ed estendi il linguaggio di scripting CSCS nei tuoi progetti? Lascia un commento qui sotto — Sarei felice di sentirti!
Ulteriori letture
Se vuoi esplorare un po' di più il linguaggio CSCS, ecco alcuni degli articoli di cui ho scritto sull'argomento:
- "Un parser di espressioni split-and-merge in C#", MSDN Magazine (ottobre 2015)
- "Scripting personalizzabile in C#", MSDN Magazine (febbraio 2016)
- "Scrittura di app mobili native utilizzando un linguaggio di scripting personalizzabile", MSDN Magazine (febbraio 2018)
- "CSCS: script personalizzati in C#", GitHub
- "Sviluppo di app native multipiattaforma con un linguaggio di scripting funzionale", CODE Magazine
- "Implementare in modo succinto un linguaggio personalizzato" (eBook)
- "Scrivere in modo succinto app mobili native in un linguaggio funzionale" (eBook)
Come risorsa aggiuntiva, consiglio anche di leggere come migliorare le prestazioni di CSCS precompilandone le funzioni.