diff --git a/examples/vga_shell/vga_shell.ino b/examples/vga_shell/vga_shell.ino new file mode 100644 index 0000000..4e7e37f --- /dev/null +++ b/examples/vga_shell/vga_shell.ino @@ -0,0 +1,196 @@ +/* + * This file is part of the Rishka distribution (https://github.com/rishka-esp32/rishka). + * Copyright (c) 2024 Nathanne Isip. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * This example initializes a VGA display using FabGL library, loads a font for a terminal, + * initializes an SD card using SPI, initializes a Rishka virtual machine, loads the specified + * file into the Rishka VM, executes it, and then waits for the next input. + */ + +#include +#include +#include +#include + +#define SD_CS 13 // SD card chip select pin +#define SD_SCK 14 // SD card SPI clock pin +#define SD_MOSI 12 // SD card SPI MOSI pin +#define SD_MISO 35 // SD card SPI MISO pin + +// TFT display controller, PS/2 keyboard controller, +// and Terminal instance +fabgl::VGAController DisplayController; +fabgl::PS2Controller PS2Controller; +fabgl::Terminal Terminal; + +// SPI instance for SD card +SPIClass sdSpi(HSPI); + +// Rishka virtual machine instance +RishkaVM* vm; + +// This function utilizes a LineEditor instance to read a line +// from the terminal input. It then returns the read line as a String. +static inline String readLine() { + fabgl::LineEditor line(&Terminal); + line.edit(); + + return String(line.get()); +} + +// This function splits a given input string into tokens based on space characters. +// It also handles quotes to allow space characters within quoted strings. +void splitString(const String& input, char** tokens, int maxTokens, int &count) { + int tokenCount = 0, tokenStart = 0; + bool inQuotes = false; + + // Iterate over each character in the input string + for(int i = 0; i < input.length(); i++) { + // Check if the current character is a quotation mark + if(input[i] == '"') + // Toggle the inQuotes flag to handle quoted strings + inQuotes = !inQuotes; + else if(input[i] == ' ' && !inQuotes) { + // If the current character is a space and not inside quotes, + // extract the token from the input string + if(tokenCount < maxTokens) { + // Extract the substring between tokenStart and i + // and copy it into the tokens array + input.substring(tokenStart, i) + .toCharArray(tokens[tokenCount], i - tokenStart + 1); + + // Null-terminate the token string + tokens[tokenCount++][i - tokenStart] = '\0'; + tokenStart = i + 1; + count++; + } + else break; // Break if maximum token count reached + } + } + + // Handle the case where there are remaining characters after the last space + if(tokenCount < maxTokens && tokenStart < input.length()) { + // Extract the substring starting from tokenStart to the end of the input string + // and copy it into the tokens array + input.substring(tokenStart).toCharArray(tokens[tokenCount], input.length() - tokenStart + 1); + tokens[tokenCount++][input.length() - tokenStart] = '\0'; + count++; + } +} + +void setup() { + Serial.begin(115200); + PS2Controller.begin(PS2Preset::KeyboardPort0); + + // Initialize VGA display + DisplayController.begin(); + DisplayController.setResolution(VGA_640x240_60Hz); + + // Initialize terminal + Terminal.begin(&DisplayController); + Terminal.loadFont(&fabgl::FONT_8x14); + Terminal.setBackgroundColor(Color::Black); + Terminal.setForegroundColor(Color::White); + Terminal.connectLocally(); + Terminal.clear(); + Terminal.enableCursor(true); + + // Initialize SD card + sdSpi.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS); + if(!SD.begin(SD_CS, sdSpi, 80000000)) { + Terminal.println("Cannot \e[94mInitialize\e[97m SD card."); + + do sdSpi.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS); + while(!SD.begin(SD_CS, sdSpi, 80000000)); + + Terminal.clear(); + } + + // Initialize PSRAM + if(!psramInit()) { + Terminal.println("\e[94mCannot\e[97m initialize PSRAM."); + while(true); + } + + // Initialize the Rishka VM instance. + vm = new RishkaVM(); + vm->initialize(&Terminal); + + // Virtual key listener to halt program + Terminal.onVirtualKeyItem = [&](VirtualKeyItem * vkItem) { + if(vkItem->CTRL && vkItem->vk == VirtualKey::VK_c && vm->isRunning()) { + // Stop the VM if CTRL+C was pressed + vm->stopVM(); + vm->reset(); + + Terminal.println("^C"); + } + }; + + // Print prompt + Terminal.print("\e[32m[\e[97m" + vm->getWorkingDirectory() + "\e[97m\e[32m]~\e[97m "); +} + +void loop() { + // Read input from PS/2 keyboard + String input = readLine(); + + // Skip if input is empty + if(input == "") { + // Print prompt + Terminal.print(String("\e[32m[\e[97m") + + vm->getWorkingDirectory() + "\e[97m\e[32m]~\e[97m "); + return; + } + + char* tokens[10]; + int count = 0; + + // Split input string as argument tokens + for(int i = 0; i < 10; i++) + tokens[i] = (char*) malloc(50 * sizeof(char)); + splitString(input, tokens, 10, count); + + // Attempt to load specified file into Rishka virtual machine + if(!vm->loadFile(tokens[0])) { + char message[50]; + + // If loading file fails, print error message and return + sprintf(message, "Failed to \e[94mload\e[97m specified file: %s", tokens[0]); + vm->panic(message); + + // Print prompt + Terminal.print(String("\e[32m[\e[97m") + + vm->getWorkingDirectory() + "\e[97m\e[32m]~\e[97m "); + vm->reset(); + + return; + } + + // Run loaded program on Rishka virtual machine + vm->run(count, tokens); + // Reset Rishka virtual machine for next execution + vm->reset(); + + // Free all tokens in memory + for(int i = 0; i < 10; i++) + free(tokens[i]); + + // Print prompt for next input + Terminal.print(String("\e[32m[\e[97m") + + vm->getWorkingDirectory() + "\e[97m\e[32m]~\e[97m "); +}