Использование языка сценариев CSCS для кроссплатформенной разработки

Опубликовано: 2022-03-10
Краткое резюме ↬ В этой статье Василий Каплан объясняет, как можно использовать язык сценариев для разработки кроссплатформенных мобильных приложений. Вы найдете примеры как в iOS, так и в Android, которые включают размещение виджетов на экране, SQLite, веб-запросы и разбор JSON.
Наша цель не построить платформу; это должно быть пересечь их всех.

- Марк Цукерберг

CSCS (Customized Scripting in C#) — это язык сценариев с открытым исходным кодом, реализованный на C#. Синтаксически он очень похож на JavaScript, но также имеет некоторое сходство с Python. Некоторые из этих сходств являются ключевыми словами в хорошо известной конструкции if…elif…else , а также имеют то же определение области действия переменной, что и в Python (например, переменная, определенная внутри блока if или внутри цикла, также будет видна снаружи). .

В отличие от JavaScript и Python, переменные и функции в CSCS нечувствительны к регистру. Основная цель CSCS — позволить разработчику писать как можно меньше кода . Кроме того, один и тот же код используется для разработки под iOS и Android. Кроме того, CSCS можно использовать для разработки под Windows, Mac и Unity.

Примечание . Подробнее о том, как Microsoft использует CSCS в своем продукте Maquette (на основе Unity), можно прочитать здесь.

CSCS можно добавить в проект, внедрив его исходный код C# в проект Visual Studio Xamarin. В отличие от большинства других языков, вы полностью владеете исходным кодом CSCS и можете легко добавлять или изменять его функции. Я поделюсь примером этого позже в статье.

Кроме того, мы собираемся узнать, как начать работу с CSCS и использовать некоторые дополнительные функции, которые были описаны в других статьях. Среди этих функций мы собираемся получить доступ к веб-службе через веб-запросы с разбором строки JSON, а также будем использовать SQLite на iOS и Android.

Самый простой способ начать работу — загрузить образец проекта с использованием CSCS и начать играть с файлом start.cscs . Это то, что мы будем делать в следующем разделе: создание приложения для iOS/Android с базовым графическим интерфейсом и событиями.

Еще после прыжка! Продолжить чтение ниже ↓

"Привет мир!" В CSCS

Давайте начнем с относительно простого примера кода CSCS, который создает экран с несколькими виджетами:

 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); }

На изображении ниже показан получившийся пользовательский интерфейс на iPhone, а также на устройстве Android после нажатия кнопки «Привет» и отсутствия ввода в поле «Редактирование текста»:

Привет мир!" на iPhone (слева) и Android (справа)
"Привет мир!" на iPhone (слева) и Android (справа) (большой превью)

Давайте кратко рассмотрим код выше. Он начинается с вызова функции AutoScale() , который сообщает синтаксическому анализатору, что размеры виджета зависят от размера экрана, т. е. они будут автоматически изменены (виджет будет выглядеть больше на больших экранах и меньше на меньших). экраны). Этот параметр также может быть переопределен для каждого виджета.

Обратите внимание, что нет необходимости создавать специальный обработчик нажатия кнопки. Если вы определяете функцию с именем widgetName_click() , она будет использоваться в качестве обработчика, когда пользователь нажимает на виджет с именем widgetName (это не обязательно должна быть кнопка, это может быть любой виджет). Вот почему функция buttonHi_click() будет запущена, как только пользователь нажмет на кнопку.

Вы, возможно, заметили, что графический интерфейс полностью построен на коде. Это делается путем предоставления относительного местоположения виджета при его добавлении. Общий формат команды определения местоположения следующий:

 location = GetLocation(WidgetX, HorizontalPlacement, WidgetY, VerticalPlacement, deltaX=0, deltaY=0, autoResize=true);

Таким образом, вы можете разместить виджет относительно других виджетов на экране. Частным случаем виджета является виджет «ROOT», то есть главный экран.

После создания местоположения вам необходимо предоставить его в качестве аргумента любой из следующих функций:

  • AddLabel ,
  • AddButton ,
  • AddCombobox ,
  • AddStepper ,
  • AddListView ,
  • AddTextView ,
  • AddStepper ,
  • AddImageView ,
  • AddSlider ,
  • AddPickerView ,
  • и так далее.

Все вышеперечисленное имеет одинаковую структуру:

 AddButton(location, newWidgetname, initialValue, width, height);

Ширина и высота виджета будут зависеть от размера экрана, если ранее была запущена команда AutoScale() . Также начальным значением (в случае кнопки) является отображаемый на ней текст. Это можно изменить в любое время, вызвав SetText(widgetName, newText) .

Использование кода Visual Studio для отладки CSCS

