├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── LICENSE-littlefs.md ├── README.md ├── cp2pico.sh ├── lfs.c ├── lfs.h ├── lfs_util.c ├── lfs_util.h ├── lfs_wrapper.c ├── lfs_wrapper.h ├── pbserialmon.py ├── piccoloBASIC.c ├── piccoloBASIC.h ├── pico_sdk_import.cmake ├── tokenizer.c ├── tokenizer.h ├── ubasic.c ├── ubasic.h └── vartype.h /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | old_stuff 3 | .vscode 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | set(CMAKE_C_STANDARD 11) 4 | set(CMAKE_CXX_STANDARD 17) 5 | set(CMAKE_BUILD_TYPE Debug) 6 | 7 | # Initialise pico_sdk from installed location 8 | # (note this can come from environment, CMake cache etc) 9 | set(PICO_SDK_PATH "/home/gary/pico/pico-sdk") 10 | 11 | set(PICO_BOARD pico CACHE STRING "Board type") 12 | 13 | # Pull in SDK (must be before project) 14 | include(pico_sdk_import.cmake) 15 | 16 | if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") 17 | message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") 18 | endif() 19 | 20 | project(piccoloBASIC C CXX ASM) 21 | 22 | # Initialize the SDK 23 | pico_sdk_init() 24 | 25 | if (TARGET tinyusb_device) 26 | add_executable(piccoloBASIC piccoloBASIC.c piccoloBASIC.h tokenizer.c tokenizer.h ubasic.c ubasic.h vartype.h lfs.c lfs.h lfs_util.c lfs_util.h lfs_wrapper.c) 27 | 28 | # pull in common dependencies 29 | target_link_libraries(piccoloBASIC pico_stdlib hardware_flash) 30 | 31 | # enable usb output, disable uart output 32 | pico_enable_stdio_usb(piccoloBASIC 1) 33 | pico_enable_stdio_uart(piccoloBASIC 0) 34 | 35 | # Add the standard include files to the build 36 | target_include_directories(piccoloBASIC PRIVATE 37 | ${CMAKE_CURRENT_LIST_DIR} 38 | ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required 39 | ) 40 | 41 | # create map/bin/hex/uf2 file etc. 42 | pico_add_extra_outputs(piccoloBASIC) 43 | 44 | elseif(PICO_ON_DEVICE) 45 | message(WARNING "not building hello_usb because TinyUSB submodule is not initialized in the SDK") 46 | endif() 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, Gary Explains 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /LICENSE-littlefs.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022, The littlefs authors. 2 | Copyright (c) 2017, Arm Limited. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | - Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | - Redistributions in binary form must reproduce the above copyright notice, this 10 | list of conditions and the following disclaimer in the documentation and/or 11 | other materials provided with the distribution. 12 | - Neither the name of ARM nor the names of its contributors may be used to 13 | endorse or promote products derived from this software without specific prior 14 | written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # piccoloBASIC 2 | A BASIC interpreter for the Raspberry Pi Pico (i.e. BASIC for microcontrollers) 3 | 4 | ## Videos 5 | I have lots of information about PiccoloBASIC in several videos on my YouTube channel [Gary Explains](https://youtube.com/c/GaryExplains) 6 | 7 | Watching these videos is the quickest way to understand what this project is about. 8 | 9 | - [Program the Raspberry Pi Pico using BASIC - Introducing PiccoloBASIC](https://youtu.be/oWyMGDDcykY) 10 | - [I wrote a BASIC interpreter! What should I do with it?](https://youtu.be/4MiT-29I_jI) 11 | - [My BASIC interpreter for the Raspberry Pi Pico is working!](https://youtube.com/shorts/7dQfG__jr_c) 12 | 13 | ## Press Coverage 14 | 15 | - [PiccoloBASIC – A BASIC interpreter for the Raspberry Pi Pico board](https://www.cnx-software.com/2023/06/23/piccolobasic-basic-interpreter-raspberry-pi-pico-board/) 16 | - [Raspberry Pi Pico Gets Basic Interpreter Called PiccoloBASIC](https://www.tomshardware.com/news/raspberry-pi-pico-basic-interpreter-piccolobasic) 17 | 18 | ## A Comma A Day Keeps The Pedants Away 19 | Since the Internet seems to be full of people with way too much time on their hands, I would just like to kindly shoo away any C/C++ pedants out there. To be honest, I am not interested. 20 | 21 | Having said that, like-minded people who wish to be part of the community, to contribute and to extend Piccolo BASIC are very welcome. See __Contributing__ 22 | 23 | ## Build Instructions 24 | Make sure you have the Pico C/C++ SDK installed and working on your machine. [Getting started with Raspberry Pi Pico is 25 | the best place to start.](https://datasheets.raspberrypi.org/pico/getting-started-with-pico.pdf) 26 | 27 | You need to have PICO_SDK_PATH defined, e.g. `export PICO_SDK_PATH=/home/pi/pico/pico-sdk/` 28 | 29 | Clone the code from the repository. Create a directory called `build`. Change directory into `build` and run `cmake ..` 30 | 31 | Run `make -j4` 32 | 33 | The resulting file `piccoloBASIC.uf2` can be flashed on your Pico in the normal way (i.e. reset will pressing `bootsel` and copy the .uf2 file to the drive). 34 | 35 | ## Releases 36 | If you don't want to build from the source code then look in [Releases](https://github.com/garyexplains/piccoloBASIC/releases) for some pre-built binaries. 37 | 38 | ## Usage 39 | 1. Flash `piccoloBASIC.uf2` on to your Pico 40 | 2. Use `pbserialmon.py` to upload your BASIC program. 41 | - It must be called `main.bas` 42 | - e.g. `./pbserialmon.py main.bas /dev/ttyACM0` 43 | 3. Use `pbserialmon.py` to see the output from your program 44 | - e.g. `./pbserialmon.py /dev/ttyACM0` 45 | 46 | ## Features 47 | - Let, if, print, for, goto, gosub 48 | - String variables (let z$="hello") 49 | - Floating point numbers and variables (let z#=1.234) 50 | - Builtin functions [zero, randint, not, time] 51 | - Sleep, delay, randomize, push & pop (for integers) 52 | - Maths functions like cos, sin, tan, sqr, etc 53 | - LittleFS support 54 | - Rudimentary GPIO support 55 | 56 | ## Examples 57 | Here are some example programs written in PiccoloBASIC. 58 | ### Hello, World! 59 | ``` 60 | loop: 61 | print "Gary Explains" 62 | sleep 1 63 | goto loop: 64 | ``` 65 | ### For loop 66 | ``` 67 | for i = 1 to 10 68 | let x = randint() 69 | print x 70 | next i 71 | end 72 | ``` 73 | ### Gosub 74 | ``` 75 | gosub asub: 76 | for i = 1 to 10 77 | print i 78 | next i 79 | print "end" 80 | end 81 | asub: 82 | print "subroutine" 83 | return 84 | ``` 85 | ### Blinky 86 | ``` 87 | pininit 25 88 | pindirout 25 89 | loop: 90 | print "ON" 91 | pinon 25 92 | sleep 1 93 | print "OFF" 94 | pinoff 25 95 | sleep 1 96 | goto loop: 97 | ``` 98 | ### 99 Bottles 99 | ``` 100 | let b = 99 101 | let b$ = "99" 102 | let s$ = " bottles" 103 | for a = 1 to 99 104 | print b$; s$; " of soda on the wall, "; b$; s$; " of soda." 105 | let b = b - 1 106 | if b > 0 then let p$ = "one" 107 | if b > 0 then let b$ = b else let b$ = "no more" 108 | if b = 0 then let p$ = "it" 109 | if b = 1 then let s$ = " bottle" else let s$ = " bottles" 110 | print "take "; p$; " down and pass it around, "; b$; s$; " of soda on the wall." 111 | next a 112 | print "no more bottles of soda on the wall, no more bottles of soda." 113 | print "go to the store and buy some more, 99 bottles of soda on the wall." 114 | ``` 115 | ## History 116 | The starting point for piccoloBASIC was "uBASIC: a really simple BASIC interpreter" by Adam Dunkels. 117 | 118 | - http://dunkels.com/adam/ubasic/ 119 | - https://github.com/adamdunkels/ubasic 120 | 121 | Here is what Adam said about the project: 122 | 123 | > Written in a couple of hours, for the fun of it. Ended up being used in a bunch of places! 124 | 125 | > The (non-interactive) uBASIC interpreter supports only the most basic BASIC functionality: if/then/else, for/next, let, goto, gosub, print, and mathematical expressions. There is only support for integer variables and the variables can only have single character names. I have added an API that allows for the program that uses the uBASIC interpreter to get and set BASIC variables, so it might be possible to actually use the uBASIC code for something useful (e.g. a small scripting language for an application that has to be really small). 126 | 127 | 128 | ### Modifications by Gary Sims 129 | Building on the excellent work of Adam Dunkels, I have tweaked this for my needs. 130 | 131 | - ubas - A program to run a .bas script 132 | - Removed need for linenumbers (but expectedly broke goto/gosub) 133 | - Added labels for goto and gosub 134 | - Added some simple error reporting 135 | - Added sleep and delay 136 | - Added floating point numbers and variables (let z#=1.234) 137 | - Added some builtin functions [zero, randint, not, time] 138 | - Added randomize 139 | - Added push and pop (for integers) 140 | - Added rnd, cos, sin, tan, sqr, etc 141 | - Added string variables (let z$="hello") 142 | - Added os, which calls system() 143 | - Ported to Raspberry Pi Pico 144 | - Added support for LittleFS 145 | - Added CMD mode 146 | - Added serial monitor and uploader tool 147 | - Added simple GPIO functionality 148 | 149 | ### Working on 150 | - Too much! 151 | ### BUGS 152 | - Many! 153 | - Floating point literals don't work in "if" statements: if b# < 20.9 then print "Boom" 154 | - printf is seen as print by the tokenizer as the first 5 letters are the same 155 | - There is probably a memory leak somewhere related to strings. 156 | 157 | ## LittleFS 158 | Arm developed a fail-safe filesystem for microcontrollers, it is called LittleFS: 159 | - Power-loss resilience - littlefs is designed to handle random power failures. 160 | - Dynamic wear leveling - littlefs is designed with flash in mind, and provides wear leveling over dynamic blocks. 161 | - Bounded RAM/ROM - littlefs is designed to work with a small amount of memory. RAM usage is strictly bounded, which means RAM consumption does not change as the filesystem grows. 162 | - Open source - under the BSD-3-Clause license. 163 | - Used by MicroPython / CircuitPython already 164 | 165 | See https://github.com/littlefs-project/littlefs 166 | 167 | ### LittleFS license 168 | 3-Clause BSD License 169 | Copyright (c) 2022, The littlefs authors. 170 | Copyright (c) 2017, Arm Limited. All rights reserved. 171 | 172 | ## Flash layout 173 | Raspberry Pi Pico has 2MB of Flash. 174 | - Total flash 2048K 175 | - First 640K is for firmware 176 | - i.e. PiccoloBASIC or MicroPython 177 | - Rest is for LittleFS 178 | - BASIC programs, Python scripts etc 179 | - This way PiccoloBASIC is compatible with MicroPython 180 | 181 | ``` 182 | 0K ------------------------------------- 183 | | | 184 | | PiccoloBASIC firmware | 185 | | | 186 | 640K ------------------------------------- 187 | | | 188 | | LittelFS | 189 | | . | 190 | | . | 191 | | . | 192 | | main.bas | 193 | | | 194 | | | 195 | 2048K ------------------------------------- 196 | ``` 197 | 198 | ## CMD Mode 199 | To upload BASIC scripts and store them in LittleFS, there is a CMD mode in PiccoloBASIC. Sending `CTRL-C` twice one after the other will pause the interpreter and enter CMD mode: 200 | - ls 201 | - cd 202 | - rm 203 | - reboot 204 | - exit 205 | - upload 206 | 207 | See `pbserialmon.py` for details on the protocol for the upload command 208 | 209 | ## Roadmap 210 | ### More language features 211 | - Peek and poke 212 | - Longer variable names (currently just one letter!) 213 | - Negative numbers + 64 bit numbers + hex numbers 214 | - Better loops (steps, reverse, while etc) 215 | - File IO 216 | ### More hardware support 217 | - Complete GPIO, I2C, SPI, PIO, etc 218 | - Networking and Bluetooth 219 | - Support some standard displays 220 | - USB keyboard 221 | ### PiccoloOS 222 | Build PiccoloBASIC on top of [PiccoloOS](https://github.com/garyexplains/piccolo_os_v1.1) 223 | - Multi-tasking BASIC 👀 224 | - Locks and synchronization 225 | - Dual CPU support 226 | 227 | ## Contributing 228 | There is lots to do! Please feel free to fork and/or continue working on Piccolo BASIC as you see fit. 229 | 230 | ## Resources 231 | - [Gary Explains](https://youtube.com/c/GaryExplains) 232 | - [PiccoloOS](https://github.com/garyexplains/piccolo_os_v1.1) 233 | - [LittleFS](https://github.com/littlefs-project/littlefs) 234 | - [uBasic](https://github.com/adamdunkels/ubasic) 235 | 236 | ## License - 3-Clause BSD License 237 | Copyright (C) 2006, Adam Dunkels 238 | Copyright (C) 2023, Gary Sims 239 | All rights reserved. 240 | 241 | SPDX short identifier: BSD-3-Clause 242 | -------------------------------------------------------------------------------- /cp2pico.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | sudo mkdir -p /mnt/pico 3 | sudo mount /dev/sda1 /mnt/pico 4 | sudo cp build/piccoloBASIC.uf2 /mnt/pico 5 | sudo sync 6 | sudo umount /mnt/pico -------------------------------------------------------------------------------- /lfs.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The little filesystem 3 | * 4 | * Copyright (c) 2022, The littlefs authors. 5 | * Copyright (c) 2017, Arm Limited. All rights reserved. 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | */ 8 | #ifndef LFS_H 9 | #define LFS_H 10 | 11 | #include "lfs_util.h" 12 | 13 | #ifdef __cplusplus 14 | extern "C" 15 | { 16 | #endif 17 | 18 | 19 | /// Version info /// 20 | 21 | // Software library version 22 | // Major (top-nibble), incremented on backwards incompatible changes 23 | // Minor (bottom-nibble), incremented on feature additions 24 | #define LFS_VERSION 0x00020006 25 | #define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16)) 26 | #define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0)) 27 | 28 | // Version of On-disk data structures 29 | // Major (top-nibble), incremented on backwards incompatible changes 30 | // Minor (bottom-nibble), incremented on feature additions 31 | #define LFS_DISK_VERSION 0x00020001 32 | #define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16)) 33 | #define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >> 0)) 34 | 35 | 36 | /// Definitions /// 37 | 38 | // Type definitions 39 | typedef uint32_t lfs_size_t; 40 | typedef uint32_t lfs_off_t; 41 | 42 | typedef int32_t lfs_ssize_t; 43 | typedef int32_t lfs_soff_t; 44 | 45 | typedef uint32_t lfs_block_t; 46 | 47 | // Maximum name size in bytes, may be redefined to reduce the size of the 48 | // info struct. Limited to <= 1022. Stored in superblock and must be 49 | // respected by other littlefs drivers. 50 | #ifndef LFS_NAME_MAX 51 | #define LFS_NAME_MAX 255 52 | #endif 53 | 54 | // Maximum size of a file in bytes, may be redefined to limit to support other 55 | // drivers. Limited on disk to <= 4294967296. However, above 2147483647 the 56 | // functions lfs_file_seek, lfs_file_size, and lfs_file_tell will return 57 | // incorrect values due to using signed integers. Stored in superblock and 58 | // must be respected by other littlefs drivers. 59 | #ifndef LFS_FILE_MAX 60 | #define LFS_FILE_MAX 2147483647 61 | #endif 62 | 63 | // Maximum size of custom attributes in bytes, may be redefined, but there is 64 | // no real benefit to using a smaller LFS_ATTR_MAX. Limited to <= 1022. 65 | #ifndef LFS_ATTR_MAX 66 | #define LFS_ATTR_MAX 1022 67 | #endif 68 | 69 | // Possible error codes, these are negative to allow 70 | // valid positive return values 71 | enum lfs_error { 72 | LFS_ERR_OK = 0, // No error 73 | LFS_ERR_IO = -5, // Error during device operation 74 | LFS_ERR_CORRUPT = -84, // Corrupted 75 | LFS_ERR_NOENT = -2, // No directory entry 76 | LFS_ERR_EXIST = -17, // Entry already exists 77 | LFS_ERR_NOTDIR = -20, // Entry is not a dir 78 | LFS_ERR_ISDIR = -21, // Entry is a dir 79 | LFS_ERR_NOTEMPTY = -39, // Dir is not empty 80 | LFS_ERR_BADF = -9, // Bad file number 81 | LFS_ERR_FBIG = -27, // File too large 82 | LFS_ERR_INVAL = -22, // Invalid parameter 83 | LFS_ERR_NOSPC = -28, // No space left on device 84 | LFS_ERR_NOMEM = -12, // No more memory available 85 | LFS_ERR_NOATTR = -61, // No data/attr available 86 | LFS_ERR_NAMETOOLONG = -36, // File name too long 87 | }; 88 | 89 | // File types 90 | enum lfs_type { 91 | // file types 92 | LFS_TYPE_REG = 0x001, 93 | LFS_TYPE_DIR = 0x002, 94 | 95 | // internally used types 96 | LFS_TYPE_SPLICE = 0x400, 97 | LFS_TYPE_NAME = 0x000, 98 | LFS_TYPE_STRUCT = 0x200, 99 | LFS_TYPE_USERATTR = 0x300, 100 | LFS_TYPE_FROM = 0x100, 101 | LFS_TYPE_TAIL = 0x600, 102 | LFS_TYPE_GLOBALS = 0x700, 103 | LFS_TYPE_CRC = 0x500, 104 | 105 | // internally used type specializations 106 | LFS_TYPE_CREATE = 0x401, 107 | LFS_TYPE_DELETE = 0x4ff, 108 | LFS_TYPE_SUPERBLOCK = 0x0ff, 109 | LFS_TYPE_DIRSTRUCT = 0x200, 110 | LFS_TYPE_CTZSTRUCT = 0x202, 111 | LFS_TYPE_INLINESTRUCT = 0x201, 112 | LFS_TYPE_SOFTTAIL = 0x600, 113 | LFS_TYPE_HARDTAIL = 0x601, 114 | LFS_TYPE_MOVESTATE = 0x7ff, 115 | LFS_TYPE_CCRC = 0x500, 116 | LFS_TYPE_FCRC = 0x5ff, 117 | 118 | // internal chip sources 119 | LFS_FROM_NOOP = 0x000, 120 | LFS_FROM_MOVE = 0x101, 121 | LFS_FROM_USERATTRS = 0x102, 122 | }; 123 | 124 | // File open flags 125 | enum lfs_open_flags { 126 | // open flags 127 | LFS_O_RDONLY = 1, // Open a file as read only 128 | #ifndef LFS_READONLY 129 | LFS_O_WRONLY = 2, // Open a file as write only 130 | LFS_O_RDWR = 3, // Open a file as read and write 131 | LFS_O_CREAT = 0x0100, // Create a file if it does not exist 132 | LFS_O_EXCL = 0x0200, // Fail if a file already exists 133 | LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size 134 | LFS_O_APPEND = 0x0800, // Move to end of file on every write 135 | #endif 136 | 137 | // internally used flags 138 | #ifndef LFS_READONLY 139 | LFS_F_DIRTY = 0x010000, // File does not match storage 140 | LFS_F_WRITING = 0x020000, // File has been written since last flush 141 | #endif 142 | LFS_F_READING = 0x040000, // File has been read since last flush 143 | #ifndef LFS_READONLY 144 | LFS_F_ERRED = 0x080000, // An error occurred during write 145 | #endif 146 | LFS_F_INLINE = 0x100000, // Currently inlined in directory entry 147 | }; 148 | 149 | // File seek flags 150 | enum lfs_whence_flags { 151 | LFS_SEEK_SET = 0, // Seek relative to an absolute position 152 | LFS_SEEK_CUR = 1, // Seek relative to the current file position 153 | LFS_SEEK_END = 2, // Seek relative to the end of the file 154 | }; 155 | 156 | 157 | // Configuration provided during initialization of the littlefs 158 | struct lfs_config { 159 | // Opaque user provided context that can be used to pass 160 | // information to the block device operations 161 | void *context; 162 | 163 | // Read a region in a block. Negative error codes are propagated 164 | // to the user. 165 | int (*read)(const struct lfs_config *c, lfs_block_t block, 166 | lfs_off_t off, void *buffer, lfs_size_t size); 167 | 168 | // Program a region in a block. The block must have previously 169 | // been erased. Negative error codes are propagated to the user. 170 | // May return LFS_ERR_CORRUPT if the block should be considered bad. 171 | int (*prog)(const struct lfs_config *c, lfs_block_t block, 172 | lfs_off_t off, const void *buffer, lfs_size_t size); 173 | 174 | // Erase a block. A block must be erased before being programmed. 175 | // The state of an erased block is undefined. Negative error codes 176 | // are propagated to the user. 177 | // May return LFS_ERR_CORRUPT if the block should be considered bad. 178 | int (*erase)(const struct lfs_config *c, lfs_block_t block); 179 | 180 | // Sync the state of the underlying block device. Negative error codes 181 | // are propagated to the user. 182 | int (*sync)(const struct lfs_config *c); 183 | 184 | #ifdef LFS_THREADSAFE 185 | // Lock the underlying block device. Negative error codes 186 | // are propagated to the user. 187 | int (*lock)(const struct lfs_config *c); 188 | 189 | // Unlock the underlying block device. Negative error codes 190 | // are propagated to the user. 191 | int (*unlock)(const struct lfs_config *c); 192 | #endif 193 | 194 | // Minimum size of a block read in bytes. All read operations will be a 195 | // multiple of this value. 196 | lfs_size_t read_size; 197 | 198 | // Minimum size of a block program in bytes. All program operations will be 199 | // a multiple of this value. 200 | lfs_size_t prog_size; 201 | 202 | // Size of an erasable block in bytes. This does not impact ram consumption 203 | // and may be larger than the physical erase size. However, non-inlined 204 | // files take up at minimum one block. Must be a multiple of the read and 205 | // program sizes. 206 | lfs_size_t block_size; 207 | 208 | // Number of erasable blocks on the device. 209 | lfs_size_t block_count; 210 | 211 | // Number of erase cycles before littlefs evicts metadata logs and moves 212 | // the metadata to another block. Suggested values are in the 213 | // range 100-1000, with large values having better performance at the cost 214 | // of less consistent wear distribution. 215 | // 216 | // Set to -1 to disable block-level wear-leveling. 217 | int32_t block_cycles; 218 | 219 | // Size of block caches in bytes. Each cache buffers a portion of a block in 220 | // RAM. The littlefs needs a read cache, a program cache, and one additional 221 | // cache per file. Larger caches can improve performance by storing more 222 | // data and reducing the number of disk accesses. Must be a multiple of the 223 | // read and program sizes, and a factor of the block size. 224 | lfs_size_t cache_size; 225 | 226 | // Size of the lookahead buffer in bytes. A larger lookahead buffer 227 | // increases the number of blocks found during an allocation pass. The 228 | // lookahead buffer is stored as a compact bitmap, so each byte of RAM 229 | // can track 8 blocks. Must be a multiple of 8. 230 | lfs_size_t lookahead_size; 231 | 232 | // Optional statically allocated read buffer. Must be cache_size. 233 | // By default lfs_malloc is used to allocate this buffer. 234 | void *read_buffer; 235 | 236 | // Optional statically allocated program buffer. Must be cache_size. 237 | // By default lfs_malloc is used to allocate this buffer. 238 | void *prog_buffer; 239 | 240 | // Optional statically allocated lookahead buffer. Must be lookahead_size 241 | // and aligned to a 32-bit boundary. By default lfs_malloc is used to 242 | // allocate this buffer. 243 | void *lookahead_buffer; 244 | 245 | // Optional upper limit on length of file names in bytes. No downside for 246 | // larger names except the size of the info struct which is controlled by 247 | // the LFS_NAME_MAX define. Defaults to LFS_NAME_MAX when zero. Stored in 248 | // superblock and must be respected by other littlefs drivers. 249 | lfs_size_t name_max; 250 | 251 | // Optional upper limit on files in bytes. No downside for larger files 252 | // but must be <= LFS_FILE_MAX. Defaults to LFS_FILE_MAX when zero. Stored 253 | // in superblock and must be respected by other littlefs drivers. 254 | lfs_size_t file_max; 255 | 256 | // Optional upper limit on custom attributes in bytes. No downside for 257 | // larger attributes size but must be <= LFS_ATTR_MAX. Defaults to 258 | // LFS_ATTR_MAX when zero. 259 | lfs_size_t attr_max; 260 | 261 | // Optional upper limit on total space given to metadata pairs in bytes. On 262 | // devices with large blocks (e.g. 128kB) setting this to a low size (2-8kB) 263 | // can help bound the metadata compaction time. Must be <= block_size. 264 | // Defaults to block_size when zero. 265 | lfs_size_t metadata_max; 266 | }; 267 | 268 | // File info structure 269 | struct lfs_info { 270 | // Type of the file, either LFS_TYPE_REG or LFS_TYPE_DIR 271 | uint8_t type; 272 | 273 | // Size of the file, only valid for REG files. Limited to 32-bits. 274 | lfs_size_t size; 275 | 276 | // Name of the file stored as a null-terminated string. Limited to 277 | // LFS_NAME_MAX+1, which can be changed by redefining LFS_NAME_MAX to 278 | // reduce RAM. LFS_NAME_MAX is stored in superblock and must be 279 | // respected by other littlefs drivers. 280 | char name[LFS_NAME_MAX+1]; 281 | }; 282 | 283 | // Custom attribute structure, used to describe custom attributes 284 | // committed atomically during file writes. 285 | struct lfs_attr { 286 | // 8-bit type of attribute, provided by user and used to 287 | // identify the attribute 288 | uint8_t type; 289 | 290 | // Pointer to buffer containing the attribute 291 | void *buffer; 292 | 293 | // Size of attribute in bytes, limited to LFS_ATTR_MAX 294 | lfs_size_t size; 295 | }; 296 | 297 | // Optional configuration provided during lfs_file_opencfg 298 | struct lfs_file_config { 299 | // Optional statically allocated file buffer. Must be cache_size. 300 | // By default lfs_malloc is used to allocate this buffer. 301 | void *buffer; 302 | 303 | // Optional list of custom attributes related to the file. If the file 304 | // is opened with read access, these attributes will be read from disk 305 | // during the open call. If the file is opened with write access, the 306 | // attributes will be written to disk every file sync or close. This 307 | // write occurs atomically with update to the file's contents. 308 | // 309 | // Custom attributes are uniquely identified by an 8-bit type and limited 310 | // to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller 311 | // than the buffer, it will be padded with zeros. If the stored attribute 312 | // is larger, then it will be silently truncated. If the attribute is not 313 | // found, it will be created implicitly. 314 | struct lfs_attr *attrs; 315 | 316 | // Number of custom attributes in the list 317 | lfs_size_t attr_count; 318 | }; 319 | 320 | 321 | /// internal littlefs data structures /// 322 | typedef struct lfs_cache { 323 | lfs_block_t block; 324 | lfs_off_t off; 325 | lfs_size_t size; 326 | uint8_t *buffer; 327 | } lfs_cache_t; 328 | 329 | typedef struct lfs_mdir { 330 | lfs_block_t pair[2]; 331 | uint32_t rev; 332 | lfs_off_t off; 333 | uint32_t etag; 334 | uint16_t count; 335 | bool erased; 336 | bool split; 337 | lfs_block_t tail[2]; 338 | } lfs_mdir_t; 339 | 340 | // littlefs directory type 341 | typedef struct lfs_dir { 342 | struct lfs_dir *next; 343 | uint16_t id; 344 | uint8_t type; 345 | lfs_mdir_t m; 346 | 347 | lfs_off_t pos; 348 | lfs_block_t head[2]; 349 | } lfs_dir_t; 350 | 351 | // littlefs file type 352 | typedef struct lfs_file { 353 | struct lfs_file *next; 354 | uint16_t id; 355 | uint8_t type; 356 | lfs_mdir_t m; 357 | 358 | struct lfs_ctz { 359 | lfs_block_t head; 360 | lfs_size_t size; 361 | } ctz; 362 | 363 | uint32_t flags; 364 | lfs_off_t pos; 365 | lfs_block_t block; 366 | lfs_off_t off; 367 | lfs_cache_t cache; 368 | 369 | const struct lfs_file_config *cfg; 370 | } lfs_file_t; 371 | 372 | typedef struct lfs_superblock { 373 | uint32_t version; 374 | lfs_size_t block_size; 375 | lfs_size_t block_count; 376 | lfs_size_t name_max; 377 | lfs_size_t file_max; 378 | lfs_size_t attr_max; 379 | } lfs_superblock_t; 380 | 381 | typedef struct lfs_gstate { 382 | uint32_t tag; 383 | lfs_block_t pair[2]; 384 | } lfs_gstate_t; 385 | 386 | // The littlefs filesystem type 387 | typedef struct lfs { 388 | lfs_cache_t rcache; 389 | lfs_cache_t pcache; 390 | 391 | lfs_block_t root[2]; 392 | struct lfs_mlist { 393 | struct lfs_mlist *next; 394 | uint16_t id; 395 | uint8_t type; 396 | lfs_mdir_t m; 397 | } *mlist; 398 | uint32_t seed; 399 | 400 | lfs_gstate_t gstate; 401 | lfs_gstate_t gdisk; 402 | lfs_gstate_t gdelta; 403 | 404 | struct lfs_free { 405 | lfs_block_t off; 406 | lfs_block_t size; 407 | lfs_block_t i; 408 | lfs_block_t ack; 409 | uint32_t *buffer; 410 | } free; 411 | 412 | const struct lfs_config *cfg; 413 | lfs_size_t name_max; 414 | lfs_size_t file_max; 415 | lfs_size_t attr_max; 416 | 417 | #ifdef LFS_MIGRATE 418 | struct lfs1 *lfs1; 419 | #endif 420 | } lfs_t; 421 | 422 | 423 | /// Filesystem functions /// 424 | 425 | #ifndef LFS_READONLY 426 | // Format a block device with the littlefs 427 | // 428 | // Requires a littlefs object and config struct. This clobbers the littlefs 429 | // object, and does not leave the filesystem mounted. The config struct must 430 | // be zeroed for defaults and backwards compatibility. 431 | // 432 | // Returns a negative error code on failure. 433 | int lfs_format(lfs_t *lfs, const struct lfs_config *config); 434 | #endif 435 | 436 | // Mounts a littlefs 437 | // 438 | // Requires a littlefs object and config struct. Multiple filesystems 439 | // may be mounted simultaneously with multiple littlefs objects. Both 440 | // lfs and config must be allocated while mounted. The config struct must 441 | // be zeroed for defaults and backwards compatibility. 442 | // 443 | // Returns a negative error code on failure. 444 | int lfs_mount(lfs_t *lfs, const struct lfs_config *config); 445 | 446 | // Unmounts a littlefs 447 | // 448 | // Does nothing besides releasing any allocated resources. 449 | // Returns a negative error code on failure. 450 | int lfs_unmount(lfs_t *lfs); 451 | 452 | /// General operations /// 453 | 454 | #ifndef LFS_READONLY 455 | // Removes a file or directory 456 | // 457 | // If removing a directory, the directory must be empty. 458 | // Returns a negative error code on failure. 459 | int lfs_remove(lfs_t *lfs, const char *path); 460 | #endif 461 | 462 | #ifndef LFS_READONLY 463 | // Rename or move a file or directory 464 | // 465 | // If the destination exists, it must match the source in type. 466 | // If the destination is a directory, the directory must be empty. 467 | // 468 | // Returns a negative error code on failure. 469 | int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath); 470 | #endif 471 | 472 | // Find info about a file or directory 473 | // 474 | // Fills out the info structure, based on the specified file or directory. 475 | // Returns a negative error code on failure. 476 | int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info); 477 | 478 | // Get a custom attribute 479 | // 480 | // Custom attributes are uniquely identified by an 8-bit type and limited 481 | // to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller than 482 | // the buffer, it will be padded with zeros. If the stored attribute is larger, 483 | // then it will be silently truncated. If no attribute is found, the error 484 | // LFS_ERR_NOATTR is returned and the buffer is filled with zeros. 485 | // 486 | // Returns the size of the attribute, or a negative error code on failure. 487 | // Note, the returned size is the size of the attribute on disk, irrespective 488 | // of the size of the buffer. This can be used to dynamically allocate a buffer 489 | // or check for existence. 490 | lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path, 491 | uint8_t type, void *buffer, lfs_size_t size); 492 | 493 | #ifndef LFS_READONLY 494 | // Set custom attributes 495 | // 496 | // Custom attributes are uniquely identified by an 8-bit type and limited 497 | // to LFS_ATTR_MAX bytes. If an attribute is not found, it will be 498 | // implicitly created. 499 | // 500 | // Returns a negative error code on failure. 501 | int lfs_setattr(lfs_t *lfs, const char *path, 502 | uint8_t type, const void *buffer, lfs_size_t size); 503 | #endif 504 | 505 | #ifndef LFS_READONLY 506 | // Removes a custom attribute 507 | // 508 | // If an attribute is not found, nothing happens. 509 | // 510 | // Returns a negative error code on failure. 511 | int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type); 512 | #endif 513 | 514 | 515 | /// File operations /// 516 | 517 | #ifndef LFS_NO_MALLOC 518 | // Open a file 519 | // 520 | // The mode that the file is opened in is determined by the flags, which 521 | // are values from the enum lfs_open_flags that are bitwise-ored together. 522 | // 523 | // Returns a negative error code on failure. 524 | int lfs_file_open(lfs_t *lfs, lfs_file_t *file, 525 | const char *path, int flags); 526 | 527 | // if LFS_NO_MALLOC is defined, lfs_file_open() will fail with LFS_ERR_NOMEM 528 | // thus use lfs_file_opencfg() with config.buffer set. 529 | #endif 530 | 531 | // Open a file with extra configuration 532 | // 533 | // The mode that the file is opened in is determined by the flags, which 534 | // are values from the enum lfs_open_flags that are bitwise-ored together. 535 | // 536 | // The config struct provides additional config options per file as described 537 | // above. The config struct must remain allocated while the file is open, and 538 | // the config struct must be zeroed for defaults and backwards compatibility. 539 | // 540 | // Returns a negative error code on failure. 541 | int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, 542 | const char *path, int flags, 543 | const struct lfs_file_config *config); 544 | 545 | // Close a file 546 | // 547 | // Any pending writes are written out to storage as though 548 | // sync had been called and releases any allocated resources. 549 | // 550 | // Returns a negative error code on failure. 551 | int lfs_file_close(lfs_t *lfs, lfs_file_t *file); 552 | 553 | // Synchronize a file on storage 554 | // 555 | // Any pending writes are written out to storage. 556 | // Returns a negative error code on failure. 557 | int lfs_file_sync(lfs_t *lfs, lfs_file_t *file); 558 | 559 | // Read data from file 560 | // 561 | // Takes a buffer and size indicating where to store the read data. 562 | // Returns the number of bytes read, or a negative error code on failure. 563 | lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, 564 | void *buffer, lfs_size_t size); 565 | 566 | #ifndef LFS_READONLY 567 | // Write data to file 568 | // 569 | // Takes a buffer and size indicating the data to write. The file will not 570 | // actually be updated on the storage until either sync or close is called. 571 | // 572 | // Returns the number of bytes written, or a negative error code on failure. 573 | lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, 574 | const void *buffer, lfs_size_t size); 575 | #endif 576 | 577 | // Change the position of the file 578 | // 579 | // The change in position is determined by the offset and whence flag. 580 | // Returns the new position of the file, or a negative error code on failure. 581 | lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, 582 | lfs_soff_t off, int whence); 583 | 584 | #ifndef LFS_READONLY 585 | // Truncates the size of the file to the specified size 586 | // 587 | // Returns a negative error code on failure. 588 | int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size); 589 | #endif 590 | 591 | // Return the position of the file 592 | // 593 | // Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR) 594 | // Returns the position of the file, or a negative error code on failure. 595 | lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file); 596 | 597 | // Change the position of the file to the beginning of the file 598 | // 599 | // Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_SET) 600 | // Returns a negative error code on failure. 601 | int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file); 602 | 603 | // Return the size of the file 604 | // 605 | // Similar to lfs_file_seek(lfs, file, 0, LFS_SEEK_END) 606 | // Returns the size of the file, or a negative error code on failure. 607 | lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file); 608 | 609 | 610 | /// Directory operations /// 611 | 612 | #ifndef LFS_READONLY 613 | // Create a directory 614 | // 615 | // Returns a negative error code on failure. 616 | int lfs_mkdir(lfs_t *lfs, const char *path); 617 | #endif 618 | 619 | // Open a directory 620 | // 621 | // Once open a directory can be used with read to iterate over files. 622 | // Returns a negative error code on failure. 623 | int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path); 624 | 625 | // Close a directory 626 | // 627 | // Releases any allocated resources. 628 | // Returns a negative error code on failure. 629 | int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir); 630 | 631 | // Read an entry in the directory 632 | // 633 | // Fills out the info structure, based on the specified file or directory. 634 | // Returns a positive value on success, 0 at the end of directory, 635 | // or a negative error code on failure. 636 | int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info); 637 | 638 | // Change the position of the directory 639 | // 640 | // The new off must be a value previous returned from tell and specifies 641 | // an absolute offset in the directory seek. 642 | // 643 | // Returns a negative error code on failure. 644 | int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off); 645 | 646 | // Return the position of the directory 647 | // 648 | // The returned offset is only meant to be consumed by seek and may not make 649 | // sense, but does indicate the current position in the directory iteration. 650 | // 651 | // Returns the position of the directory, or a negative error code on failure. 652 | lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir); 653 | 654 | // Change the position of the directory to the beginning of the directory 655 | // 656 | // Returns a negative error code on failure. 657 | int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir); 658 | 659 | 660 | /// Filesystem-level filesystem operations 661 | 662 | // Finds the current size of the filesystem 663 | // 664 | // Note: Result is best effort. If files share COW structures, the returned 665 | // size may be larger than the filesystem actually is. 666 | // 667 | // Returns the number of allocated blocks, or a negative error code on failure. 668 | lfs_ssize_t lfs_fs_size(lfs_t *lfs); 669 | 670 | // Traverse through all blocks in use by the filesystem 671 | // 672 | // The provided callback will be called with each block address that is 673 | // currently in use by the filesystem. This can be used to determine which 674 | // blocks are in use or how much of the storage is available. 675 | // 676 | // Returns a negative error code on failure. 677 | int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); 678 | 679 | #ifndef LFS_READONLY 680 | // Attempt to make the filesystem consistent and ready for writing 681 | // 682 | // Calling this function is not required, consistency will be implicitly 683 | // enforced on the first operation that writes to the filesystem, but this 684 | // function allows the work to be performed earlier and without other 685 | // filesystem changes. 686 | // 687 | // Returns a negative error code on failure. 688 | int lfs_fs_mkconsistent(lfs_t *lfs); 689 | #endif 690 | 691 | #ifndef LFS_READONLY 692 | #ifdef LFS_MIGRATE 693 | // Attempts to migrate a previous version of littlefs 694 | // 695 | // Behaves similarly to the lfs_format function. Attempts to mount 696 | // the previous version of littlefs and update the filesystem so it can be 697 | // mounted with the current version of littlefs. 698 | // 699 | // Requires a littlefs object and config struct. This clobbers the littlefs 700 | // object, and does not leave the filesystem mounted. The config struct must 701 | // be zeroed for defaults and backwards compatibility. 702 | // 703 | // Returns a negative error code on failure. 704 | int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg); 705 | #endif 706 | #endif 707 | 708 | 709 | #ifdef __cplusplus 710 | } /* extern "C" */ 711 | #endif 712 | 713 | #endif 714 | -------------------------------------------------------------------------------- /lfs_util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lfs util functions 3 | * 4 | * Copyright (c) 2022, The littlefs authors. 5 | * Copyright (c) 2017, Arm Limited. All rights reserved. 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | */ 8 | #include "lfs_util.h" 9 | 10 | // Only compile if user does not provide custom config 11 | #ifndef LFS_CONFIG 12 | 13 | 14 | // Software CRC implementation with small lookup table 15 | uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) { 16 | static const uint32_t rtable[16] = { 17 | 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 18 | 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 19 | 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 20 | 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c, 21 | }; 22 | 23 | const uint8_t *data = buffer; 24 | 25 | for (size_t i = 0; i < size; i++) { 26 | crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 0)) & 0xf]; 27 | crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 4)) & 0xf]; 28 | } 29 | 30 | return crc; 31 | } 32 | 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /lfs_util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lfs utility functions 3 | * 4 | * Copyright (c) 2022, The littlefs authors. 5 | * Copyright (c) 2017, Arm Limited. All rights reserved. 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | */ 8 | #ifndef LFS_UTIL_H 9 | #define LFS_UTIL_H 10 | 11 | // Users can override lfs_util.h with their own configuration by defining 12 | // LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h). 13 | // 14 | // If LFS_CONFIG is used, none of the default utils will be emitted and must be 15 | // provided by the config file. To start, I would suggest copying lfs_util.h 16 | // and modifying as needed. 17 | #ifdef LFS_CONFIG 18 | #define LFS_STRINGIZE(x) LFS_STRINGIZE2(x) 19 | #define LFS_STRINGIZE2(x) #x 20 | #include LFS_STRINGIZE(LFS_CONFIG) 21 | #else 22 | 23 | // System includes 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #ifndef LFS_NO_MALLOC 30 | #include 31 | #endif 32 | #ifndef LFS_NO_ASSERT 33 | #include 34 | #endif 35 | #if !defined(LFS_NO_DEBUG) || \ 36 | !defined(LFS_NO_WARN) || \ 37 | !defined(LFS_NO_ERROR) || \ 38 | defined(LFS_YES_TRACE) 39 | #include 40 | #endif 41 | 42 | #ifdef __cplusplus 43 | extern "C" 44 | { 45 | #endif 46 | 47 | 48 | // Macros, may be replaced by system specific wrappers. Arguments to these 49 | // macros must not have side-effects as the macros can be removed for a smaller 50 | // code footprint 51 | 52 | // Logging functions 53 | #ifndef LFS_TRACE 54 | #ifdef LFS_YES_TRACE 55 | #define LFS_TRACE_(fmt, ...) \ 56 | printf("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 57 | #define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "") 58 | #else 59 | #define LFS_TRACE(...) 60 | #endif 61 | #endif 62 | 63 | #ifndef LFS_DEBUG 64 | #ifndef LFS_NO_DEBUG 65 | #define LFS_DEBUG_(fmt, ...) \ 66 | printf("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 67 | #define LFS_DEBUG(...) LFS_DEBUG_(__VA_ARGS__, "") 68 | #else 69 | #define LFS_DEBUG(...) 70 | #endif 71 | #endif 72 | 73 | #ifndef LFS_WARN 74 | #ifndef LFS_NO_WARN 75 | #define LFS_WARN_(fmt, ...) \ 76 | printf("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 77 | #define LFS_WARN(...) LFS_WARN_(__VA_ARGS__, "") 78 | #else 79 | #define LFS_WARN(...) 80 | #endif 81 | #endif 82 | 83 | #ifndef LFS_ERROR 84 | #ifndef LFS_NO_ERROR 85 | #define LFS_ERROR_(fmt, ...) \ 86 | printf("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 87 | #define LFS_ERROR(...) LFS_ERROR_(__VA_ARGS__, "") 88 | #else 89 | #define LFS_ERROR(...) 90 | #endif 91 | #endif 92 | 93 | // Runtime assertions 94 | #ifndef LFS_ASSERT 95 | #ifndef LFS_NO_ASSERT 96 | #define LFS_ASSERT(test) assert(test) 97 | #else 98 | #define LFS_ASSERT(test) 99 | #endif 100 | #endif 101 | 102 | 103 | // Builtin functions, these may be replaced by more efficient 104 | // toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more 105 | // expensive basic C implementation for debugging purposes 106 | 107 | // Min/max functions for unsigned 32-bit numbers 108 | static inline uint32_t lfs_max(uint32_t a, uint32_t b) { 109 | return (a > b) ? a : b; 110 | } 111 | 112 | static inline uint32_t lfs_min(uint32_t a, uint32_t b) { 113 | return (a < b) ? a : b; 114 | } 115 | 116 | // Align to nearest multiple of a size 117 | static inline uint32_t lfs_aligndown(uint32_t a, uint32_t alignment) { 118 | return a - (a % alignment); 119 | } 120 | 121 | static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) { 122 | return lfs_aligndown(a + alignment-1, alignment); 123 | } 124 | 125 | // Find the smallest power of 2 greater than or equal to a 126 | static inline uint32_t lfs_npw2(uint32_t a) { 127 | #if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) 128 | return 32 - __builtin_clz(a-1); 129 | #else 130 | uint32_t r = 0; 131 | uint32_t s; 132 | a -= 1; 133 | s = (a > 0xffff) << 4; a >>= s; r |= s; 134 | s = (a > 0xff ) << 3; a >>= s; r |= s; 135 | s = (a > 0xf ) << 2; a >>= s; r |= s; 136 | s = (a > 0x3 ) << 1; a >>= s; r |= s; 137 | return (r | (a >> 1)) + 1; 138 | #endif 139 | } 140 | 141 | // Count the number of trailing binary zeros in a 142 | // lfs_ctz(0) may be undefined 143 | static inline uint32_t lfs_ctz(uint32_t a) { 144 | #if !defined(LFS_NO_INTRINSICS) && defined(__GNUC__) 145 | return __builtin_ctz(a); 146 | #else 147 | return lfs_npw2((a & -a) + 1) - 1; 148 | #endif 149 | } 150 | 151 | // Count the number of binary ones in a 152 | static inline uint32_t lfs_popc(uint32_t a) { 153 | #if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) 154 | return __builtin_popcount(a); 155 | #else 156 | a = a - ((a >> 1) & 0x55555555); 157 | a = (a & 0x33333333) + ((a >> 2) & 0x33333333); 158 | return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24; 159 | #endif 160 | } 161 | 162 | // Find the sequence comparison of a and b, this is the distance 163 | // between a and b ignoring overflow 164 | static inline int lfs_scmp(uint32_t a, uint32_t b) { 165 | return (int)(unsigned)(a - b); 166 | } 167 | 168 | // Convert between 32-bit little-endian and native order 169 | static inline uint32_t lfs_fromle32(uint32_t a) { 170 | #if (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ 171 | (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ 172 | (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) 173 | return a; 174 | #elif !defined(LFS_NO_INTRINSICS) && ( \ 175 | (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ 176 | (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ 177 | (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) 178 | return __builtin_bswap32(a); 179 | #else 180 | return (((uint8_t*)&a)[0] << 0) | 181 | (((uint8_t*)&a)[1] << 8) | 182 | (((uint8_t*)&a)[2] << 16) | 183 | (((uint8_t*)&a)[3] << 24); 184 | #endif 185 | } 186 | 187 | static inline uint32_t lfs_tole32(uint32_t a) { 188 | return lfs_fromle32(a); 189 | } 190 | 191 | // Convert between 32-bit big-endian and native order 192 | static inline uint32_t lfs_frombe32(uint32_t a) { 193 | #if !defined(LFS_NO_INTRINSICS) && ( \ 194 | (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ 195 | (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ 196 | (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) 197 | return __builtin_bswap32(a); 198 | #elif (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ 199 | (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ 200 | (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) 201 | return a; 202 | #else 203 | return (((uint8_t*)&a)[0] << 24) | 204 | (((uint8_t*)&a)[1] << 16) | 205 | (((uint8_t*)&a)[2] << 8) | 206 | (((uint8_t*)&a)[3] << 0); 207 | #endif 208 | } 209 | 210 | static inline uint32_t lfs_tobe32(uint32_t a) { 211 | return lfs_frombe32(a); 212 | } 213 | 214 | // Calculate CRC-32 with polynomial = 0x04c11db7 215 | uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size); 216 | 217 | // Allocate memory, only used if buffers are not provided to littlefs 218 | // Note, memory must be 64-bit aligned 219 | static inline void *lfs_malloc(size_t size) { 220 | #ifndef LFS_NO_MALLOC 221 | return malloc(size); 222 | #else 223 | (void)size; 224 | return NULL; 225 | #endif 226 | } 227 | 228 | // Deallocate memory, only used if buffers are not provided to littlefs 229 | static inline void lfs_free(void *p) { 230 | #ifndef LFS_NO_MALLOC 231 | free(p); 232 | #else 233 | (void)p; 234 | #endif 235 | } 236 | 237 | 238 | #ifdef __cplusplus 239 | } /* extern "C" */ 240 | #endif 241 | 242 | #endif 243 | #endif 244 | -------------------------------------------------------------------------------- /lfs_wrapper.c: -------------------------------------------------------------------------------- 1 | #include "pico/binary_info.h" 2 | #include "pico/stdlib.h" 3 | 4 | #include "hardware/flash.h" 5 | #include "hardware/sync.h" 6 | 7 | 8 | #include "lfs_wrapper.h" 9 | 10 | // Define the flash sizes 11 | // This is setup to read a block of the flash from the end 12 | // Same config as Micropython would should mean the littlefs filesytem 13 | // will remain intact between MicroPython and PiccoloBASIC. 14 | 15 | #define BLOCK_SIZE_BYTES (FLASH_SECTOR_SIZE) 16 | 17 | #ifndef PICCOLOBASIC_HW_FLASH_STORAGE_BYTES 18 | #define PICCOLOBASIC_HW_FLASH_STORAGE_BYTES (1408 * 1024) 19 | #endif 20 | static_assert(PICCOLOBASIC_HW_FLASH_STORAGE_BYTES % 4096 == 0, 21 | "Flash storage size must be a multiple of 4K"); 22 | 23 | #ifndef PICCOLOBASIC_HW_FLASH_STORAGE_BASE 24 | #define PICCOLOBASIC_HW_FLASH_STORAGE_BASE \ 25 | (PICO_FLASH_SIZE_BYTES - PICCOLOBASIC_HW_FLASH_STORAGE_BYTES) 26 | #endif 27 | 28 | static_assert(PICCOLOBASIC_HW_FLASH_STORAGE_BYTES <= PICO_FLASH_SIZE_BYTES, 29 | "PICCOLOBASIC_HW_FLASH_STORAGE_BYTES too big"); 30 | static_assert(PICCOLOBASIC_HW_FLASH_STORAGE_BASE + 31 | PICCOLOBASIC_HW_FLASH_STORAGE_BYTES <= 32 | PICO_FLASH_SIZE_BYTES, 33 | "PICCOLOBASIC_HW_FLASH_STORAGE_BYTES too big"); 34 | 35 | // variables used by the filesystem 36 | lfs_t lfs; 37 | lfs_file_t current_lfs_file; 38 | 39 | int pico_lfsflash_read(const struct lfs_config *c, lfs_block_t block, 40 | lfs_off_t off, void *buffer, lfs_size_t size) { 41 | uint32_t fs_start = XIP_BASE + PICCOLOBASIC_HW_FLASH_STORAGE_BASE; 42 | uint32_t addr = fs_start + (block * c->block_size) + off; 43 | 44 | // printf("[FS] READ: %p, %d\n", addr, size); 45 | 46 | memcpy(buffer, (unsigned char *)addr, size); 47 | return 0; 48 | } 49 | 50 | int pico_lfsflash_prog(const struct lfs_config *c, lfs_block_t block, 51 | lfs_off_t off, const void *buffer, lfs_size_t size) { 52 | uint32_t fs_start = PICCOLOBASIC_HW_FLASH_STORAGE_BASE; 53 | uint32_t addr = fs_start + (block * c->block_size) + off; 54 | 55 | // printf("[FS] WRITE: %p, %d\n", addr, size); 56 | 57 | uint32_t ints = save_and_disable_interrupts(); 58 | flash_range_program(addr, (const uint8_t *)buffer, size); 59 | restore_interrupts(ints); 60 | 61 | return 0; 62 | } 63 | 64 | int pico_lfsflash_erase(const struct lfs_config *c, lfs_block_t block) { 65 | uint32_t fs_start = PICCOLOBASIC_HW_FLASH_STORAGE_BASE; 66 | uint32_t offset = fs_start + (block * c->block_size); 67 | 68 | // printf("[FS] ERASE: %p, %d\n", offset, block); 69 | 70 | uint32_t ints = save_and_disable_interrupts(); 71 | flash_range_erase(offset, c->block_size); 72 | restore_interrupts(ints); 73 | 74 | return 0; 75 | } 76 | 77 | int pico_lfsflash_sync(const struct lfs_config *c) { return 0; } 78 | 79 | // configuration of the filesystem is provided by this struct 80 | const struct lfs_config lfswrapper_cfg = { 81 | // block device operations 82 | .read = &pico_lfsflash_read, 83 | .prog = &pico_lfsflash_prog, 84 | .erase = &pico_lfsflash_erase, 85 | .sync = &pico_lfsflash_sync, 86 | 87 | // block device configuration 88 | .read_size = FLASH_PAGE_SIZE, 89 | .prog_size = FLASH_PAGE_SIZE, 90 | 91 | .block_size = BLOCK_SIZE_BYTES, 92 | .block_count = PICCOLOBASIC_HW_FLASH_STORAGE_BYTES / BLOCK_SIZE_BYTES, 93 | .block_cycles = 500, 94 | 95 | .cache_size = FLASH_PAGE_SIZE, 96 | .lookahead_size = FLASH_PAGE_SIZE, 97 | }; 98 | 99 | int lfswrapper_lfs_mount() { 100 | // mount the filesystem 101 | int err = lfs_mount(&lfs, &lfswrapper_cfg); 102 | 103 | // reformat if we can't mount the filesystem 104 | // this should only happen on the first boot 105 | if (err) { 106 | lfs_format(&lfs, &lfswrapper_cfg); 107 | lfs_mount(&lfs, &lfswrapper_cfg); 108 | } 109 | } 110 | 111 | int lfswrapper_dir_open(const char *path) { 112 | lfs_dir_t *dir = lfs_malloc(sizeof(lfs_dir_t)); 113 | if (dir == NULL) 114 | return -1; 115 | if (lfs_dir_open(&lfs, dir, path) != LFS_ERR_OK) { 116 | lfs_free(dir); 117 | return -1; 118 | } 119 | return (int)dir; 120 | } 121 | int lfswrapper_dir_read(int dir, struct lfs_info *info) { 122 | return lfs_dir_read(&lfs, (lfs_dir_t *)dir, info); 123 | } 124 | 125 | int lfswrapper_dir_seek(int dir, lfs_off_t off) { 126 | return lfs_dir_seek(&lfs, (lfs_dir_t *)dir, off); 127 | } 128 | 129 | lfs_soff_t lfswrapper_dir_tell(int dir) { 130 | return lfs_dir_tell(&lfs, (lfs_dir_t *)dir); 131 | } 132 | 133 | int lfswrapper_dir_rewind(int dir) { 134 | return lfs_dir_rewind(&lfs, (lfs_dir_t *)dir); 135 | } 136 | 137 | int lfswrapper_dir_close(int dir) { 138 | return lfs_dir_close(&lfs, (lfs_dir_t *)dir); 139 | lfs_free((void *)dir); 140 | } 141 | 142 | int lfswrapper_file_open(char *n, int flags) { 143 | return lfs_file_open(&lfs, ¤t_lfs_file, n, flags); 144 | } 145 | 146 | int lfswrapper_file_close() { return lfs_file_close(&lfs, ¤t_lfs_file); } 147 | 148 | int lfswrapper_file_write(const void *buffer, int sz) { 149 | return (int)lfs_file_write(&lfs, ¤t_lfs_file, buffer, (lfs_size_t)sz); 150 | } 151 | 152 | int lfswrapper_file_read(void *buffer, int sz) { 153 | return (int)lfs_file_read(&lfs, ¤t_lfs_file, buffer, sz); 154 | } 155 | 156 | int lfswrapper_get_file_size(char *path) { 157 | struct lfs_info info; 158 | int sts = lfs_stat(&lfs, path, &info); 159 | if (sts < 0) 160 | return -1; 161 | return info.size; 162 | } 163 | 164 | int lfswrapper_delete_file(char *path) { return lfs_remove(&lfs, path); } 165 | 166 | void lfswrapper_dump_dir(char *path) { 167 | // display each directory entry name 168 | printf("%s\n", path); 169 | int dir = lfswrapper_dir_open(path); 170 | if (dir < 0) 171 | return; 172 | 173 | struct lfs_info info; 174 | while (lfswrapper_dir_read(dir, &info) > 0) 175 | printf("%s\n", info.name); 176 | lfswrapper_dir_close(dir); 177 | } 178 | -------------------------------------------------------------------------------- /lfs_wrapper.h: -------------------------------------------------------------------------------- 1 | #include "lfs.h" 2 | 3 | int lfswrapper_lfs_mount(); 4 | int lfswrapper_dir_open(const char* path); 5 | int lfswrapper_dir_read(int dir, struct lfs_info* info); 6 | int lfswrapper_dir_seek(int dir, lfs_off_t off); 7 | lfs_soff_t lfswrapper_dir_tell(int dir); 8 | int lfswrapper_dir_rewind(int dir); 9 | int lfswrapper_dir_close(int dir); 10 | void lfswrapper_dump_dir(char *); 11 | int lfswrapper_file_open(char *n, int flags); 12 | int lfswrapper_file_close(); 13 | int lfswrapper_file_write(const void *buffer, int sz); 14 | int lfswrapper_file_read(void *buffer, int sz); 15 | int lfswrapper_get_file_size(char *path); 16 | int lfswrapper_delete_file(char *path); -------------------------------------------------------------------------------- /pbserialmon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import time 3 | import serial 4 | import sys 5 | import os 6 | 7 | def waitforok(serl): 8 | while 1: 9 | r=serl.readline() 10 | rstr = str(r, 'ascii') 11 | if len(rstr) == 0: 12 | continue 13 | if "+OK" in rstr: 14 | break 15 | 16 | def serialmon(): 17 | ser = serial.Serial( 18 | port=sys.argv[1], 19 | baudrate = 9600, 20 | parity=serial.PARITY_NONE, 21 | stopbits=serial.STOPBITS_ONE, 22 | bytesize=serial.EIGHTBITS, 23 | timeout=1 24 | ) 25 | 26 | while 1: 27 | r=ser.readline() 28 | rstr = str(r, 'ascii') 29 | if len(rstr) == 0: 30 | continue 31 | print(rstr, end="") 32 | 33 | def uploader(): 34 | print("Uploading " + sys.argv[1] + " to piccoloBASIC via " + sys.argv[2]) 35 | file_stats = os.stat(sys.argv[1]) 36 | if file_stats.st_size < 0: 37 | print("Can't get file size of " + sys.argv[1]) 38 | exit(1) 39 | 40 | ser = serial.Serial( 41 | port=sys.argv[2], 42 | baudrate = 9600, 43 | parity=serial.PARITY_NONE, 44 | stopbits=serial.STOPBITS_ONE, 45 | bytesize=serial.EIGHTBITS, 46 | timeout=1 47 | ) 48 | 49 | packet = bytearray() 50 | packet.append(0x03) 51 | packet.append(0x03) 52 | 53 | ser.write(packet) 54 | 55 | # Wait to enter CMD mode 56 | while 1: 57 | r=ser.readline() 58 | rstr = str(r, 'ascii') 59 | if len(rstr) == 0: 60 | continue 61 | if "+OK PiccoloBASIC CMD Mode" in rstr: 62 | break 63 | print("Entered CMD mode.") 64 | print("Starting upload of " + sys.argv[1] + " (" + str(file_stats.st_size) + " bytes)") 65 | 66 | cmd = "upload " + sys.argv[1] + " " + str(file_stats.st_size) + "\n" 67 | packet = bytearray(cmd, 'ascii') 68 | ser.write(packet) 69 | 70 | waitforok(ser) 71 | 72 | with open(sys.argv[1], 'rb') as f: 73 | byte = f.read(1) 74 | while byte != b'': 75 | up = "" + str(ord(byte)) + "\n" 76 | #print(up) 77 | packet = bytearray(up, 'ascii') 78 | ser.write(packet) 79 | waitforok(ser) 80 | byte = f.read(1) 81 | 82 | cmd = "exit\n" 83 | packet = bytearray(cmd, 'ascii') 84 | ser.write(packet) 85 | 86 | print("Upload complete.") 87 | exit(0) 88 | 89 | print("PiccoloBASIC serial monitor and file uploader.") 90 | 91 | if len(sys.argv)==2: 92 | serialmon() 93 | 94 | if len(sys.argv)==3: 95 | uploader() 96 | 97 | print("Usage: " + sys.argv[0] + "[] "); 98 | print("If you include a file, as the first parameter, it will be uploaded to the board running PiccoloBASIC.") 99 | print("eg: " + sys.argv[0] + " /dev/ttyACM0"); 100 | print("eg: " + sys.argv[0] + " main.bas /dev/ttyACM0"); 101 | 102 | exit(1) 103 | 104 | -------------------------------------------------------------------------------- /piccoloBASIC.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023, Gary Sims 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. Neither the name of the author nor the names of its contributors 14 | * may be used to endorse or promote products derived from this software 15 | * without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 | * SUCH DAMAGE. 28 | * 29 | */ 30 | 31 | /* 32 | * Warning: This code needs more error checking for bad/malformed commands sent 33 | * in CMD mode 34 | */ 35 | 36 | #include 37 | #include 38 | #include 39 | // #include 40 | 41 | #include "hardware/watchdog.h" 42 | #include "pico/stdlib.h" 43 | 44 | #include "lfs_wrapper.h" 45 | #include "piccoloBASIC.h" 46 | #include "ubasic.h" 47 | 48 | #define MAX_CMD_LINE 100 49 | #define MAX_PATH_LEN 100 50 | 51 | /* 52 | * Eek! Globals! 53 | */ 54 | char cwd[MAX_PATH_LEN]; 55 | int lookahead = -1; 56 | int needsreboot = 0; 57 | 58 | static char *getLine(int echo) { 59 | const uint startLineLength = 60 | 8; // the linebuffer will automatically grow for longer lines 61 | const char eof = 255; // EOF in stdio.h -is -1, but getchar returns int 255 to 62 | // avoid blocking 63 | 64 | char *pStart = (char *)malloc(startLineLength); 65 | char *pPos = pStart; // next character position 66 | size_t maxLen = startLineLength; // current max buffer size 67 | size_t len = maxLen; // current max length 68 | int c; 69 | 70 | if (!pStart) { 71 | return NULL; // out of memory or dysfunctional heap 72 | } 73 | 74 | while (1) { 75 | if (lookahead >= 0) { 76 | c = lookahead; 77 | lookahead = -1; 78 | } else { 79 | c = getchar(); 80 | } 81 | if ((echo) && (c >= ' ') && (c <= 126)) 82 | printf("%c", c); 83 | if (c == 0x03) { // CTRL-C 84 | if (!pStart) { 85 | free(pStart); 86 | } 87 | return NULL; 88 | } 89 | 90 | if ((char)c == eof) { 91 | break; // Done 92 | } 93 | 94 | if ((char)c == '\n') { 95 | break; // Done 96 | } 97 | 98 | if ((char)c == '\r') { 99 | lookahead = getchar_timeout_us(500000); 100 | if (lookahead == PICO_ERROR_TIMEOUT) { 101 | // Assume \r was the end of the line 102 | break; // Done 103 | } 104 | if (lookahead == '\n') { 105 | lookahead = -1; 106 | break; 107 | } else { 108 | // Assume \r was the end of the line 109 | break; // Done 110 | } 111 | } 112 | 113 | if (--len == 0) { // allow larger buffer 114 | len = maxLen; 115 | // double the current line buffer size 116 | char *pNew = (char *)realloc(pStart, maxLen *= 2); 117 | if (!pNew) { 118 | free(pStart); 119 | return NULL; // out of memory abort 120 | } 121 | // fix pointer for new buffer 122 | pPos = pNew + (pPos - pStart); 123 | pStart = pNew; 124 | } 125 | *pPos++ = c; 126 | } 127 | 128 | *pPos = '\0'; // set string end mark 129 | if (echo) 130 | printf("\n"); 131 | return pStart; 132 | } 133 | 134 | int doupload(char *uploadfilename, int uploadfilesize) { 135 | int count = 0; 136 | char *program = malloc(PROG_BUFFER_SIZE); 137 | if (program == NULL) { 138 | return -1; 139 | } 140 | 141 | lfswrapper_file_open(uploadfilename, LFS_O_RDWR | LFS_O_CREAT | LFS_O_TRUNC); 142 | while (count < uploadfilesize) { 143 | char *result = getLine(0); 144 | printf("+OK\n"); 145 | stdio_flush(); 146 | int b = atoi(result); 147 | program[count++] = (char)b; 148 | free(result); 149 | } 150 | lfswrapper_file_write(program, uploadfilesize); 151 | lfswrapper_file_close(); 152 | return count; 153 | } 154 | 155 | int enter_CMD_mode() { 156 | char line[MAX_CMD_LINE]; 157 | char *result = NULL; 158 | int done = 0; 159 | 160 | stdio_flush(); 161 | printf("+OK PiccoloBASIC CMD Mode\n"); 162 | stdio_flush(); 163 | 164 | sprintf(cwd, "%s", "/"); 165 | 166 | while (!done) { 167 | if (result != NULL) 168 | free(result); 169 | result = getLine(0); 170 | // Extract the first token 171 | char *token = strtok(result, " "); 172 | 173 | if (token != NULL) { 174 | if (strcmp(token, "exit") == 0) { 175 | if (needsreboot) 176 | watchdog_reboot(0, SRAM_END, 0); 177 | done = 1; 178 | } else if (strcmp(token, "reboot") == 0) { 179 | watchdog_reboot(0, SRAM_END, 0); 180 | } else if (strcmp(token, "ls") == 0) { 181 | printf("+OK\n"); 182 | lfswrapper_dump_dir(cwd); 183 | } else if (strcmp(token, "upload") == 0) { // upload main.bas 432 184 | printf("+OK\n"); 185 | token = strtok(NULL, " "); // filename 186 | char *uploadfilename = malloc(strlen(token) + 1); 187 | strcpy(uploadfilename, token); 188 | token = strtok(NULL, " "); // file size in bytes 189 | int uploadfilesize = atoi(token); 190 | if (uploadfilesize > 0) { 191 | doupload(uploadfilename, uploadfilesize); 192 | } 193 | free(uploadfilename); 194 | needsreboot = 1; 195 | } else if (strcmp(token, "rm") == 0) { // upload main.bas 432 196 | printf("+OK\n"); 197 | token = strtok(NULL, " "); // filename 198 | lfswrapper_delete_file(token); 199 | } else if (strcmp(token, "cd") == 0) { 200 | printf("+OK\n"); 201 | token = strtok(NULL, " "); 202 | if ((strcmp(token, "..") == 0) || (strcmp(token, "/") == 0)) { 203 | sprintf(cwd, "//", token); 204 | } else if ((strcmp(token, ".") == 0)) { 205 | sprintf(cwd, "%s", cwd); 206 | } else { 207 | sprintf(cwd, "/%s", token); 208 | } 209 | } else { 210 | printf("-ERR %s\n", token); 211 | } 212 | } 213 | } 214 | } 215 | 216 | int check_if_should_enter_CMD_mode() { 217 | int chr = getchar_timeout_us(0); 218 | if (chr != PICO_ERROR_TIMEOUT) { 219 | if (chr == 3) { // CTRL-C 220 | int chr2 = getchar_timeout_us(500 * 1000); 221 | if (chr2 != PICO_ERROR_TIMEOUT) { 222 | enter_CMD_mode(); 223 | return 1; 224 | } 225 | } 226 | } 227 | 228 | return 0; 229 | } 230 | 231 | int main(int argc, char *argv[]) { 232 | int proglen; 233 | bool norun = false; 234 | 235 | stdio_init_all(); 236 | 237 | // Check if GPI10 is high, if so don't run program 238 | // This allows the uploader to enter CMD mode so 239 | // it still possible to upload a new BASIC program 240 | gpio_init(10); 241 | gpio_set_dir(10, GPIO_IN); 242 | gpio_pull_down(10); 243 | norun = gpio_get(10); 244 | 245 | lfswrapper_lfs_mount(); 246 | 247 | if (!norun) { 248 | // Allocate memory for the program 249 | char *program = malloc(PROG_BUFFER_SIZE); 250 | if (program == NULL) { 251 | perror("Error allocating memory for program"); 252 | return 1; 253 | } 254 | 255 | int mainsz = lfswrapper_get_file_size("main.bas"); 256 | 257 | if (mainsz > 0) { 258 | lfswrapper_file_open("main.bas", LFS_O_RDONLY); 259 | proglen = lfswrapper_file_read(program, PROG_BUFFER_SIZE); 260 | program[proglen] = 0; 261 | lfswrapper_file_close(); 262 | } 263 | ubasic_init(program); 264 | do { 265 | ubasic_run(); 266 | } while (!ubasic_finished()); 267 | 268 | // Free the memory allocated for the program 269 | free(program); 270 | } else { 271 | // Eek! Hardcoded! 272 | gpio_init(14); 273 | gpio_set_dir(14, GPIO_OUT); 274 | gpio_put(14, 1); 275 | } 276 | // Never actually return/exit 277 | while (true) { 278 | check_if_should_enter_CMD_mode(); 279 | sleep_ms(500); 280 | } 281 | return 0; 282 | } 283 | -------------------------------------------------------------------------------- /piccoloBASIC.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023, Gary Sims 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. Neither the name of the author nor the names of its contributors 14 | * may be used to endorse or promote products derived from this software 15 | * without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 | * SUCH DAMAGE. 28 | * 29 | */ 30 | #ifndef __UBAS_H__ 31 | #define __UBAS_H__ 32 | 33 | #define PROG_BUFFER_SIZE 4096 // Size of the buffer to read the file into 34 | 35 | int check_if_should_enter_CMD_mode(); 36 | 37 | #endif /* __UBAS_H__ */ 38 | 39 | 40 | -------------------------------------------------------------------------------- /pico_sdk_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /external/pico_sdk_import.cmake 2 | 3 | # This can be dropped into an external project to help locate this SDK 4 | # It should be include()ed prior to project() 5 | 6 | if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) 7 | set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) 8 | message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") 9 | endif () 10 | 11 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) 12 | set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) 13 | message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") 14 | endif () 15 | 16 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) 17 | set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) 18 | message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") 19 | endif () 20 | 21 | set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") 22 | set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") 23 | set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") 24 | 25 | if (NOT PICO_SDK_PATH) 26 | if (PICO_SDK_FETCH_FROM_GIT) 27 | include(FetchContent) 28 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 29 | if (PICO_SDK_FETCH_FROM_GIT_PATH) 30 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 31 | endif () 32 | # GIT_SUBMODULES_RECURSE was added in 3.17 33 | if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") 34 | FetchContent_Declare( 35 | pico_sdk 36 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 37 | GIT_TAG master 38 | GIT_SUBMODULES_RECURSE FALSE 39 | ) 40 | else () 41 | FetchContent_Declare( 42 | pico_sdk 43 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 44 | GIT_TAG master 45 | ) 46 | endif () 47 | 48 | if (NOT pico_sdk) 49 | message("Downloading Raspberry Pi Pico SDK") 50 | FetchContent_Populate(pico_sdk) 51 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 52 | endif () 53 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 54 | else () 55 | message(FATAL_ERROR 56 | "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." 57 | ) 58 | endif () 59 | endif () 60 | 61 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 62 | if (NOT EXISTS ${PICO_SDK_PATH}) 63 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") 64 | endif () 65 | 66 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) 67 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) 68 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") 69 | endif () 70 | 71 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) 72 | 73 | include(${PICO_SDK_INIT_CMAKE_FILE}) 74 | -------------------------------------------------------------------------------- /tokenizer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006, Adam Dunkels 3 | * Copyright (c) 2023, Gary Sims 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 3. Neither the name of the author nor the names of its contributors 15 | * may be used to endorse or promote products derived from this software 16 | * without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 | * SUCH DAMAGE. 29 | * 30 | */ 31 | 32 | #include "tokenizer.h" 33 | #include 34 | #include /* printf() */ 35 | #include 36 | #include 37 | 38 | #define DEBUG 0 39 | 40 | #if DEBUG 41 | #define DEBUG_PRINTF(...) printf(__VA_ARGS__) 42 | #else 43 | #define DEBUG_PRINTF(...) 44 | #endif 45 | 46 | static char const *ptr, *nextptr; 47 | 48 | #define MAX_NUMLEN 18 49 | 50 | struct keyword_token { 51 | char *keyword; 52 | int token; 53 | }; 54 | 55 | static int current_token = TOKENIZER_ERROR; 56 | 57 | static const struct keyword_token keywords[] = { 58 | {"let", TOKENIZER_LET}, {"print", TOKENIZER_PRINT}, 59 | {"if", TOKENIZER_IF}, {"then", TOKENIZER_THEN}, 60 | {"else", TOKENIZER_ELSE}, {"for", TOKENIZER_FOR}, 61 | {"to", TOKENIZER_TO}, {"next", TOKENIZER_NEXT}, 62 | {"goto", TOKENIZER_GOTO}, {"gosub", TOKENIZER_GOSUB}, 63 | {"return", TOKENIZER_RETURN}, {"call", TOKENIZER_CALL}, 64 | {"rem", TOKENIZER_REM}, {"peek", TOKENIZER_PEEK}, 65 | {"poke", TOKENIZER_POKE}, {"end", TOKENIZER_END}, 66 | {"delay", TOKENIZER_DELAY}, {"sleep", TOKENIZER_SLEEP}, 67 | {"zero", TOKENIZER_ZERO}, {"not", TOKENIZER_NOT}, 68 | {"randomize", TOKENIZER_RANDOMIZE}, {"randint", TOKENIZER_RANDINT}, 69 | {"rnd", TOKENIZER_RND}, {"time", TOKENIZER_TIME}, 70 | {"push", TOKENIZER_PUSH}, {"pop", TOKENIZER_POP}, 71 | {"abs", TOKENIZER_ABS}, {"atn", TOKENIZER_ATN}, 72 | {"cos", TOKENIZER_COS}, {"exp", TOKENIZER_EXP}, 73 | {"log", TOKENIZER_LOG}, {"tan", TOKENIZER_TAN}, 74 | {"sin", TOKENIZER_SIN}, {"sqr", TOKENIZER_SQR}, 75 | {"len", TOKENIZER_LEN}, {"os", TOKENIZER_OS}, 76 | {"pininit", TOKENIZER_GPIOINIT}, {"pindirin", TOKENIZER_GPIODIRIN}, 77 | {"pindirout", TOKENIZER_GPIODIROUT}, {"pinon", TOKENIZER_GPIOON}, 78 | {"pinoff", TOKENIZER_GPIOOFF}, 79 | {"//", TOKENIZER_REM}, 80 | {NULL, TOKENIZER_ERROR}}; 81 | 82 | /*---------------------------------------------------------------------------*/ 83 | static int singlechar(void) { 84 | if (*ptr == '\n') { 85 | return TOKENIZER_CR; 86 | } else if (*ptr == ',') { 87 | return TOKENIZER_COMMA; 88 | } else if (*ptr == ';') { 89 | return TOKENIZER_SEMICOLON; 90 | } else if (*ptr == '+') { 91 | return TOKENIZER_PLUS; 92 | } else if (*ptr == '-') { 93 | return TOKENIZER_MINUS; 94 | } else if (*ptr == '&') { 95 | return TOKENIZER_AND; 96 | } else if (*ptr == '|') { 97 | return TOKENIZER_OR; 98 | } else if (*ptr == '*') { 99 | return TOKENIZER_ASTR; 100 | } else if (*ptr == '/') { 101 | if ((*(ptr + 1) != 0) && (*(ptr + 1) == '/')) 102 | return 0; 103 | else 104 | return TOKENIZER_SLASH; 105 | } else if (*ptr == '%') { 106 | return TOKENIZER_MOD; 107 | } else if (*ptr == '(') { 108 | return TOKENIZER_LEFTPAREN; 109 | } else if (*ptr == '#') { 110 | return TOKENIZER_HASH; 111 | } else if (*ptr == ')') { 112 | return TOKENIZER_RIGHTPAREN; 113 | } else if (*ptr == '<') { 114 | return TOKENIZER_LT; 115 | } else if (*ptr == '>') { 116 | return TOKENIZER_GT; 117 | } else if (*ptr == '=') { 118 | return TOKENIZER_EQ; 119 | } 120 | return 0; 121 | } 122 | /*---------------------------------------------------------------------------*/ 123 | int isfloatdigit(char c) { 124 | return (((c >= '0' && c <= '9') || (c == '.')) ? 1 : 0); 125 | } 126 | /*---------------------------------------------------------------------------*/ 127 | static int get_next_token(void) { 128 | struct keyword_token const *kt; 129 | int i; 130 | int isfloat = 0; 131 | 132 | DEBUG_PRINTF("get_next_token(): '%s'\n", ptr); 133 | 134 | if (*ptr == 0) { 135 | return TOKENIZER_ENDOFINPUT; 136 | } 137 | 138 | if (isdigit(*ptr)) { 139 | for (i = 0; i < MAX_NUMLEN; ++i) { 140 | if (!isfloatdigit(ptr[i])) { 141 | if (i > 0) { 142 | nextptr = ptr + i; 143 | if (isfloat) 144 | return TOKENIZER_NUMFLOAT; 145 | else 146 | return TOKENIZER_NUMBER; 147 | } else { 148 | printf("Number is too short\n"); 149 | exit(-1); 150 | } 151 | } 152 | if (ptr[i] == '.') 153 | isfloat = 1; 154 | if (!isfloatdigit(ptr[i])) { 155 | DEBUG_PRINTF("Malformed number\n"); 156 | exit(-1); 157 | } 158 | } 159 | printf("Number is too long\n"); 160 | exit(-1); 161 | } else if (singlechar()) { 162 | nextptr = ptr + 1; 163 | return singlechar(); 164 | } else if (*ptr == '"') { 165 | nextptr = ptr; 166 | do { 167 | ++nextptr; 168 | } while (*nextptr != '"'); 169 | ++nextptr; 170 | return TOKENIZER_STRING; 171 | } else { 172 | for (kt = keywords; kt->keyword != NULL; ++kt) { 173 | if (strncmp(ptr, kt->keyword, strlen(kt->keyword)) == 0) { 174 | nextptr = ptr + strlen(kt->keyword); 175 | return kt->token; 176 | } 177 | } 178 | } 179 | 180 | // Is it a label? 181 | nextptr = ptr; 182 | do { 183 | ++nextptr; 184 | } while ((*nextptr != '\n') && (*nextptr != ':') && 185 | (nextptr - ptr < MAX_LABELLEN)); 186 | 187 | if (*nextptr == ':') { 188 | ++nextptr; 189 | return TOKENIZER_LABEL; 190 | } 191 | 192 | // Floasting point variable? 193 | if ((*ptr >= 'a' && *ptr <= 'z') && (*(ptr + 1) == '#')) { 194 | nextptr = ptr + 2; 195 | return TOKENIZER_VARFLOAT; 196 | } 197 | 198 | // String variable? 199 | if ((*ptr >= 'a' && *ptr <= 'z') && (*(ptr + 1) == '$')) { 200 | nextptr = ptr + 2; 201 | return TOKENIZER_VARSTRING; 202 | } 203 | 204 | // Integer variable? 205 | if (*ptr >= 'a' && *ptr <= 'z') { 206 | nextptr = ptr + 1; 207 | return TOKENIZER_VARIABLE; 208 | } 209 | 210 | return TOKENIZER_ERROR; 211 | } 212 | /*---------------------------------------------------------------------------*/ 213 | void tokenizer_goto(const char *program) { 214 | ptr = program; 215 | current_token = get_next_token(); 216 | } 217 | /*---------------------------------------------------------------------------*/ 218 | void tokenizer_init(const char *program) { 219 | tokenizer_goto(program); 220 | current_token = get_next_token(); 221 | } 222 | /*---------------------------------------------------------------------------*/ 223 | int tokenizer_token(void) { return current_token; } 224 | /*---------------------------------------------------------------------------*/ 225 | void tokenizer_next(void) { 226 | 227 | if (tokenizer_finished()) { 228 | return; 229 | } 230 | 231 | DEBUG_PRINTF("tokenizer_next: %p\n", nextptr); 232 | ptr = nextptr; 233 | 234 | while (*ptr == ' ') { 235 | ++ptr; 236 | } 237 | current_token = get_next_token(); 238 | 239 | if (current_token == TOKENIZER_REM) { 240 | while (!(*nextptr == '\n' || tokenizer_finished())) { 241 | ++nextptr; 242 | } 243 | if (*nextptr == '\n') { 244 | ++nextptr; 245 | } 246 | tokenizer_next(); 247 | } 248 | 249 | DEBUG_PRINTF("tokenizer_next: '%s' %d\n", ptr, current_token); 250 | return; 251 | } 252 | /*---------------------------------------------------------------------------*/ 253 | VARIABLE_TYPE tokenizer_num(void) { 254 | return atoi(ptr); 255 | } 256 | /*---------------------------------------------------------------------------*/ 257 | VARFLOAT_TYPE tokenizer_numfloat(void) { 258 | return atof(ptr); 259 | } 260 | /*---------------------------------------------------------------------------*/ 261 | void tokenizer_string(char *dest, int len) { 262 | char *string_end; 263 | int string_len; 264 | 265 | if (tokenizer_token() != TOKENIZER_STRING) { 266 | printf("Internal error, expecting string\n"); 267 | exit(-1); 268 | } 269 | string_end = strchr(ptr + 1, '"'); 270 | if (string_end == NULL) { 271 | printf("Error: Missing quote\n"); 272 | exit(-1); 273 | } 274 | string_len = string_end - ptr - 1; 275 | if (len < string_len) { 276 | string_len = len; 277 | } 278 | 279 | if (string_len >= len) { 280 | printf("Error: String too long\n"); 281 | exit(-1); 282 | } 283 | memcpy(dest, ptr + 1, string_len); 284 | dest[string_len] = 0; 285 | } 286 | /*---------------------------------------------------------------------------*/ 287 | void tokenizer_label(char *dest, int len) { 288 | char *string_end; 289 | int string_len; 290 | 291 | DEBUG_PRINTF("tokenizer_label ptr is: '%s'\n", ptr); 292 | string_end = strchr(ptr + 1, ':'); 293 | if (string_end == NULL) { 294 | printf("Internal error, no : found in label\n"); 295 | exit(-1); 296 | } 297 | 298 | DEBUG_PRINTF("tokenizer_label string_end is: '%s'\n", string_end); 299 | 300 | string_len = string_end - ptr; 301 | DEBUG_PRINTF("tokenizer_label string_len is: '%d'\n", string_len); 302 | if (len < string_len) { 303 | string_len = len; 304 | } 305 | if (string_len >= len) { 306 | printf("Error: Label too long\n"); 307 | exit(-1); 308 | } 309 | memcpy(dest, ptr, string_len); 310 | dest[string_len] = 0; 311 | } 312 | /*---------------------------------------------------------------------------*/ 313 | void tokenizer_error_print(int line, char *msg) { 314 | printf("Error on line %d: %s\n", line, msg); 315 | } 316 | /*---------------------------------------------------------------------------*/ 317 | int tokenizer_finished(void) { 318 | return *ptr == 0 || current_token == TOKENIZER_ENDOFINPUT; 319 | } 320 | /*---------------------------------------------------------------------------*/ 321 | int tokenizer_variable_num(void) { return *ptr - 'a'; } 322 | /*---------------------------------------------------------------------------*/ 323 | char const *tokenizer_pos(void) { return ptr; } 324 | -------------------------------------------------------------------------------- /tokenizer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006, Adam Dunkels 3 | * Copyright (c) 2023, Gary Sims 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 3. Neither the name of the author nor the names of its contributors 15 | * may be used to endorse or promote products derived from this software 16 | * without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 | * SUCH DAMAGE. 29 | * 30 | */ 31 | #ifndef __TOKENIZER_H__ 32 | #define __TOKENIZER_H__ 33 | 34 | #include "vartype.h" 35 | 36 | #define MAX_LABELLEN 16 37 | 38 | enum { 39 | TOKENIZER_ERROR, 40 | TOKENIZER_ENDOFINPUT, 41 | TOKENIZER_NUMBER, 42 | TOKENIZER_NUMFLOAT, 43 | TOKENIZER_STRING, 44 | TOKENIZER_VARIABLE, 45 | TOKENIZER_VARFLOAT, 46 | TOKENIZER_VARSTRING, 47 | TOKENIZER_LABEL, 48 | TOKENIZER_LET, 49 | TOKENIZER_PRINT, 50 | TOKENIZER_IF, 51 | TOKENIZER_THEN, 52 | TOKENIZER_ELSE, 53 | TOKENIZER_FOR, 54 | TOKENIZER_TO, 55 | TOKENIZER_NEXT, 56 | TOKENIZER_GOTO, 57 | TOKENIZER_GOSUB, 58 | TOKENIZER_RETURN, 59 | TOKENIZER_CALL, 60 | TOKENIZER_REM, 61 | TOKENIZER_PEEK, 62 | TOKENIZER_POKE, 63 | TOKENIZER_DELAY, 64 | TOKENIZER_SLEEP, 65 | TOKENIZER_RANDOMIZE, 66 | TOKENIZER_PUSH, 67 | TOKENIZER_POP, 68 | TOKENIZER_END, 69 | TOKENIZER_GPIOINIT, 70 | TOKENIZER_GPIODIRIN, 71 | TOKENIZER_GPIODIROUT, 72 | TOKENIZER_GPIOON, 73 | TOKENIZER_GPIOOFF, 74 | TOKENIZER_BUILTINS__START, 75 | TOKENIZER_ZERO, 76 | TOKENIZER_NOT, 77 | TOKENIZER_RANDINT, 78 | TOKENIZER_TIME, 79 | TOKENIZER_BUILTINS__END, 80 | TOKENIZER_BUILTINSF__START, 81 | TOKENIZER_RND, 82 | TOKENIZER_ABS, 83 | TOKENIZER_ATN, 84 | TOKENIZER_COS, 85 | TOKENIZER_EXP, 86 | TOKENIZER_LOG, 87 | TOKENIZER_SIN, 88 | TOKENIZER_SQR, 89 | TOKENIZER_TAN, 90 | TOKENIZER_BUILTINSF__END, 91 | TOKENIZER_LEN, 92 | TOKENIZER_OS, 93 | TOKENIZER_COMMA, 94 | TOKENIZER_SEMICOLON, 95 | TOKENIZER_PLUS, 96 | TOKENIZER_MINUS, 97 | TOKENIZER_AND, 98 | TOKENIZER_OR, 99 | TOKENIZER_ASTR, 100 | TOKENIZER_SLASH, 101 | TOKENIZER_MOD, 102 | TOKENIZER_HASH, 103 | TOKENIZER_LEFTPAREN, 104 | TOKENIZER_RIGHTPAREN, 105 | TOKENIZER_LT, 106 | TOKENIZER_GT, 107 | TOKENIZER_EQ, 108 | TOKENIZER_CR, 109 | }; 110 | 111 | void tokenizer_goto(const char *program); 112 | void tokenizer_init(const char *program); 113 | void tokenizer_next(void); 114 | int tokenizer_token(void); 115 | VARIABLE_TYPE tokenizer_num(void); 116 | VARFLOAT_TYPE tokenizer_numfloat(void); 117 | int tokenizer_variable_num(void); 118 | void tokenizer_string(char *dest, int len); 119 | void tokenizer_label(char *dest, int len); 120 | 121 | int tokenizer_finished(void); 122 | void tokenizer_error_print(int line, char *msg); 123 | 124 | char const *tokenizer_pos(void); 125 | 126 | #endif /* __TOKENIZER_H__ */ 127 | -------------------------------------------------------------------------------- /ubasic.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006, Adam Dunkels 3 | * Copyright (c) 2023, Gary Sims 4 | * All rights reserved. 5 | * 6 | * Bug fixes 2021, Alessio Villa (see 7 | * https://github.com/adamdunkels/ubasic/issues/4) 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions 11 | * are met: 12 | * 1. Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in the 16 | * documentation and/or other materials provided with the distribution. 17 | * 3. Neither the name of the author nor the names of its contributors 18 | * may be used to endorse or promote products derived from this software 19 | * without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 | * SUCH DAMAGE. 32 | * 33 | */ 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #include "pico/stdlib.h" 44 | 45 | #define DEBUG 0 46 | 47 | #if DEBUG 48 | #define DEBUG_PRINTF(...) printf(__VA_ARGS__) 49 | #else 50 | #define DEBUG_PRINTF(...) 51 | #endif 52 | 53 | #define DDEBUG 0 54 | 55 | #if DDEBUG 56 | #define DDEBUG_PRINTF(...) printf(__VA_ARGS__) 57 | #else 58 | #define DDEBUG_PRINTF(...) 59 | #endif 60 | 61 | #include "tokenizer.h" 62 | #include "ubasic.h" 63 | #include "piccoloBASIC.h" 64 | 65 | static char const *program_ptr; 66 | #define MAX_STRINGLEN 128 67 | static char string[MAX_STRINGLEN]; 68 | 69 | #define MAX_GOSUB_STACK_DEPTH 10 70 | static int gosub_stack[MAX_GOSUB_STACK_DEPTH]; 71 | static int gosub_stack_ptr; 72 | 73 | #define MAX_INT_STACK_DEPTH 256 74 | static int int_stack[MAX_INT_STACK_DEPTH]; 75 | static int int_stack_ptr; 76 | 77 | struct for_state { 78 | int line_after_for; 79 | int for_variable; 80 | int to; 81 | }; 82 | #define MAX_FOR_STACK_DEPTH 4 83 | static struct for_state for_stack[MAX_FOR_STACK_DEPTH]; 84 | static int for_stack_ptr; 85 | 86 | static int gline_number; 87 | 88 | static unsigned long RANDOM_NUM_SEED_x=123456789; 89 | 90 | #define MAX_VARNUM 26 91 | 92 | struct line_index { 93 | int line_number; 94 | char label[MAX_LABELLEN]; 95 | char const *program_text_position; 96 | struct line_index *next; 97 | }; 98 | struct line_index *line_index_head = NULL; 99 | struct line_index *line_index_current = NULL; 100 | 101 | static VARIABLE_TYPE variables[MAX_VARNUM]; 102 | static VARFLOAT_TYPE float_variables[MAX_VARNUM]; 103 | static VARSTRING_TYPE string_variables[MAX_VARNUM]; 104 | 105 | static int ended; 106 | 107 | static VARIABLE_TYPE expr(void); 108 | static VARFLOAT_TYPE exprf(void); 109 | static VARSTRING_TYPE exprs(void); 110 | static void line_statement(void); 111 | static void statement(void); 112 | static void index_free(void); 113 | static void index_add_label(int linenum, char *label); 114 | static VARIABLE_TYPE builtin(int token, int p); 115 | static VARFLOAT_TYPE builtinf(int token, VARFLOAT_TYPE p); 116 | static VARSTRING_TYPE builtinstr(int token, VARSTRING_TYPE p); 117 | static VARSTRING_TYPE sprintfloat(VARFLOAT_TYPE f); 118 | static void printfloat(VARFLOAT_TYPE f); 119 | 120 | peek_func peek_function = NULL; 121 | poke_func poke_function = NULL; 122 | 123 | /*---------------------------------------------------------------------------*/ 124 | void ubasic_init(const char *program) { 125 | program_ptr = program; 126 | for_stack_ptr = gosub_stack_ptr = 0; 127 | index_free(); 128 | peek_function = NULL; 129 | poke_function = NULL; 130 | tokenizer_init(program); 131 | gline_number = 1; 132 | ended = 0; 133 | for(int i=0;i r2; 526 | break; 527 | case TOKENIZER_EQ: 528 | r1 = r1 == r2; 529 | break; 530 | } 531 | op = tokenizer_token(); 532 | } 533 | return r1; 534 | } 535 | /*---------------------------------------------------------------------------*/ 536 | static void index_free(void) { 537 | if (line_index_head != NULL) { 538 | line_index_current = line_index_head; 539 | do { 540 | DDEBUG_PRINTF("Freeing index for line %d.\n", 541 | line_index_current->line_number); 542 | line_index_head = line_index_current; 543 | line_index_current = line_index_current->next; 544 | free(line_index_head); 545 | } while (line_index_current != NULL); 546 | line_index_head = NULL; 547 | } 548 | } 549 | /*---------------------------------------------------------------------------*/ 550 | static char const *index_find(int linenum) { 551 | struct line_index *lidx; 552 | lidx = line_index_head; 553 | 554 | #if DEBUG 555 | int step = 0; 556 | #endif 557 | 558 | while (lidx != NULL && lidx->line_number != linenum) { 559 | lidx = lidx->next; 560 | 561 | #if DEBUG 562 | if (lidx != NULL) { 563 | DDEBUG_PRINTF("index_find: Step %3d. Found index for line %d: %p.\n", step, 564 | lidx->line_number, (void *)lidx->program_text_position); 565 | } 566 | step++; 567 | #endif 568 | } 569 | if (lidx != NULL && lidx->line_number == linenum) { 570 | DDEBUG_PRINTF("index_find: Returning index for line %d.\n", linenum); 571 | return lidx->program_text_position; 572 | } 573 | DEBUG_PRINTF("index_find: Returning NULL for %d.\n", linenum); 574 | return NULL; 575 | } 576 | /*---------------------------------------------------------------------------*/ 577 | static char const *index_find_by_pos(const char *pos) { 578 | struct line_index *lidx; 579 | lidx = line_index_head; 580 | 581 | #if DEBUG 582 | int step = 0; 583 | #endif 584 | 585 | while (lidx != NULL && lidx->program_text_position != pos) { 586 | lidx = lidx->next; 587 | 588 | #if DEBUG 589 | if (lidx != NULL) { 590 | DDEBUG_PRINTF( 591 | "index_find_by_pos: Step %3d. Found index for %p at line %d: %p.\n", 592 | step, pos, lidx->line_number, (void *)lidx->program_text_position); 593 | } 594 | step++; 595 | #endif 596 | } 597 | if (lidx != NULL && lidx->program_text_position == pos) { 598 | DDEBUG_PRINTF("index_find_by_pos: Returning index for line %d.\n", 599 | lidx->line_number); 600 | return lidx->program_text_position; 601 | } 602 | DDEBUG_PRINTF("index_find_by_pos: Returning NULL for %p.\n", pos); 603 | return NULL; 604 | } 605 | 606 | /*---------------------------------------------------------------------------*/ 607 | static int linenum_find_by_pos(const char *pos) { 608 | struct line_index *lidx; 609 | lidx = line_index_head; 610 | 611 | #if DEBUG 612 | int step = 0; 613 | #endif 614 | 615 | while (lidx != NULL && lidx->program_text_position != pos) { 616 | lidx = lidx->next; 617 | 618 | #if DEBUG 619 | if (lidx != NULL) { 620 | DDEBUG_PRINTF( 621 | "linenum_find_by_pos: Step %3d. Found index for %p at line %d: %p.\n", 622 | step, pos, lidx->line_number, (void *)lidx->program_text_position); 623 | } 624 | step++; 625 | #endif 626 | } 627 | if (lidx != NULL && lidx->program_text_position == pos) { 628 | DDEBUG_PRINTF("linenum_find_by_pos: Returning line %d for index %p.\n", 629 | lidx->line_number, pos); 630 | return lidx->line_number; 631 | } 632 | DDEBUG_PRINTF("linenum_find_by_pos: Returning NULL for %p.\n", pos); 633 | return -1; 634 | } 635 | /*---------------------------------------------------------------------------*/ 636 | static char const *index_find_by_label(char *label) { 637 | struct line_index *lidx; 638 | lidx = line_index_head; 639 | 640 | #if DEBUG 641 | int step = 0; 642 | #endif 643 | 644 | while ((lidx != NULL) && (strcmp(lidx->label, label) != 0)) { 645 | lidx = lidx->next; 646 | 647 | #if DEBUG 648 | if (lidx != NULL) { 649 | DDEBUG_PRINTF( 650 | "index_find_by_label: Step %3d. Found index for label %s: %p.\n", 651 | step, lidx->label, (void *)lidx->program_text_position); 652 | } 653 | step++; 654 | #endif 655 | } 656 | if ((lidx != NULL) && (strcmp(lidx->label, label) == 0)) { 657 | DDEBUG_PRINTF("index_find_by_label: Returning index for label %s.\n", label); 658 | return lidx->program_text_position; 659 | } 660 | DDEBUG_PRINTF("index_find_by_label: Returning NULL for %s.\n", label); 661 | return NULL; 662 | } 663 | /*---------------------------------------------------------------------------*/ 664 | static void index_add_label(int linenum, char *label) { 665 | struct line_index *lidx; 666 | lidx = line_index_head; 667 | 668 | #if DEBUG 669 | int step = 0; 670 | #endif 671 | 672 | while (lidx != NULL && lidx->line_number != linenum) { 673 | lidx = lidx->next; 674 | 675 | #if DEBUG 676 | if (lidx != NULL) { 677 | DDEBUG_PRINTF("index_add_label: Step %3d. Found index for line %d: %p.\n", 678 | step, lidx->line_number, 679 | (void *)lidx->program_text_position); 680 | } 681 | step++; 682 | #endif 683 | } 684 | if (lidx != NULL && lidx->line_number == linenum) { 685 | DDEBUG_PRINTF("index_add_label: Setting label to %s for line %d.\n", label, 686 | linenum); 687 | strcpy(lidx->label, label); 688 | return; 689 | } 690 | DDEBUG_PRINTF("index_add_label: Label not set for line %d.\n", linenum); 691 | return; 692 | } 693 | 694 | /*---------------------------------------------------------------------------*/ 695 | static void index_add(int linenum, char const *sourcepos) { 696 | if (line_index_head != NULL && index_find(linenum)) { 697 | return; 698 | } 699 | 700 | struct line_index *new_lidx; 701 | 702 | new_lidx = malloc(sizeof(struct line_index)); 703 | new_lidx->line_number = linenum; 704 | new_lidx->program_text_position = sourcepos; 705 | new_lidx->next = NULL; 706 | 707 | if (line_index_head != NULL) { 708 | line_index_current->next = new_lidx; 709 | line_index_current = line_index_current->next; 710 | } else { 711 | line_index_current = new_lidx; 712 | line_index_head = line_index_current; 713 | } 714 | DDEBUG_PRINTF("index_add: Adding index for line %d: %p.\n", linenum, 715 | (void *)sourcepos); 716 | } 717 | /*---------------------------------------------------------------------------*/ 718 | static void jump_linenum_slow(int linenum) { 719 | int lc = 1; 720 | int last_token = TOKENIZER_ERROR; 721 | int err_lc = gline_number - 1; 722 | 723 | DEBUG_PRINTF("jump_linenum_slow: start\n"); 724 | tokenizer_init(program_ptr); 725 | do { 726 | last_token = tokenizer_token(); 727 | if (last_token == TOKENIZER_CR) 728 | lc++; 729 | 730 | tokenizer_next(); 731 | if (lc == linenum) { 732 | gline_number = lc; 733 | DEBUG_PRINTF("## jump_linenum_slow: returning at line %d\n", lc); 734 | return; 735 | } 736 | } while (tokenizer_token() != TOKENIZER_ENDOFINPUT); 737 | printf("Error: On line %d, line %d not found\n", err_lc, linenum); 738 | } 739 | /*---------------------------------------------------------------------------*/ 740 | static void jump_linenum(int linenum) { 741 | char const *pos = index_find(linenum); 742 | DEBUG_PRINTF("jump_linenum: Trying to go to line %d.\n", linenum); 743 | if (pos != NULL) { 744 | DEBUG_PRINTF("jump_linenum: Going to line %d.\n", linenum); 745 | tokenizer_goto(pos); 746 | } else { 747 | /* We'll try to find a yet-unindexed line to jump to. */ 748 | DEBUG_PRINTF("jump_linenum: Calling jump_linenum_slow for %d.\n", linenum); 749 | jump_linenum_slow(linenum); 750 | } 751 | } 752 | /*---------------------------------------------------------------------------*/ 753 | static void jump_label_slow(char *label) { 754 | char l[MAX_LABELLEN]; 755 | int last_token = TOKENIZER_ERROR; 756 | int lc = 1; 757 | int err_lc = gline_number - 1; 758 | 759 | DEBUG_PRINTF("jump_label_slow: start\n"); 760 | tokenizer_init(program_ptr); 761 | do { 762 | last_token = tokenizer_token(); 763 | if (last_token == TOKENIZER_CR) 764 | lc++; 765 | tokenizer_next(); 766 | if ((tokenizer_token() == TOKENIZER_LABEL) && 767 | ((last_token != TOKENIZER_GOSUB) && (last_token != TOKENIZER_GOTO))) { 768 | tokenizer_label(l, MAX_LABELLEN); 769 | DEBUG_PRINTF( 770 | "== jump_label_slow: found a label %s with last_token of %d\n", l, 771 | last_token); 772 | if (strcmp(label, l) == 0) { 773 | gline_number = lc; 774 | DEBUG_PRINTF("## jump_label_slow: returning at line %d\n", lc); 775 | return; 776 | } 777 | } 778 | } while (tokenizer_token() != TOKENIZER_ENDOFINPUT); 779 | printf("Error: On line %d, label %s not found\n", err_lc, label); 780 | } 781 | /*---------------------------------------------------------------------------*/ 782 | static void jump_label(char *label) { 783 | char const *pos = index_find_by_label(label); 784 | DEBUG_PRINTF("jump_label: Trying to go to label %s.\n", label); 785 | if (pos != NULL) { 786 | DEBUG_PRINTF("jump_label: Going to label %s.\n", label); 787 | tokenizer_goto(pos); 788 | } else { 789 | /* We'll try to find a yet-unindexed line to jump to. */ 790 | DEBUG_PRINTF("jump_label: Calling jump_label_slow for %s.\n", label); 791 | jump_label_slow(label); 792 | } 793 | } 794 | /*---------------------------------------------------------------------------*/ 795 | static VARIABLE_TYPE builtin(int token, int p) { 796 | time_t seconds; 797 | 798 | if (token <= TOKENIZER_BUILTINS__START || token > TOKENIZER_BUILTINS__END) { 799 | printf("Error: Invalid builtin function %d (%d)\n", token, p); 800 | ubasic_exit(gline_number - 1, "Invalid builtin function", ubasic_exit_static_itoa(token)); 801 | } 802 | 803 | switch (token) { 804 | case TOKENIZER_ZERO: 805 | return 0; 806 | break; 807 | case TOKENIZER_NOT: 808 | if (p == 0) 809 | return 1; 810 | else 811 | return 0; 812 | break; 813 | case TOKENIZER_RANDINT: 814 | return abs((VARIABLE_TYPE)(RANDOM_NUM_SEED_x = 815 | 69069 * RANDOM_NUM_SEED_x + 362437)); 816 | break; 817 | case TOKENIZER_TIME: 818 | seconds = time(NULL); 819 | return (VARIABLE_TYPE)seconds; 820 | break; 821 | default: 822 | break; 823 | } 824 | 825 | return 0; 826 | } 827 | /*---------------------------------------------------------------------------*/ 828 | static VARFLOAT_TYPE builtinf(int token, VARFLOAT_TYPE p) { 829 | int r; 830 | 831 | if (token <= TOKENIZER_BUILTINSF__START || token > TOKENIZER_BUILTINSF__END) { 832 | printf("Error: Invalid builtinf function %d (%f)\n", token, p); 833 | ubasic_exit(gline_number - 1, "Invalid builtinf function", ubasic_exit_static_itoa(token)); 834 | } 835 | 836 | switch (token) { 837 | case TOKENIZER_SQR: 838 | return sqrt(p); 839 | break; 840 | case TOKENIZER_RND: 841 | r = abs((RANDOM_NUM_SEED_x = 69069 * RANDOM_NUM_SEED_x + 362437)); 842 | return (double)(r) / (double)INT_MAX; 843 | break; 844 | case TOKENIZER_ABS: 845 | return fabs(p); 846 | break; 847 | case TOKENIZER_ATN: 848 | return atan(p); 849 | break; 850 | case TOKENIZER_COS: 851 | return cos(p); 852 | break; 853 | case TOKENIZER_EXP: 854 | return exp(p); 855 | break; 856 | case TOKENIZER_LOG: 857 | return log(p); 858 | break; 859 | case TOKENIZER_SIN: 860 | return sin(p); 861 | break; 862 | case TOKENIZER_TAN: 863 | return tan(p); 864 | break; 865 | default: 866 | break; 867 | } 868 | 869 | return 0.0; 870 | } 871 | /*---------------------------------------------------------------------------*/ 872 | static VARSTRING_TYPE builtinstr(int token, VARSTRING_TYPE p) { 873 | int r; 874 | 875 | // if (token <= TOKENIZER_BUILTINSF__START || token > TOKENIZER_BUILTINSF__END) { 876 | // printf("Error: Invalid builtinf function %d (%f)\n", token, p); 877 | // ubasic_exit(-1); 878 | // } 879 | 880 | switch (token) { 881 | case TOKENIZER_LEN: 882 | return NULL; 883 | break; 884 | default: 885 | break; 886 | } 887 | 888 | return NULL; 889 | } 890 | /*---------------------------------------------------------------------------*/ 891 | static void goto_statement(void) { 892 | char l[MAX_LABELLEN]; 893 | accept(TOKENIZER_GOTO); 894 | DEBUG_PRINTF("Enter goto_statement\n"); 895 | tokenizer_label(l, MAX_LABELLEN); 896 | accept(TOKENIZER_LABEL); 897 | if (tokenizer_token() == TOKENIZER_CR) { 898 | accept(TOKENIZER_CR); 899 | } 900 | jump_label(l); 901 | } 902 | /*---------------------------------------------------------------------------*/ 903 | // Potential overflow bug 904 | static void printfloat(VARFLOAT_TYPE f) { 905 | 906 | char buff[48]; 907 | int len; 908 | len = snprintf(buff, sizeof buff, "%f", f); 909 | DEBUG_PRINTF("printfloat: %s\n", buff); 910 | char *p = buff + len - 1; 911 | while (*p == '0') { 912 | *p-- = 0; 913 | } 914 | if (*p == '.') { 915 | *(p + 1) = '0'; 916 | *(p + 2) = 0; 917 | } 918 | printf("%s", buff); 919 | } 920 | /*---------------------------------------------------------------------------*/ 921 | // Potential overflow bug 922 | static VARSTRING_TYPE sprintfloat(VARFLOAT_TYPE f) { 923 | 924 | char buff[48]; 925 | int len; 926 | len = snprintf(buff, sizeof buff, "%f", f); 927 | DEBUG_PRINTF("sprintfloat: %s\n", buff); 928 | char *p = buff + len - 1; 929 | while (*p == '0') { 930 | *p-- = 0; 931 | } 932 | if (*p == '.') { 933 | *(p + 1) = '0'; 934 | *(p + 2) = 0; 935 | } 936 | return strdup(buff); 937 | } 938 | /*---------------------------------------------------------------------------*/ 939 | static void print_statement(void) { 940 | accept(TOKENIZER_PRINT); 941 | do { 942 | DEBUG_PRINTF("Print loop\n"); 943 | if (tokenizer_token() == TOKENIZER_STRING) { 944 | tokenizer_string(string, sizeof(string)); 945 | printf("%s", string); 946 | tokenizer_next(); 947 | } else if (tokenizer_token() == TOKENIZER_VARSTRING) { 948 | printf("%s", exprs()); 949 | } else if (tokenizer_token() == TOKENIZER_COMMA) { 950 | printf(" "); 951 | tokenizer_next(); 952 | } else if (tokenizer_token() == TOKENIZER_SEMICOLON) { 953 | tokenizer_next(); 954 | } else if (tokenizer_token() == TOKENIZER_VARIABLE || 955 | tokenizer_token() == TOKENIZER_NUMBER || 956 | (tokenizer_token() > TOKENIZER_BUILTINS__START && tokenizer_token() < TOKENIZER_BUILTINS__END)) { 957 | printf("%d", expr()); 958 | } else if (tokenizer_token() == TOKENIZER_VARFLOAT || 959 | tokenizer_token() == TOKENIZER_NUMFLOAT || 960 | (tokenizer_token() > TOKENIZER_BUILTINSF__START && tokenizer_token() < TOKENIZER_BUILTINSF__END)) { 961 | printfloat(exprf()); 962 | } else { 963 | break; 964 | } 965 | } while (tokenizer_token() != TOKENIZER_CR && 966 | tokenizer_token() != TOKENIZER_ENDOFINPUT); 967 | printf("\n"); 968 | DEBUG_PRINTF("End of print\n"); 969 | tokenizer_next(); 970 | } 971 | /*---------------------------------------------------------------------------*/ 972 | static void os_statement(void) { 973 | accept(TOKENIZER_OS); 974 | do { 975 | DEBUG_PRINTF("OS loop\n"); 976 | if (tokenizer_token() == TOKENIZER_STRING) { 977 | tokenizer_string(string, sizeof(string)); 978 | system(string); 979 | tokenizer_next(); 980 | } else if (tokenizer_token() == TOKENIZER_VARSTRING) { 981 | system(exprs()); 982 | } else { 983 | break; 984 | } 985 | } while (tokenizer_token() != TOKENIZER_CR && 986 | tokenizer_token() != TOKENIZER_ENDOFINPUT); 987 | printf("\n"); 988 | DEBUG_PRINTF("End of OS statement\n"); 989 | tokenizer_next(); 990 | } 991 | /*---------------------------------------------------------------------------*/ 992 | static void if_statement(void) { 993 | int r; 994 | DEBUG_PRINTF("if_statement start\n"); 995 | accept(TOKENIZER_IF); 996 | 997 | r = relation(); 998 | DEBUG_PRINTF("if_statement: relation %d\n", r); 999 | accept(TOKENIZER_THEN); 1000 | if (r) { 1001 | statement(); 1002 | if (tokenizer_token() == TOKENIZER_ELSE) { 1003 | accept(TOKENIZER_ELSE); 1004 | while (tokenizer_token() != TOKENIZER_CR && 1005 | tokenizer_token() != TOKENIZER_ENDOFINPUT) { 1006 | tokenizer_next(); 1007 | } 1008 | } 1009 | if (tokenizer_token() == TOKENIZER_CR) { 1010 | accept(TOKENIZER_CR); 1011 | } 1012 | DEBUG_PRINTF("if_statement end of true\n"); 1013 | } else { 1014 | do { 1015 | tokenizer_next(); 1016 | } while (tokenizer_token() != TOKENIZER_ELSE && 1017 | tokenizer_token() != TOKENIZER_CR && 1018 | tokenizer_token() != TOKENIZER_ENDOFINPUT); 1019 | if (tokenizer_token() == TOKENIZER_ELSE) { 1020 | tokenizer_next(); 1021 | statement(); 1022 | } else if (tokenizer_token() == TOKENIZER_CR) { 1023 | tokenizer_next(); 1024 | } 1025 | DEBUG_PRINTF("if_statement end of false\n"); 1026 | } 1027 | DEBUG_PRINTF("if_statement end\n"); 1028 | } 1029 | /*---------------------------------------------------------------------------*/ 1030 | static void let_statement(void) { 1031 | int var; 1032 | 1033 | if (tokenizer_token() == TOKENIZER_VARIABLE) { 1034 | var = tokenizer_variable_num(); 1035 | 1036 | accept(TOKENIZER_VARIABLE); 1037 | accept(TOKENIZER_EQ); 1038 | ubasic_set_variable(var, expr()); 1039 | DEBUG_PRINTF("let_statement: assign %d to %d\n", variables[var], var); 1040 | if (tokenizer_token() == TOKENIZER_CR) 1041 | tokenizer_next(); 1042 | } else if (tokenizer_token() == TOKENIZER_VARFLOAT) { 1043 | var = tokenizer_variable_num(); 1044 | 1045 | accept(TOKENIZER_VARFLOAT); 1046 | accept(TOKENIZER_EQ); 1047 | ubasic_set_float_variable(var, exprf()); 1048 | DEBUG_PRINTF("let_statement: assign %f to %d\n", float_variables[var], var); 1049 | if (tokenizer_token() == TOKENIZER_CR) 1050 | tokenizer_next(); 1051 | } else { 1052 | // TOKENIZER_VARSTRING 1053 | var = tokenizer_variable_num(); 1054 | accept(TOKENIZER_VARSTRING); 1055 | accept(TOKENIZER_EQ); 1056 | ubasic_set_string_variable(var, exprs()); 1057 | DEBUG_PRINTF("let_statement: assign %s to %d\n", string_variables[var], var); 1058 | if (tokenizer_token() == TOKENIZER_CR) 1059 | tokenizer_next(); 1060 | } 1061 | } 1062 | /*---------------------------------------------------------------------------*/ 1063 | static void gosub_statement(void) { 1064 | char l[MAX_LABELLEN]; 1065 | accept(TOKENIZER_GOSUB); 1066 | DEBUG_PRINTF("Enter gosub_statement\n"); 1067 | tokenizer_label(l, MAX_LABELLEN); 1068 | accept(TOKENIZER_LABEL); 1069 | if (tokenizer_token() == TOKENIZER_CR) { 1070 | accept(TOKENIZER_CR); 1071 | } 1072 | 1073 | if (gosub_stack_ptr < MAX_GOSUB_STACK_DEPTH) { 1074 | gosub_stack[gosub_stack_ptr] = gline_number; 1075 | gosub_stack_ptr++; 1076 | jump_label(l); 1077 | } else { 1078 | printf("Error: gosub stack exhausted\n"); 1079 | ubasic_exit(gline_number - 1, "Gosub stack exhausted", ""); 1080 | } 1081 | } 1082 | /*---------------------------------------------------------------------------*/ 1083 | static void return_statement(void) { 1084 | accept(TOKENIZER_RETURN); 1085 | if (gosub_stack_ptr > 0) { 1086 | gosub_stack_ptr--; 1087 | jump_linenum(gosub_stack[gosub_stack_ptr]); 1088 | } else { 1089 | printf("Error: No matching return on line %d\n", gline_number - 1); 1090 | ubasic_exit(gline_number - 1, "No matching return", 0); 1091 | } 1092 | } 1093 | /*---------------------------------------------------------------------------*/ 1094 | static void next_statement(void) { 1095 | int var; 1096 | 1097 | accept(TOKENIZER_NEXT); 1098 | var = tokenizer_variable_num(); 1099 | accept(TOKENIZER_VARIABLE); 1100 | if (for_stack_ptr > 0 && var == for_stack[for_stack_ptr - 1].for_variable) { 1101 | ubasic_set_variable(var, ubasic_get_variable(var) + 1); 1102 | if (ubasic_get_variable(var) <= for_stack[for_stack_ptr - 1].to) { 1103 | jump_linenum(for_stack[for_stack_ptr - 1].line_after_for); 1104 | } else { 1105 | for_stack_ptr--; 1106 | accept(TOKENIZER_CR); 1107 | } 1108 | } else { 1109 | printf("Error: On line %d, unexpected next, no matching for\n", 1110 | gline_number - 1); 1111 | ubasic_exit(gline_number - 1, "Unexpected next, no matching for", ""); 1112 | } 1113 | } 1114 | /*---------------------------------------------------------------------------*/ 1115 | static void for_statement(void) { 1116 | int for_variable, to; 1117 | 1118 | accept(TOKENIZER_FOR); 1119 | for_variable = tokenizer_variable_num(); 1120 | accept(TOKENIZER_VARIABLE); 1121 | accept(TOKENIZER_EQ); 1122 | ubasic_set_variable(for_variable, expr()); 1123 | accept(TOKENIZER_TO); 1124 | to = expr(); 1125 | accept(TOKENIZER_CR); 1126 | 1127 | if (for_stack_ptr < MAX_FOR_STACK_DEPTH) { 1128 | for_stack[for_stack_ptr].line_after_for = gline_number; 1129 | for_stack[for_stack_ptr].for_variable = for_variable; 1130 | for_stack[for_stack_ptr].to = to; 1131 | DEBUG_PRINTF("for_statement: new for at %d, var %d, from %d to %d\n", 1132 | for_stack[for_stack_ptr].line_after_for, 1133 | for_stack[for_stack_ptr].for_variable, 1134 | variables[for_stack[for_stack_ptr].for_variable], 1135 | for_stack[for_stack_ptr].to); 1136 | 1137 | for_stack_ptr++; 1138 | } else { 1139 | printf("Error: On line %d, for stack depth exceeded (max: %d)\n", 1140 | gline_number - 1, MAX_FOR_STACK_DEPTH); 1141 | ubasic_exit(gline_number - 1, "for stack depth exceeded", ubasic_exit_static_itoa(MAX_FOR_STACK_DEPTH)); 1142 | } 1143 | } 1144 | /*---------------------------------------------------------------------------*/ 1145 | static void peek_statement(void) { 1146 | VARIABLE_TYPE peek_addr; 1147 | int var; 1148 | 1149 | accept(TOKENIZER_PEEK); 1150 | peek_addr = expr(); 1151 | accept(TOKENIZER_COMMA); 1152 | var = tokenizer_variable_num(); 1153 | accept(TOKENIZER_VARIABLE); 1154 | accept(TOKENIZER_CR); 1155 | 1156 | ubasic_set_variable(var, peek_function(peek_addr)); 1157 | } 1158 | /*---------------------------------------------------------------------------*/ 1159 | static void poke_statement(void) { 1160 | VARIABLE_TYPE poke_addr; 1161 | VARIABLE_TYPE value; 1162 | DEBUG_PRINTF("Enter poke_statement\n"); 1163 | accept(TOKENIZER_POKE); 1164 | poke_addr = expr(); 1165 | accept(TOKENIZER_COMMA); 1166 | value = expr(); 1167 | accept(TOKENIZER_CR); 1168 | 1169 | poke_function(poke_addr, value); 1170 | } 1171 | /*---------------------------------------------------------------------------*/ 1172 | static void end_statement(void) { 1173 | accept(TOKENIZER_END); 1174 | ended = 1; 1175 | } 1176 | /*---------------------------------------------------------------------------*/ 1177 | static void sleep_statement(void) { 1178 | DEBUG_PRINTF("Enter sleep_statement\n"); 1179 | accept(TOKENIZER_SLEEP); 1180 | 1181 | int sleep_value = expr(); 1182 | if (tokenizer_token() == TOKENIZER_CR) 1183 | tokenizer_next(); 1184 | sleep_ms(sleep_value * 1000); 1185 | } 1186 | /*---------------------------------------------------------------------------*/ 1187 | static void delay_statement(void) { 1188 | DEBUG_PRINTF("Enter delay_statement\n"); 1189 | accept(TOKENIZER_DELAY); 1190 | 1191 | int delay_value = expr(); 1192 | if (tokenizer_token() == TOKENIZER_CR) 1193 | tokenizer_next(); 1194 | sleep_ms(delay_value); 1195 | } 1196 | /*---------------------------------------------------------------------------*/ 1197 | static void randomize_statement(void) { 1198 | DEBUG_PRINTF("Enter randomize_statement\n"); 1199 | accept(TOKENIZER_RANDOMIZE); 1200 | 1201 | int randomize_value = expr(); 1202 | if (tokenizer_token() == TOKENIZER_CR) 1203 | tokenizer_next(); 1204 | RANDOM_NUM_SEED_x = randomize_value; 1205 | } 1206 | /*---------------------------------------------------------------------------*/ 1207 | static void push_statement(void) { 1208 | DEBUG_PRINTF("Enter push_statement\n"); 1209 | accept(TOKENIZER_PUSH); 1210 | 1211 | int push_value = expr(); 1212 | if (tokenizer_token() == TOKENIZER_CR) 1213 | tokenizer_next(); 1214 | if (int_stack_ptr < MAX_INT_STACK_DEPTH) { 1215 | int_stack[int_stack_ptr] = push_value; 1216 | int_stack_ptr++; 1217 | } else { 1218 | printf("Error: On line %d, integer stack exhausted\n", gline_number - 1); 1219 | ubasic_exit(gline_number - 1, "integer stack exhausted", ""); 1220 | } 1221 | DEBUG_PRINTF("Exit push_statement\n"); 1222 | } 1223 | /*---------------------------------------------------------------------------*/ 1224 | static void pop_statement(void) { 1225 | int var; 1226 | 1227 | DEBUG_PRINTF("Enter pop_statement\n"); 1228 | accept(TOKENIZER_POP); 1229 | 1230 | if (tokenizer_token() == TOKENIZER_VARIABLE) { 1231 | var = tokenizer_variable_num(); 1232 | accept(TOKENIZER_VARIABLE); 1233 | if (int_stack_ptr > 0) { 1234 | int_stack_ptr--; 1235 | ubasic_set_variable(var, int_stack[int_stack_ptr]); 1236 | DEBUG_PRINTF("pop_statement: assign %d to %d\n", variables[var], var); 1237 | } else { 1238 | printf("Error: On line %d, integer stack is empty\n", gline_number - 1); 1239 | ubasic_exit(gline_number - 1, "integer stack is empty", ""); 1240 | } 1241 | } 1242 | if (tokenizer_token() == TOKENIZER_CR) 1243 | tokenizer_next(); 1244 | } 1245 | /*--------------------------------------------------------------------------- 1246 | * GPIO functions 1247 | *---------------------------------------------------------------------------*/ 1248 | static void gpio_init_statement(void) { 1249 | DEBUG_PRINTF("Enter gpio_init_statement\n"); 1250 | accept(TOKENIZER_GPIOINIT); 1251 | 1252 | int pin = expr(); 1253 | if (tokenizer_token() == TOKENIZER_CR) 1254 | tokenizer_next(); 1255 | gpio_init(pin); 1256 | } 1257 | /*---------------------------------------------------------------------------*/ 1258 | static void gpio_dir_in_statement(void) { 1259 | DEBUG_PRINTF("Enter gpio_dir_in_statement\n"); 1260 | accept(TOKENIZER_GPIODIRIN); 1261 | 1262 | int pin = expr(); 1263 | if (tokenizer_token() == TOKENIZER_CR) 1264 | tokenizer_next(); 1265 | gpio_set_dir(pin, GPIO_IN); 1266 | } 1267 | /*---------------------------------------------------------------------------*/ 1268 | static void gpio_dir_out_statement(void) { 1269 | DEBUG_PRINTF("Enter gpio_dir_out_statement\n"); 1270 | accept(TOKENIZER_GPIODIROUT); 1271 | 1272 | int pin = expr(); 1273 | if (tokenizer_token() == TOKENIZER_CR) 1274 | tokenizer_next(); 1275 | gpio_set_dir(pin, GPIO_OUT); 1276 | } 1277 | /*---------------------------------------------------------------------------*/ 1278 | static void gpio_on_statement(void) { 1279 | DEBUG_PRINTF("Enter gpio_on_statement\n"); 1280 | accept(TOKENIZER_GPIOON); 1281 | 1282 | int pin = expr(); 1283 | if (tokenizer_token() == TOKENIZER_CR) 1284 | tokenizer_next(); 1285 | gpio_put(pin, 1); 1286 | } 1287 | /*---------------------------------------------------------------------------*/ 1288 | static void gpio_off_statement(void) { 1289 | DEBUG_PRINTF("Enter gpio_off_statement\n"); 1290 | accept(TOKENIZER_GPIOOFF); 1291 | 1292 | int pin = expr(); 1293 | if (tokenizer_token() == TOKENIZER_CR) 1294 | tokenizer_next(); 1295 | gpio_put(pin, 0); 1296 | } 1297 | /*--------------------------------------------------------------------------- 1298 | * End GPIO functions 1299 | *---------------------------------------------------------------------------*/ 1300 | 1301 | static void label_statement(void) { 1302 | char l[MAX_LABELLEN]; 1303 | DEBUG_PRINTF("Enter label_statement\n"); 1304 | tokenizer_label(l, MAX_LABELLEN); 1305 | DEBUG_PRINTF("Found label %s\n", l); 1306 | index_add_label(gline_number - 1, l); 1307 | accept(TOKENIZER_LABEL); 1308 | accept(TOKENIZER_CR); 1309 | DEBUG_PRINTF("End label_statement\n"); 1310 | } 1311 | /*---------------------------------------------------------------------------*/ 1312 | static void statement(void) { 1313 | int token; 1314 | 1315 | token = tokenizer_token(); 1316 | 1317 | switch (token) { 1318 | case TOKENIZER_PRINT: 1319 | print_statement(); 1320 | break; 1321 | case TOKENIZER_IF: 1322 | if_statement(); 1323 | break; 1324 | case TOKENIZER_GOTO: 1325 | goto_statement(); 1326 | break; 1327 | case TOKENIZER_GOSUB: 1328 | gosub_statement(); 1329 | break; 1330 | case TOKENIZER_RETURN: 1331 | return_statement(); 1332 | break; 1333 | case TOKENIZER_FOR: 1334 | for_statement(); 1335 | break; 1336 | case TOKENIZER_PEEK: 1337 | peek_statement(); 1338 | break; 1339 | case TOKENIZER_POKE: 1340 | poke_statement(); 1341 | break; 1342 | case TOKENIZER_SLEEP: 1343 | sleep_statement(); 1344 | break; 1345 | case TOKENIZER_DELAY: 1346 | delay_statement(); 1347 | break; 1348 | case TOKENIZER_RANDOMIZE: 1349 | randomize_statement(); 1350 | break; 1351 | case TOKENIZER_POP: 1352 | pop_statement(); 1353 | break; 1354 | case TOKENIZER_PUSH: 1355 | push_statement(); 1356 | break; 1357 | case TOKENIZER_OS: 1358 | os_statement(); 1359 | break; 1360 | case TOKENIZER_NEXT: 1361 | next_statement(); 1362 | break; 1363 | case TOKENIZER_END: 1364 | end_statement(); 1365 | break; 1366 | case TOKENIZER_LABEL: 1367 | label_statement(); 1368 | break; 1369 | case TOKENIZER_GPIOINIT: 1370 | gpio_init_statement(); 1371 | break; 1372 | case TOKENIZER_GPIODIRIN: 1373 | gpio_dir_in_statement(); 1374 | break; 1375 | case TOKENIZER_GPIODIROUT: 1376 | gpio_dir_out_statement(); 1377 | break; 1378 | case TOKENIZER_GPIOON: 1379 | gpio_on_statement(); 1380 | break; 1381 | case TOKENIZER_GPIOOFF: 1382 | gpio_off_statement(); 1383 | break; 1384 | case TOKENIZER_LET: 1385 | accept(TOKENIZER_LET); 1386 | /* Fall through. */ 1387 | case TOKENIZER_VARIABLE: 1388 | case TOKENIZER_VARFLOAT: 1389 | case TOKENIZER_VARSTRING: 1390 | let_statement(); 1391 | break; 1392 | default: 1393 | printf("Error: On line %d, unknown statement(): %d\n", gline_number - 1, 1394 | token); 1395 | ubasic_exit(gline_number, "unknown statement()", ""); 1396 | } 1397 | } 1398 | /*---------------------------------------------------------------------------*/ 1399 | static void line_statement(void) { 1400 | // DEBUG_PRINTF("----------- Line number %d ---------\n", tokenizer_num()); 1401 | // index_add(tokenizer_num(), tokenizer_pos()); 1402 | // accept(TOKENIZER_NUMBER); 1403 | int cline_number = linenum_find_by_pos(tokenizer_pos()); 1404 | if (cline_number < 0) { 1405 | DEBUG_PRINTF("----------- New Line number %d ---------\n", gline_number); 1406 | index_add(gline_number++, tokenizer_pos()); 1407 | } else { 1408 | DEBUG_PRINTF("----------- Line number %d ---------\n", cline_number); 1409 | gline_number = cline_number + 1; 1410 | } 1411 | 1412 | statement(); 1413 | return; 1414 | } 1415 | /*---------------------------------------------------------------------------*/ 1416 | void ubasic_run(void) { 1417 | if (tokenizer_finished()) { 1418 | DEBUG_PRINTF("uBASIC program finished\n"); 1419 | return; 1420 | } 1421 | 1422 | line_statement(); 1423 | check_if_should_enter_CMD_mode(); 1424 | } 1425 | /*---------------------------------------------------------------------------*/ 1426 | int ubasic_finished(void) { return ended || tokenizer_finished(); } 1427 | /*---------------------------------------------------------------------------*/ 1428 | void ubasic_set_variable(int varnum, VARIABLE_TYPE value) { 1429 | if (varnum >= 0 && varnum <= MAX_VARNUM) { 1430 | variables[varnum] = value; 1431 | } 1432 | } 1433 | /*---------------------------------------------------------------------------*/ 1434 | VARIABLE_TYPE 1435 | ubasic_get_variable(int varnum) { 1436 | if (varnum >= 0 && varnum <= MAX_VARNUM) { 1437 | return variables[varnum]; 1438 | } 1439 | return 0; 1440 | } 1441 | /*---------------------------------------------------------------------------*/ 1442 | void ubasic_set_float_variable(int varnum, VARFLOAT_TYPE value) { 1443 | if (varnum >= 0 && varnum <= MAX_VARNUM) { 1444 | float_variables[varnum] = value; 1445 | } 1446 | } 1447 | /*---------------------------------------------------------------------------*/ 1448 | VARFLOAT_TYPE ubasic_get_float_variable(int varnum) { 1449 | if (varnum >= 0 && varnum <= MAX_VARNUM) { 1450 | return float_variables[varnum]; 1451 | } 1452 | return 0; 1453 | } 1454 | /*---------------------------------------------------------------------------*/ 1455 | void ubasic_set_string_variable(int varnum, VARSTRING_TYPE value) { 1456 | if (varnum >= 0 && varnum <= MAX_VARNUM) { 1457 | if(string_variables[varnum]!=NULL) 1458 | free(string_variables[varnum]); 1459 | string_variables[varnum] = malloc(strlen(value) + 1); 1460 | strcpy(string_variables[varnum], value); 1461 | } 1462 | } 1463 | /*---------------------------------------------------------------------------*/ 1464 | VARSTRING_TYPE ubasic_get_string_variable(int varnum) { 1465 | if (varnum >= 0 && varnum <= MAX_VARNUM) { 1466 | return string_variables[varnum]; 1467 | } 1468 | return 0; 1469 | } 1470 | -------------------------------------------------------------------------------- /ubasic.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006, Adam Dunkels 3 | * Copyright (c) 2023, Gary Sims 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 3. Neither the name of the author nor the names of its contributors 15 | * may be used to endorse or promote products derived from this software 16 | * without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 | * SUCH DAMAGE. 29 | * 30 | */ 31 | #ifndef __UBASIC_H__ 32 | #define __UBASIC_H__ 33 | 34 | #include "vartype.h" 35 | 36 | typedef VARIABLE_TYPE (*peek_func)(VARIABLE_TYPE); 37 | typedef void (*poke_func)(VARIABLE_TYPE, VARIABLE_TYPE); 38 | 39 | void ubasic_init(const char *program); 40 | void ubasic_run(void); 41 | int ubasic_finished(void); 42 | void ubasic_exit(int errline, char *errmsg, char *errp); 43 | 44 | VARIABLE_TYPE ubasic_get_variable(int varnum); 45 | void ubasic_set_variable(int varum, VARIABLE_TYPE value); 46 | 47 | VARFLOAT_TYPE ubasic_get_float_variable(int varnum); 48 | void ubasic_set_float_variable(int varum, VARFLOAT_TYPE value); 49 | 50 | VARSTRING_TYPE ubasic_get_string_variable(int varnum); 51 | void ubasic_set_string_variable(int varum, VARSTRING_TYPE value); 52 | 53 | void ubasic_init_peek_poke(const char *program, peek_func peek, poke_func poke); 54 | void poke(VARIABLE_TYPE arg, VARIABLE_TYPE value); 55 | 56 | #endif /* __UBASIC_H__ */ 57 | -------------------------------------------------------------------------------- /vartype.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006, Adam Dunkels 3 | * Copyright (c) 2013, Danyil Bohdan 4 | * Copyright (c) 2023, Gary Sims 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 3. Neither the name of the author nor the names of its contributors 16 | * may be used to endorse or promote products derived from this software 17 | * without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 20 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 | * SUCH DAMAGE. 30 | * 31 | */ 32 | #ifndef __VARTYPE_H__ 33 | #define __VARTYPE_H__ 34 | 35 | #include 36 | 37 | #define VARIABLE_TYPE int 38 | #define VARFLOAT_TYPE double 39 | #define VARSTRING_TYPE char * 40 | 41 | #endif /* __VARTYPE_H__ */ 42 | --------------------------------------------------------------------------------