Мы также можем использовать код Visual Studio для отладки скриптов CSCS. Если вы хотите разрабатывать приложения для Android и iOS, вам нужно использовать Mac. После установки Visual Studio Code установите отладчик CSCS и расширение REPL.

Чтобы использовать расширение, добавьте эту строку кода в любом месте вашего start.cscs скрипта start.cscs:

 StartDebugger();

На следующем изображении ниже показано, как вы можете использовать Visual Studio Code для отладки и изменения функциональности «Hello, World!» приложение, которое мы разработали в предыдущем разделе. В следующем примере мы добавим метку и кнопку на лету к существующему макету.

Для этого мы просто выбираем код, который будет выполняться парсером, и нажимаем Ctrl + 8 . В результате в центре экрана будут добавлены метка и кнопка. Мы также добавляем обработчик кнопки, который будет обновлять новую метку текущим временем при каждом нажатии кнопки.

Изменение макета на лету с помощью Visual Studio Code
Изменение макета на лету с помощью Visual Studio Code (большой предварительный просмотр)

Использование SQLite в CSCS

SQLite представляет собой тип реляционной базы данных ACID (атомарность, согласованность, изоляция, долговечность) и был разработан Ричардом Хиппом (первая версия была выпущена в 2000 году). В отличие от других реляционных баз данных, таких как Microsoft SQL Server или Oracle Database, она является встроенной. (Встраивается не только в устройство, но и в конечную программу.) Входит в программу в виде очень компактной библиотеки размером менее 500 КБ. Но два приложения (выпущенные одним и тем же разработчиком) могут читать одну и ту же базу данных SQLite, если путь к файлу базы данных известен обоим приложениям.

Преимущество SQLite в том, что его можно использовать без дополнительной установки на устройстве iOS или Android. Недостаток в том, что она явно не может хранить столько данных, сколько «обычная» БД, а также в том, что она слабо типизирована (т.е. вы можете вставить строку вместо целого числа — тогда оно будет преобразовано в целое число или 0 в случае ошибки). С другой стороны, последнее также можно рассматривать как преимущество.

SQLite можно легко использовать из CSCS без дополнительных операторов импорта. Вот таблица, которая поможет вам получить обзор основных функций SQLite, используемых в CSCS:

Команда Описание
SQLInit(DBName) Инициализирует базу данных или задает базу данных для использования с последующими операторами БД.
SQLDBExists(DBName) Проверяет, инициализирована ли БД. Также устанавливает базу данных, которая будет использоваться с последующими операторами БД.
SQLQuery(query) Выполняет запрос SQL (оператор выбора). Возвращает таблицу с записями.
SQLNonQuery(nonQuery) Выполняет не запрос SQL, например оператор обновления, создания или удаления. Возвращает количество затронутых записей.
SQLInsert(tableName, columnList, data) Вставляет переданную таблицу данных записей в указанную таблицу БД. Аргумент columnList имеет следующую структуру: colName1,colName2,…,colNameN

Таблица 1: Команды SQLite в CSCS

Вот как обычно используются функции SQLInit() и 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);

Позже мы увидим больше примеров того, как вы можете выбирать и вставлять данные в базу данных SQLite. Я покажу вам пример того, как записывать биржевые данные, извлеченные из веб-службы, в локальную базу данных SQLite.

Добавление пользовательских функций в CSCS

В этом разделе мы увидим, как вы можете расширить функциональность CSCS. В качестве примера ниже мы увидим существующую реализацию функции CSCS Sleep.

Чтобы добавить пользовательскую функциональность, все, что вам нужно сделать, это создать новый класс, производный от класса ParserFunction , переопределив его метод Evaluate() и зарегистрировав этот класс в синтаксическом анализаторе. Вот короткая версия (без проверки ошибок):

 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; } }

Регистрация класса в парсере может быть выполнена в любом месте на этапе инициализации с помощью следующей команды:

 ParserFunction.RegisterFunction("Sleep", new SleepFunction());

Вот и все! Теперь метод Evaluate() класса SleepFunction будет вызываться, как только парсер извлечет токен Sleep.

Обратите внимание, что CSCS нечувствителен к регистру (за исключением основных операторов потока управления: if , elif , else , for , while , function , include , new , class , return , try , throw , catch , break , continue ). Это означает, что вы можете ввести либо «sleep(100)», либо «Sleep(100)» — оба вызова приостановят исполняемый поток на 100 миллисекунд.

Обработка JSON в CSCS

JSON (нотация объектов JavaScript) — это упрощенный формат обмена данными, состоящий из пар атрибут-значение и пар типа массива. Он был разработан Дугласом Крокфордом в начале 2000-х (примерно в то же время, когда появился и SQLite).

В этом разделе мы узнаем, как анализировать JSON с помощью CSCS.

Функция CSCS для анализа строки JSON — GetVariableFromJSON(jsonText) . Эта функция возвращает хеш-таблицу, в которой ключами являются атрибуты строки JSON.

Рассмотрим следующий пример строки JSON:

 jsonString = '{ "eins" : 1, "zwei" : "zweiString", "mehr" : { "uno": "dos" }, "arrayValue" : [ "une", "deux" ] }';

После вызова:

 a = GetVariableFromJSON();

Переменная a будет хеш-таблицей со следующими значениями:

 a["eins"] = 1 a["zwei"] = "zweiString" a["mehr"]["uno"] = "dos" a["arrayValue"][0] = "une" a["arrayValue"][1] = "deux"

В следующем разделе мы увидим еще один пример анализа строки JSON из веб-службы.

Пример приложения с SQLite, веб-запросами и JSON

Для приложения, использующего SQLite, веб-службу и синтаксический анализ JSON, мы собираемся использовать веб-службу Alpha Vantage. Вы можете получить ключ API бесплатно, но бесплатная версия позволяет обращаться к их веб-сервису не более 5 раз в минуту.

Используя Alpha Vantage, вы можете извлекать различные наборы финансовых данных, включая цены на акции. Это то, что мы собираемся сделать в нашем примере приложения.

На изображении ниже показано, как приложения «Акции» выглядят на устройствах iOS и Android.

Извлечение акций из веб-сервиса Alpha Vantage на iOS (слева) и Android (справа)
Извлечение акций из веб-сервиса Alpha Vantage на iOS (слева) и Android (справа) (большой предварительный просмотр)

Код CSCS для создания графического интерфейса выглядит следующим образом:

 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();

Метод getDataFromDB() извлечет все данные из базы данных SQLite. Он использует запрос SQL, определенный следующим образом:

 query = "SELECT Symbol, Low, High, Close, Volume, DATETIME(Stamp, 'localtime') as Stamp FROM Data ORDER BY Stamp DESC LIMIT 5;";

Взгляните на приведенный ниже код реализации 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); }

Теперь давайте посмотрим, как мы получаем данные из веб-службы Alpha Vantage. Сначала мы инициализируем данные:

 baseURL = "https://www.alphavantage.co/query? " + "function=TIME_SERIES_DAILY&symbol="; apikey = "Y12T0TY5EUS6BC5F"; stocks = {"MSFT", "AAPL", "GOOG", "FB", "AMZN"}; totalStocks = stocks.Size;

Далее мы загружаем акции одну за другой, как только пользователь нажимает кнопку «Обновить»:

 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"); }

Вот основная функция CSCS, используемая для получения данных из веб-службы:

 WebRequest("GET", stockUrl, "", symbol, "OnSuccess", "OnFailure");

Последние два параметра — это функции, которые вызываются по завершении веб-запроса. Например, в случае сбоя будет вызвана следующая функция CSCS:

 function OnFailure(object, errorCode, text) { SetText(labelError, text); lockGui(false); }

В результате пользователь получит сообщение об ошибке, как показано ниже:

Ошибка при запросе веб-данных
Ошибка при запросе веб-данных (Большой предварительный просмотр)

Но, если все в порядке, мы собираемся разобрать строку JSON и вставить ее содержимое в базу данных 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]); } }

Чтобы понять, как мы получаем доступ к различным полям в приведенной выше хеш-таблице, давайте взглянем на реальную строку, полученную из веб-запроса 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" }, … } }

Как видите, мы получаем самую последнюю дату как первый элемент массива allDates , который состоит из всех извлеченных дат.

Заключение

Добавить CSCS в ваш проект очень просто. Все, что вам нужно сделать, — это просто встроить исходный код CSCS в качестве модуля в свой проект — точно так же, как это делается в примере проекта Xamarin.

Используете ли вы и расширяете язык сценариев CSCS в своих проектах? Оставьте комментарий ниже — я буду рад услышать от вас!

Дальнейшее чтение

Если вы хотите немного больше изучить язык CSCS, вот несколько статей, о которых я писал по этой теме:

  • «Синтаксический анализатор выражений разделения и слияния на C#», журнал MSDN (октябрь 2015 г.)
  • «Настраиваемые сценарии на C#», журнал MSDN (февраль 2016 г.)
  • «Написание собственных мобильных приложений с использованием настраиваемого языка сценариев», журнал MSDN (февраль 2018 г.)
  • «CSCS: настраиваемые сценарии на C#», GitHub
  • «Разработка кроссплатформенных нативных приложений с помощью функционального языка сценариев», журнал CODE.
  • «Краткое внедрение пользовательского языка» (электронная книга)
  • «Краткое написание нативных мобильных приложений на функциональном языке» (электронная книга)

В качестве дополнительного ресурса я также рекомендую прочитать, как вы можете улучшить производительность CSCS, предварительно скомпилировав его функции.