mirror of
https://github.com/clockworkpi/PicoCalc.git
synced 2026-03-19 02:22:37 +01:00
update PicoCalc SD
This commit is contained in:
BIN
Bin/PicoCalc SD/BOOT2040.uf2
Normal file
BIN
Bin/PicoCalc SD/BOOT2040.uf2
Normal file
Binary file not shown.
216
Bin/PicoCalc SD/MACHIKAP.INI
Normal file
216
Bin/PicoCalc SD/MACHIKAP.INI
Normal file
@@ -0,0 +1,216 @@
|
||||
# MachiKania system
|
||||
# initialization file
|
||||
|
||||
# Specify the autoexec file
|
||||
|
||||
AUTOEXEC=MACHIKAP.BAS
|
||||
|
||||
|
||||
# Set the direction of LCD
|
||||
|
||||
# HORIZONTAL # same as LCD0TURN
|
||||
VERTICAL # same as LCD270TURN
|
||||
# LCD180TURN
|
||||
# LCD90TURN
|
||||
|
||||
|
||||
# When using IPS-LCD, activate following line
|
||||
|
||||
LCDINVERT
|
||||
|
||||
|
||||
# Setup Initial Text width (42 or 80 for Type PU)
|
||||
|
||||
# WIDTH42
|
||||
# WIDTH80
|
||||
|
||||
|
||||
# Decide if rotate button assignment when LCD vertical setting
|
||||
|
||||
ROTATEBUTTONS
|
||||
# NOROTATEBUTTONS
|
||||
|
||||
|
||||
# Decide if output to USB serial port
|
||||
|
||||
USBSERIALON
|
||||
# USBSERIALOFF
|
||||
|
||||
|
||||
# Decide if emulate buttons by keyboard by setting key codes
|
||||
# Default:
|
||||
# UP button: UP key
|
||||
# DOWN button: DOWN key
|
||||
# LEFT button: LEFT key
|
||||
# RIGHT button: RIGHT key
|
||||
# START button: Enter key
|
||||
# FIRE button: Space key
|
||||
|
||||
EMULATEBUTTONUP=38
|
||||
EMULATEBUTTONDOWN=40
|
||||
EMULATEBUTTONLEFT=37
|
||||
EMULATEBUTTONRIGHT=39
|
||||
EMULATEBUTTONSTART=13
|
||||
EMULATEBUTTONFIRE=32
|
||||
|
||||
|
||||
# Decide if output to LCD
|
||||
|
||||
LCDOUTON
|
||||
# LCDOUTOFF
|
||||
|
||||
|
||||
# Decide if wait for 2.5 seconds in the beginning in debug mode
|
||||
|
||||
DEBUGWAIT2500
|
||||
# NODEBUGWAIT2500
|
||||
|
||||
|
||||
# Infinite loop or reset at the end of program
|
||||
|
||||
LOOPATEND
|
||||
# RESETATEND
|
||||
|
||||
|
||||
# Waiting time at the beginning in milli seconds (must be more than 499)
|
||||
|
||||
STARTWAIT=500
|
||||
|
||||
|
||||
# What to do when an exception occurs
|
||||
|
||||
# EXCRESET # Reset Machikania (default: off)
|
||||
# EXCSCREENSHOT=EXCSCRS.BIN # Save screen shot as a file (default: off)
|
||||
# EXCDUMP=EXDUMP.BIN # Dump memory to a file (default: off)
|
||||
|
||||
|
||||
# Waiting time for USB keyboard connection in milli seconds (0: infinite)
|
||||
|
||||
#WAIT4KEYBOARD=0
|
||||
WAIT4KEYBOARD=2000
|
||||
|
||||
|
||||
# Specify the keyboard type
|
||||
# Enable the keyword below
|
||||
|
||||
# 106KEY # Japanese Keyboard
|
||||
101KEY # US Keyboard
|
||||
|
||||
|
||||
# Setup Lock keys' status
|
||||
# Comment out if not lock
|
||||
# the key when initializing
|
||||
|
||||
NUMLOCK # Num Lock Key
|
||||
CAPSLOCK # Caps Lock Key
|
||||
# SCRLLOCK # Scroll Lock Key
|
||||
|
||||
|
||||
# Specify SPI pins
|
||||
# Comment out for default settings
|
||||
# Useful for small RP2040 boards like XIAO RP2040, RP2040-Zero, and Tiny-2040
|
||||
# Valid values for SPIMISO: 0, 4, 16, 20 (SPI0)
|
||||
# SPIMISO: 8, 12, 24, 28 (SPI1)
|
||||
# Valid values for SPIMOSI: 3, 7, 19, 23 (SPI0)
|
||||
# SPIMOSI: 11, 15, 27 (SPI1)
|
||||
# Valid values for SPICLK: 2, 6, 18, 22 (SPI0)
|
||||
# SPICLK: 10, 14, 26 (SPI1)
|
||||
|
||||
# SPIMISO=4
|
||||
# SPIMOSI=7
|
||||
# SPICLK=6
|
||||
|
||||
|
||||
# Specify UART pins
|
||||
# Comment out for default settings
|
||||
# Valid values for UARTTX: 0, 12, 16, 28 (UART0)
|
||||
# UARTTX: 4, 8, 20, 24 (UART1)
|
||||
# Valid values for UARTRX: 1, 13, 17, 29 (UART0)
|
||||
# UARTRX: 5, 9, 21, 25 (UART1)
|
||||
|
||||
# UARTTX=4
|
||||
# UARTRX=5
|
||||
|
||||
|
||||
# Specify I2C pins
|
||||
# Comment out for default settings
|
||||
# Note that I2C1 cannot be used for PicoCalc as it is used for keyboard
|
||||
# Valid values for I2CSDA: 0, 4, 8, 12, 16, 20, 24, 28 (I2C0)
|
||||
# I2CSDA: 2, 6, 10, 14, 18, 22, 26 (I2C1)
|
||||
# Valid values for I2CSCL: 1, 5, 9, 13, 17, 21, 25, 29 (I2C0)
|
||||
# I2CSCL: 3, 7, 11, 15, 19, 23, 27 (I2C1)
|
||||
|
||||
# I2CSDA=0
|
||||
# I2CSCL=1
|
||||
|
||||
|
||||
# Use Real Time Clock (RTC) for saving files by file system
|
||||
# If RTC is not use, timestamp of save file will be 2020/01/01
|
||||
# Remove comment to enable RTC for file system in following line
|
||||
|
||||
# RTCFILE
|
||||
|
||||
# Timezone used for RTC setting by NTP
|
||||
|
||||
# TIMEZONE=9 # JST
|
||||
# TIMEZONE=8.5 # ACST
|
||||
# TIMEZONE=5.75 # Nepal Standard Time
|
||||
TIMEZONE=0 # UTC
|
||||
# TIMEZONE=-8 # PST
|
||||
# TIMEZONE=-7 # PDT
|
||||
# TIMEZONE=-5 # EST
|
||||
# TIMEZONE=-4 # EDT
|
||||
|
||||
|
||||
# Connect to wifi when starting (Pico W only)
|
||||
|
||||
# USEWIFI
|
||||
|
||||
|
||||
# Set country code for wifi connection (Pico W only)
|
||||
|
||||
WIFICOUNTRY=JP
|
||||
# WIFICOUNTRY=US
|
||||
|
||||
|
||||
# Define wifi SSID and password (Pico W only)
|
||||
|
||||
# WIFISSID=xxxx
|
||||
# WIFIPASSWD=xxxx
|
||||
|
||||
|
||||
# Define hostname (Pico W only; default: PicoW)
|
||||
|
||||
# HOSTNAME=machikania
|
||||
|
||||
|
||||
# Define NTP server (Pico W only)
|
||||
|
||||
NTPSERVER=pool.ntp.org
|
||||
|
||||
|
||||
# Enable following line to connect to NTP server in the beginning after power on (Pico W only)
|
||||
# Make sure that USEWIFI is enabled when using this feature
|
||||
|
||||
# INITIALNTP
|
||||
|
||||
# Enable following line to show timestamp when selecting files
|
||||
# SHOWTIMESTAMP
|
||||
|
||||
# Sort order when selecting files
|
||||
# 0:A...Z 1:Z...A 2:OLD...NEW 3:NEW...OLD
|
||||
FILESORTBY=0
|
||||
|
||||
|
||||
# Set help file used in editor
|
||||
# Use help-e.txt for English help
|
||||
# Use help-k.txt for Japanese Kana help
|
||||
|
||||
HELPFILE=/docs/help-e.txt
|
||||
#HELPFILE=/docs/help-k.txt
|
||||
|
||||
|
||||
# Assign the additional PWM (4-9) to GPIO number
|
||||
# Note that this is not MachiKania port number, but RP2040/2350 GPIO number (0-29)
|
||||
|
||||
# PWM4=21
|
||||
@@ -1,75 +1,209 @@
|
||||
# PicoCalc SD
|
||||
# PicoCalc SD v0.6
|
||||
|
||||
This repository contains the official factory image and files for the **PicoCalc SD card**. It includes essential firmware, applications, and system files required for the proper functioning of **PicoCalc**.
|
||||
|
||||
## Directory Overview
|
||||
|
||||
```
|
||||
PicoCalc SD/
|
||||
├── BellLabs_Fine.mp3
|
||||
├── bifdiag.bas
|
||||
├── BOOT2040.uf2
|
||||
├── bootloader_pico.uf2
|
||||
├── cc
|
||||
│ ├── edit.lua
|
||||
│ ├── expect.lua
|
||||
│ ├── internal
|
||||
│ │ ├── menu.lua
|
||||
│ │ └── syntax
|
||||
│ │ ├── errors.lua
|
||||
│ │ ├── init.lua
|
||||
│ │ ├── lexer.lua
|
||||
│ │ └── parser.lua
|
||||
│ └── pretty.lua
|
||||
├── Chessnovice_johnybot.nes
|
||||
├── firmware
|
||||
│ ├── PicoCalc_Fuzix_v1.0.img
|
||||
├── fonts
|
||||
│ ├── 6x10.fnt
|
||||
│ ├── Acer8x8.fnt
|
||||
│ ├── Haxor12.fnt
|
||||
│ ├── HP6x8.fnt
|
||||
│ ├── HP8x8.fnt
|
||||
│ └── ProggyClean.fnt
|
||||
├── lorenz.bas
|
||||
├── lua
|
||||
│ ├── asteroids.lua
|
||||
│ ├── boxworld.bmp
|
||||
│ ├── boxworld.lua
|
||||
│ ├── browser.lua
|
||||
│ ├── bubble.lua
|
||||
│ ├── mandelbrot.lua
|
||||
│ └── piano.lua
|
||||
├── MACHIKAP.INI
|
||||
├── main.lua
|
||||
├── mand.bas
|
||||
├── pico1-apps
|
||||
│ ├── MicroPython_fa8b24c.uf2
|
||||
│ ├── phyllosoma_kb.uf2
|
||||
│ ├── PicoCalc_Fuzix_v1.0.uf2
|
||||
│ ├── PicoCalc_MP3Player_v0.5.uf2
|
||||
│ ├── PicoCalc_NES_v1.0.uf2
|
||||
│ ├── PicoCalc_PicoMite_v1.0.uf2
|
||||
│ ├── PicoCalc_stm32 #PicoCalc keyboard firmware
|
||||
│ │ ├── PicoCalc_firmware_v1.0.bin
|
||||
│ │ └── PicoCalc_firmware_v1.0.hex
|
||||
│ └── PicoCalc_uLisp_v1.0.uf2
|
||||
├── lorenz.bas
|
||||
├── mand.bas
|
||||
│ ├── PicoCalc_uLisp_v1.1.uf2
|
||||
│ ├── picolua_daf20a2.uf2
|
||||
│ ├── PicoMite_v6.02.01b4_beta.uf2
|
||||
│ └── Picoware_v1.6.9.uf2
|
||||
├── picocalc.bmp
|
||||
├── picoware
|
||||
│ ├── apps
|
||||
│ │ ├── Calculator.mpy
|
||||
│ │ ├── cat-fact.mpy
|
||||
│ │ ├── counter.mpy
|
||||
│ │ ├── flip_social
|
||||
│ │ │ ├── __init__.py
|
||||
│ │ │ ├── password.mpy
|
||||
│ │ │ ├── run.mpy
|
||||
│ │ │ ├── settings.mpy
|
||||
│ │ │ └── username.mpy
|
||||
│ │ ├── FlipSocial.mpy
|
||||
│ │ ├── games
|
||||
│ │ │ ├── 2048.mpy
|
||||
│ │ │ ├── Breakout.mpy
|
||||
│ │ │ ├── example.mpy
|
||||
│ │ │ ├── Flappy Bird.mpy
|
||||
│ │ │ ├── flip_world
|
||||
│ │ │ │ ├── assets.mpy
|
||||
│ │ │ │ ├── general.mpy
|
||||
│ │ │ │ ├── __init__.py
|
||||
│ │ │ │ ├── player.mpy
|
||||
│ │ │ │ ├── run.mpy
|
||||
│ │ │ │ └── sprite.mpy
|
||||
│ │ │ ├── FlipWorld.mpy
|
||||
│ │ │ ├── free_roam
|
||||
│ │ │ │ ├── dynamic_map.mpy
|
||||
│ │ │ │ ├── game.mpy
|
||||
│ │ │ │ ├── __init__.py
|
||||
│ │ │ │ ├── maps.mpy
|
||||
│ │ │ │ ├── player.mpy
|
||||
│ │ │ │ └── sprite.mpy
|
||||
│ │ │ ├── Free Roam.mpy
|
||||
│ │ │ ├── game_of_life.mpy
|
||||
│ │ │ ├── Maze Runner.mpy
|
||||
│ │ │ ├── Minesweeper.mpy
|
||||
│ │ │ ├── Pong.mpy
|
||||
│ │ │ ├── Snake.mpy
|
||||
│ │ │ ├── Soduko.mpy
|
||||
│ │ │ ├── Space Invaders.mpy
|
||||
│ │ │ ├── Tetris.mpy
|
||||
│ │ │ └── Tower Defense.mpy
|
||||
│ │ ├── Graph.mpy
|
||||
│ │ ├── hello_color.mpy
|
||||
│ │ ├── keyboard-simple.mpy
|
||||
│ │ ├── loading-simple.mpy
|
||||
│ │ ├── menu-simple.mpy
|
||||
│ │ ├── random-object.mpy
|
||||
│ │ ├── screensavers
|
||||
│ │ │ ├── Bubble Universe.mpy
|
||||
│ │ │ ├── Clock.mpy
|
||||
│ │ │ ├── Cube.mpy
|
||||
│ │ │ ├── DVI Bounce.mpy
|
||||
│ │ │ ├── Fire Effect.mpy
|
||||
│ │ │ ├── Matrix Rain.mpy
|
||||
│ │ │ ├── Patterns.mpy
|
||||
│ │ │ ├── PicoFlower.mpy
|
||||
│ │ │ ├── Plasma Wave.mpy
|
||||
│ │ │ └── Yin-Yang.mpy
|
||||
│ │ ├── Serial Terminal.mpy
|
||||
│ │ ├── storage-simple.mpy
|
||||
│ │ ├── textbox-simple.mpy
|
||||
│ │ ├── Text Editor.mpy
|
||||
│ │ └── Weather.mpy
|
||||
│ └── settings
|
||||
└── README.md
|
||||
|
||||
|
||||
Since PicoCalc SD v0.6 we used [uf2loader](https://github.com/pelrun/uf2loader.git) as main loader.
|
||||
|
||||
All uf2 files in folder **pico1-apps** will be showed up in menu.
|
||||
|
||||
Once a uf2 got flashed and ran, next time you can use menu item **[Default App]** to directly run it without flashing it again.
|
||||
|
||||
Important files:
|
||||
|
||||
* bootloader_pico.uf2
|
||||
uf2loader main program, should be flashed into pico.
|
||||
* BOOT2040.uf2
|
||||
uf2loader Menu UI, it is a very important file, do not delete or edit it unless you know what you are doing.
|
||||
|
||||
|
||||
## Path: pico1-apps/PicoCalc_Fuzix_v1.0.uf2 (Download)
|
||||
|
||||
[Fuzix](https://github.com/EtchedPixels/FUZIX.git) is an open-source, lightweight Unix-like operating system specifically designed for 8-bit and other resource-constrained processors.
|
||||
|
||||
Patches for PicoCalc are [here](https://github.com/clockworkpi/PicoCalc/tree/master/Code/FUZIX)
|
||||
|
||||
## Path: pico1-apps/PicoCalc_NES_v1.0.uf2 (Download)
|
||||
|
||||
A simple NES emulator for PicoCalc, it will scan all nes files in the root of SD card.
|
||||
Given the resource constraints of the Pico, it is recommended to only run NES games that are less than **44KB** in size.
|
||||
|
||||
## Path: pico1-apps/picolua_daf20a2.uf2 (Download)
|
||||
|
||||
https://github.com/Lana-chan/picocalc_lua
|
||||
A Lua interpreter for PicoCalc. It contains a REPL, basic API to draw graphics, read keys and access the SD filesystem.
|
||||
|
||||
## Path: pico1-apps/Picoware_v1.6.9.uf2 (Download)
|
||||
|
||||
https://github.com/jblanked/Picoware
|
||||
|
||||
An Open-source custom firmware for the PicoCalc, Video Game Module, and other Raspberry Pi Pico devices.
|
||||
|
||||
Here is the version based on MicroPython.
|
||||
|
||||
## Path: pico1-apps/phyllosoma_kb.uf2 (Download)
|
||||
|
||||
[MachiKania Phyllosoma](https://github.com/machikania/phyllosoma/releases) is a BASIC compiler for ARMv6-M with excellent performance., especially for Raspberry Pi Pico.
|
||||
|
||||
|
||||
## Path: pico1-apps/PicoCalc_MP3Player_v0.5.uf2 (Download)
|
||||
|
||||
[PicoCalc_MP3Player](https://github.com/clockworkpi/PicoCalc/tree/master/Code/MP3Player) is a simple MP3 playback program based on the [YAHAL](https://git.fh-aachen.de/Terstegge/YAHAL
|
||||
|
||||
) framework.
|
||||
|
||||
|
||||
## Path: pico1-apps/PicoCalc_uLisp_v1.1.uf2 (Download)
|
||||
|
||||
http://www.ulisp.com/show?56ZO
|
||||
|
||||
A self-contained Lisp computer for PicoCalc.
|
||||
|
||||
## Path: pico1-apps/PicoMite_v6.02.01b4_beta.uf2 (Download)
|
||||
|
||||
[The PicoMite](https://geoffg.net/picomite.html) is a complete operating system with a Microsoft BASIC compatible interpreter and extensive hardware support including touch sensitive LCD panels, SD Cards, WiFi/Internet and much more.
|
||||
|
||||
|
||||
```
|
||||
Copyright and Acknowledgments
|
||||
|
||||
The PicoMite firmware and MMBasic is copyright 2011-2025 by Geoff Graham and Peter Mather 2016-2025.
|
||||
1-Wire Support is copyright 1999-2006 Dallas Semiconductor Corporation and 2012 Gerard Sexton.
|
||||
FatFs (SD Card) driver is copyright 2014, ChaN.
|
||||
WAV, MP3, and FLAC file support is copyright 2019 David Reid.
|
||||
JPG support is thanks to Rich Geldreich
|
||||
The pico-sdk is copyright 2021 Raspberry Pi (Trading) Ltd.
|
||||
TinyUSB is copyright tinyusb.org
|
||||
LittleFS is copyright Christopher Haster
|
||||
Thomas Williams and Gerry Allardice for MMBasic enhancements
|
||||
The VGA driver code was derived from work by Miroslav Nemecek
|
||||
The CRC calculations are copyright Rob Tillaart
|
||||
The compiled object code (the .uf2 file) for the PicoMite firmware is free software: you can use or redistribute
|
||||
it as you please. The source code is on GitHub ( https://github.com/UKTailwind/PicoMiteAllVersions ) and
|
||||
can be freely used subject to some conditions (see the header in the source files).
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY, without even
|
||||
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
```
|
||||
|
||||
## Flashing the Factory SD Image
|
||||
|
||||
To restore your **PicoCalc SD card** to its factory state, follow these steps:
|
||||
## Path: pico1-apps/MicroPython_fa8b24c.uf2 (Download)
|
||||
|
||||
### Requirements:
|
||||
- A microSD card (at least **16GB** recommended)
|
||||
- A computer with **Linux/macOS/Windows**
|
||||
- A microSD card reader
|
||||
https://github.com/zenodante/PicoCalc-micropython-driver
|
||||
|
||||
## Custom Partitioning
|
||||
[MicroPython](https://micropython.org/) is a lean and efficient implementation of the Python 3 programming language that includes a small subset of the Python standard library and is optimised to run on microcontrollers and in constrained environments.
|
||||
|
||||
The **PicoCalc SD card** uses a dual-partition structure:
|
||||
Here is the MicroPython Drivers compatible with PicoCalc.
|
||||
|
||||
| Partition | Size | Format | Purpose |
|
||||
|-----------|--------|--------|---------|
|
||||
| `/dev/sdX1` | Remaining space | **FAT32** | Storage for PicoMite, uLisp, NES Emulator, etc. |
|
||||
| `/dev/sdX2` | **32MB** | **fuzix filesystem** | Root filesystem for **FUZIX** |
|
||||
|
||||
### Manually Partitioning an SD Card
|
||||
|
||||
If you need to manually create an SD card for **PicoCalc**, use the provided **partitioning script**:
|
||||
|
||||
```bash
|
||||
wget https://github.com/clockworkpi/PicoCalc/raw/refs/heads/master/Code/scripts/partition_usb_32mb.sh
|
||||
chmod +x partition_usb_32mb.sh
|
||||
sudo ./partition_usb_32mb.sh sdb
|
||||
```
|
||||
*(Replace `sdb` with your actual SD card device.)*
|
||||
|
||||
### Flashing the FUZIX 32MB Image
|
||||
- Download the FUZIX image:
|
||||
[PicoCalc_Fuzix_v1.0.img](https://github.com/clockworkpi/PicoCalc/blob/master/Bin/PicoCalc%20SD/firmware/PicoCalc_Fuzix_v1.0.img)
|
||||
|
||||
- Flash the image to the second partition:
|
||||
```bash
|
||||
sudo dd if=filesystem.img of=/dev/sdb2
|
||||
```
|
||||
|
||||
Please check the wiki for details
|
||||
|
||||
https://github.com/clockworkpi/PicoCalc/wiki/How-to-Create-an-Official-PicoCalc-SD-Card
|
||||
|
||||
## Notes
|
||||
- The **USB Type-C port** is the default **serial port** for **PicoCalc**, not the Micro USB port.
|
||||
- FUZIX supports a maximum **32MB** root filesystem.
|
||||
- Ensure you backup your data before modifying the SD card.
|
||||
|
||||
---
|
||||
For more details, visit the official **[PicoCalc GitHub Repository](https://github.com/clockworkpi/PicoCalc)**.
|
||||
|
||||
BIN
Bin/PicoCalc SD/bootloader_pico.uf2
Normal file
BIN
Bin/PicoCalc SD/bootloader_pico.uf2
Normal file
Binary file not shown.
815
Bin/PicoCalc SD/cc/edit.lua
Normal file
815
Bin/PicoCalc SD/cc/edit.lua
Normal file
@@ -0,0 +1,815 @@
|
||||
-- SPDX-FileCopyrightText: 2017 Daniel Ratcliffe
|
||||
--
|
||||
-- SPDX-License-Identifier: LicenseRef-CCPL
|
||||
|
||||
-- Get file to edit
|
||||
local tArgs = { ... }
|
||||
if #tArgs == 0 then
|
||||
local programName = "edit"
|
||||
print("Usage: " .. programName .. " <path>")
|
||||
return
|
||||
end
|
||||
|
||||
-- Error checking
|
||||
local sPath = tArgs[1]
|
||||
local bReadOnly = false
|
||||
if fs.exists(sPath) and fs.isDir(sPath) then
|
||||
print("Cannot edit a directory.")
|
||||
return
|
||||
end
|
||||
|
||||
local x, y = 1, 1
|
||||
local w, h = term.getSize()
|
||||
local scrollX, scrollY = 0, 0
|
||||
|
||||
local tLines, tLineLexStates = {}, {}
|
||||
local bRunning = true
|
||||
|
||||
-- colors
|
||||
local isColor = true
|
||||
local highlightColor, keywordColor, textColor, bgColor, errorColor
|
||||
if isColor then
|
||||
bgColor = colors.black
|
||||
textColor = colors.white
|
||||
highlightColor = colors.yellow
|
||||
keywordColor = colors.yellow
|
||||
errorColor = colors.red
|
||||
else
|
||||
bgColor = colors.black
|
||||
textColor = colors.white
|
||||
highlightColor = colors.white
|
||||
keywordColor = colors.white
|
||||
errorColor = colors.white
|
||||
end
|
||||
|
||||
-- Menus
|
||||
local menu = require "cc.internal.menu"
|
||||
local current_menu
|
||||
local menu_items = {}
|
||||
if not bReadOnly then
|
||||
table.insert(menu_items, "Save")
|
||||
end
|
||||
--[[if shell.openTab then
|
||||
table.insert(menu_items, "Run")
|
||||
end
|
||||
if peripheral.find("printer") then
|
||||
table.insert(menu_items, "Print")
|
||||
end]]
|
||||
table.insert(menu_items, "Run")
|
||||
table.insert(menu_items, "Exit")
|
||||
|
||||
local status_ok, status_text
|
||||
local function set_status(text, ok)
|
||||
status_ok = ok ~= false
|
||||
status_text = text
|
||||
end
|
||||
|
||||
if bReadOnly then
|
||||
set_status("File is read only", false)
|
||||
elseif fs.getFreeSpace(sPath) < 1024 then
|
||||
set_status("Disk is low on space", false)
|
||||
else
|
||||
local message
|
||||
message = "Press Ctrl to access menu"
|
||||
|
||||
if #message > w - 5 then
|
||||
message = "Press Ctrl for menu"
|
||||
end
|
||||
|
||||
set_status(message)
|
||||
end
|
||||
|
||||
local function load(_sPath)
|
||||
tLines = {}
|
||||
if fs.exists(_sPath) then
|
||||
bReadOnly = fs.isReadOnly(_sPath)
|
||||
local file = fs.open(_sPath, "r")
|
||||
local sLine = file:readLine()
|
||||
while sLine do
|
||||
table.insert(tLines, sLine)
|
||||
table.insert(tLineLexStates, false)
|
||||
sLine = file:readLine()
|
||||
end
|
||||
file:close()
|
||||
end
|
||||
|
||||
if #tLines == 0 then
|
||||
table.insert(tLines, "")
|
||||
table.insert(tLineLexStates, false)
|
||||
end
|
||||
end
|
||||
|
||||
local function save(_sPath, fWrite)
|
||||
-- Create intervening folder
|
||||
--[[local sDir = _sPath:sub(1, _sPath:len() - fs.getName(_sPath):len())
|
||||
if not fs.exists(sDir) then
|
||||
fs.makeDir(sDir)
|
||||
end]]
|
||||
|
||||
-- Save
|
||||
local file, fileerr
|
||||
local function innerSave()
|
||||
file, fileerr = fs.open(_sPath, "w")
|
||||
if file then
|
||||
if file then
|
||||
fWrite(file)
|
||||
end
|
||||
else
|
||||
error("Failed to open " .. _sPath)
|
||||
end
|
||||
end
|
||||
|
||||
local ok, err = pcall(innerSave)
|
||||
if file then
|
||||
file:close()
|
||||
end
|
||||
return ok, err, fileerr
|
||||
end
|
||||
|
||||
|
||||
local tokens = require "cc.internal.syntax.parser".tokens
|
||||
local lex_one = require "cc.internal.syntax.lexer".lex_one
|
||||
|
||||
local token_colors = {
|
||||
[tokens.STRING] = colors.red,
|
||||
[tokens.COMMENT] = colors.green,
|
||||
[tokens.NUMBER] = colors.magenta,
|
||||
-- Keywords
|
||||
[tokens.AND] = keywordColor,
|
||||
[tokens.BREAK] = keywordColor,
|
||||
[tokens.DO] = keywordColor,
|
||||
[tokens.ELSE] = keywordColor,
|
||||
[tokens.ELSEIF] = keywordColor,
|
||||
[tokens.END] = keywordColor,
|
||||
[tokens.FALSE] = keywordColor,
|
||||
[tokens.FOR] = keywordColor,
|
||||
[tokens.FUNCTION] = keywordColor,
|
||||
[tokens.GOTO] = keywordColor,
|
||||
[tokens.IF] = keywordColor,
|
||||
[tokens.IN] = keywordColor,
|
||||
[tokens.LOCAL] = keywordColor,
|
||||
[tokens.NIL] = keywordColor,
|
||||
[tokens.NOT] = keywordColor,
|
||||
[tokens.OR] = keywordColor,
|
||||
[tokens.REPEAT] = keywordColor,
|
||||
[tokens.RETURN] = keywordColor,
|
||||
[tokens.THEN] = keywordColor,
|
||||
[tokens.TRUE] = keywordColor,
|
||||
[tokens.UNTIL] = keywordColor,
|
||||
[tokens.WHILE] = keywordColor,
|
||||
}
|
||||
-- Fill in the remaining tokens.
|
||||
for _, token in pairs(tokens) do
|
||||
if not token_colors[token] then token_colors[token] = textColor end
|
||||
end
|
||||
|
||||
local lex_context = { line = function() end, report = function() end }
|
||||
|
||||
local tCompletions
|
||||
local nCompletion
|
||||
|
||||
local tCompleteEnv = _ENV
|
||||
local function complete(sLine)
|
||||
--[[if settings.get("edit.autocomplete") then
|
||||
local nStartPos = string.find(sLine, "[a-zA-Z0-9_%.:]+$")
|
||||
if nStartPos then
|
||||
sLine = string.sub(sLine, nStartPos)
|
||||
end
|
||||
if #sLine > 0 then
|
||||
return textutils.complete(sLine, tCompleteEnv)
|
||||
end
|
||||
end
|
||||
return nil]]
|
||||
end
|
||||
|
||||
local function recomplete()
|
||||
local sLine = tLines[y]
|
||||
if not bReadOnly and x == #sLine + 1 then
|
||||
tCompletions = complete(sLine)
|
||||
if tCompletions and #tCompletions > 0 then
|
||||
nCompletion = 1
|
||||
else
|
||||
nCompletion = nil
|
||||
end
|
||||
else
|
||||
tCompletions = nil
|
||||
nCompletion = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function writeCompletion(sLine)
|
||||
if nCompletion then
|
||||
local sCompletion = tCompletions[nCompletion]
|
||||
term.setTextColor(colors.white)
|
||||
term.setBackgroundColor(colors.grey)
|
||||
term.write(sCompletion)
|
||||
term.setTextColor(textColor)
|
||||
term.setBackgroundColor(bgColor)
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if two values are equal. If both values are lists, then the contents will be
|
||||
-- checked for equality, to a depth of 1.
|
||||
--
|
||||
-- @param x The first value.
|
||||
-- @param x The second value.
|
||||
-- @treturn boolean Whether the values are equal.
|
||||
local function shallowEqual(x, y)
|
||||
if x == y then return true end
|
||||
|
||||
if type(x) ~= "table" or type(y) ~= "table" then return false end
|
||||
if #x ~= #y then return false end
|
||||
|
||||
for i = 1, #x do if x[i] ~= y[i] then return false end end
|
||||
return true
|
||||
end
|
||||
|
||||
local function redrawLines(line, endLine)
|
||||
term.setCursorBlink(false)
|
||||
if not endLine then endLine = line end
|
||||
|
||||
local color = term.getTextColor()
|
||||
|
||||
-- Highlight all lines between line and endLine, highlighting further lines if their
|
||||
-- lexer state has changed and aborting at the end of the screen.
|
||||
local changed = false
|
||||
while (changed or line <= endLine) and line - scrollY < h do
|
||||
term.setCursorPos(1 - scrollX, line - scrollY)
|
||||
term.clearLine()
|
||||
|
||||
local contents = tLines[line]
|
||||
if not contents then break end
|
||||
|
||||
-- Lex our first token, either taking our continuation state (if present) or
|
||||
-- the default lexer.
|
||||
local pos, token, _, finish, continuation = 1
|
||||
local lex_state = tLineLexStates[line]
|
||||
if lex_state then
|
||||
token, finish, _, continuation = lex_state[1](lex_context, contents, table.unpack(lex_state, 2))
|
||||
else
|
||||
token, _, finish, _, continuation = lex_one(lex_context, contents, 1)
|
||||
end
|
||||
|
||||
while token do
|
||||
-- start at scrollX
|
||||
if finish >= scrollX+1 then
|
||||
-- Print out that token
|
||||
local new_color = token_colors[token]
|
||||
if new_color ~= color then
|
||||
term.setTextColor(new_color)
|
||||
color = new_color
|
||||
end
|
||||
-- limit printed line to screen
|
||||
if pos < scrollX+1 then pos = scrollX+1 end
|
||||
local cap = finish < scrollX+w and finish or scrollX+w
|
||||
term.write(contents:sub(pos, cap))
|
||||
end
|
||||
|
||||
pos = finish + 1
|
||||
|
||||
-- end if we're past the screen width
|
||||
if pos > scrollX+w then break end
|
||||
|
||||
-- If we have a continuation, then we've reached the end of the line. Abort.
|
||||
if continuation then break end
|
||||
|
||||
-- Otherwise lex another token and continue.
|
||||
token, _, finish, _, continuation = lex_one(lex_context, contents, pos)
|
||||
end
|
||||
|
||||
-- Print the rest of the line. We don't strictly speaking need this, as it will
|
||||
-- only ever contain whitespace.
|
||||
term.write(contents:sub(pos))
|
||||
|
||||
if line == y and x == #contents + 1 then
|
||||
writeCompletion()
|
||||
color = term.getTextColor()
|
||||
end
|
||||
|
||||
line = line + 1
|
||||
|
||||
-- Update the lext state of the next line. If that has changed, then
|
||||
-- re-highlight it too. We store the continuation as nil rather than
|
||||
-- false, to ensure we use the array part of the table.
|
||||
if continuation == nil then continuation = false end
|
||||
if tLineLexStates[line] ~= nil and not shallowEqual(tLineLexStates[line], continuation) then
|
||||
tLineLexStates[line] = continuation or false
|
||||
changed = true
|
||||
else
|
||||
changed = false
|
||||
end
|
||||
end
|
||||
|
||||
term.setTextColor(colors.white)
|
||||
term.setCursorPos(x - scrollX, y - scrollY)
|
||||
end
|
||||
|
||||
local function redrawText()
|
||||
redrawLines(scrollY + 1, scrollY + h - 1)
|
||||
end
|
||||
|
||||
local function redrawMenu()
|
||||
term.setCursorBlink(false)
|
||||
|
||||
-- Clear line
|
||||
term.setCursorPos(1, h)
|
||||
term.clearLine()
|
||||
|
||||
term.setCursorPos(1, h)
|
||||
if current_menu then
|
||||
-- Draw menu
|
||||
menu.draw(current_menu)
|
||||
else
|
||||
-- Draw status
|
||||
term.setTextColor(status_ok and highlightColor or errorColor)
|
||||
term.write(status_text)
|
||||
term.setTextColor(textColor)
|
||||
|
||||
-- Draw line numbers
|
||||
term.setCursorPos(w - #("Ln " .. y) + 1, h)
|
||||
term.setTextColor(highlightColor)
|
||||
term.write("Ln ")
|
||||
term.setTextColor(textColor)
|
||||
term.write(y)
|
||||
end
|
||||
|
||||
-- Reset cursor
|
||||
term.setCursorPos(x - scrollX, y - scrollY)
|
||||
term.setCursorBlink(not current_menu)
|
||||
end
|
||||
|
||||
local tMenuFuncs = {
|
||||
Save = function()
|
||||
if bReadOnly then
|
||||
set_status("Access denied", false)
|
||||
else
|
||||
local ok, _, fileerr = save(sPath, function(file)
|
||||
for _, sLine in ipairs(tLines) do
|
||||
file:writeLine(sLine)
|
||||
end
|
||||
end)
|
||||
if ok then
|
||||
set_status("Saved to " .. sPath)
|
||||
else
|
||||
if fileerr then
|
||||
set_status("Error saving: " .. fileerr, false)
|
||||
else
|
||||
set_status("Error saving to " .. sPath, false)
|
||||
end
|
||||
end
|
||||
end
|
||||
redrawMenu()
|
||||
end,
|
||||
--[[Print = function()
|
||||
local printer = peripheral.find("printer")
|
||||
if not printer then
|
||||
set_status("No printer attached", false)
|
||||
return
|
||||
end
|
||||
|
||||
local nPage = 0
|
||||
local sName = fs.getName(sPath)
|
||||
if printer.getInkLevel() < 1 then
|
||||
set_status("Printer out of ink", false)
|
||||
return
|
||||
elseif printer.getPaperLevel() < 1 then
|
||||
set_status("Printer out of paper", false)
|
||||
return
|
||||
end
|
||||
|
||||
local screenTerminal = term.current()
|
||||
local printerTerminal = {
|
||||
getCursorPos = printer.getCursorPos,
|
||||
setCursorPos = printer.setCursorPos,
|
||||
getSize = printer.getPageSize,
|
||||
write = printer.write,
|
||||
}
|
||||
printerTerminal.scroll = function()
|
||||
if nPage == 1 then
|
||||
printer.setPageTitle(sName .. " (page " .. nPage .. ")")
|
||||
end
|
||||
|
||||
while not printer.newPage() do
|
||||
if printer.getInkLevel() < 1 then
|
||||
set_status("Printer out of ink, please refill", false)
|
||||
elseif printer.getPaperLevel() < 1 then
|
||||
set_status("Printer out of paper, please refill", false)
|
||||
else
|
||||
set_status("Printer output tray full, please empty", false)
|
||||
end
|
||||
|
||||
term.redirect(screenTerminal)
|
||||
redrawMenu()
|
||||
term.redirect(printerTerminal)
|
||||
|
||||
sleep(0.5)
|
||||
end
|
||||
|
||||
nPage = nPage + 1
|
||||
if nPage == 1 then
|
||||
printer.setPageTitle(sName)
|
||||
else
|
||||
printer.setPageTitle(sName .. " (page " .. nPage .. ")")
|
||||
end
|
||||
end
|
||||
|
||||
local old_menu = current_menu
|
||||
current_menu = nil
|
||||
term.redirect(printerTerminal)
|
||||
local ok, error = pcall(function()
|
||||
term.scroll()
|
||||
for _, sLine in ipairs(tLines) do
|
||||
print(sLine)
|
||||
end
|
||||
end)
|
||||
term.redirect(screenTerminal)
|
||||
if not ok then
|
||||
print(error)
|
||||
end
|
||||
|
||||
while not printer.endPage() do
|
||||
set_status("Printer output tray full, please empty")
|
||||
redrawMenu()
|
||||
sleep(0.5)
|
||||
end
|
||||
current_menu = old_menu
|
||||
|
||||
if nPage > 1 then
|
||||
set_status("Printed " .. nPage .. " Pages")
|
||||
else
|
||||
set_status("Printed 1 Page")
|
||||
end
|
||||
redrawMenu()
|
||||
end,]]
|
||||
Exit = function()
|
||||
bRunning = false
|
||||
end,
|
||||
Run = function()
|
||||
local sTempPath = "~temp.lua"
|
||||
--[[if fs.exists(sTempPath) then
|
||||
set_status("Error saving to " .. sTempPath, false)
|
||||
return
|
||||
end]]
|
||||
local ok = save(sTempPath, function(file)
|
||||
for _, sLine in ipairs(tLines) do
|
||||
file:writeLine(sLine)
|
||||
end
|
||||
end)
|
||||
if ok then
|
||||
collectgarbage()
|
||||
local f, err = loadfile(sTempPath)
|
||||
if not f then
|
||||
set_status(tostring(err))
|
||||
else
|
||||
term.clear()
|
||||
keys.flush()
|
||||
print("Free memory: "..sys.freeMemory())
|
||||
set_status("Press Ctrl to access menu")
|
||||
local status, err = pcall(f)
|
||||
draw.enableBuffer(false)
|
||||
if not status then print(tostring(err)) end
|
||||
term.setCursorPos(1, h)
|
||||
term.write("Press any key...")
|
||||
keys.flush()
|
||||
keys.wait(false, true)
|
||||
end
|
||||
--fs.delete(sTempPath)
|
||||
else
|
||||
set_status("Error saving to " .. sTempPath, false)
|
||||
end
|
||||
collectgarbage()
|
||||
term.clear()
|
||||
redrawMenu()
|
||||
redrawText()
|
||||
end,
|
||||
}
|
||||
|
||||
local function setCursor(newX, newY)
|
||||
local _, oldY = x, y
|
||||
x, y = newX, newY
|
||||
local screenX = x - scrollX
|
||||
local screenY = y - scrollY
|
||||
|
||||
local bRedraw = false
|
||||
if screenX < 1 then
|
||||
scrollX = x - 1
|
||||
screenX = 1
|
||||
bRedraw = true
|
||||
elseif screenX > w then
|
||||
scrollX = x - w
|
||||
screenX = w
|
||||
bRedraw = true
|
||||
end
|
||||
|
||||
if screenY < 1 then
|
||||
scrollY = y - 1
|
||||
screenY = 1
|
||||
bRedraw = true
|
||||
elseif screenY > h - 1 then
|
||||
scrollY = y - (h - 1)
|
||||
screenY = h - 1
|
||||
bRedraw = true
|
||||
end
|
||||
|
||||
recomplete()
|
||||
if bRedraw then
|
||||
redrawText()
|
||||
elseif y ~= oldY then
|
||||
redrawLines(math.min(y, oldY), math.max(y, oldY))
|
||||
else
|
||||
redrawLines(y)
|
||||
end
|
||||
term.setCursorBlink(not current_menu);
|
||||
|
||||
redrawMenu()
|
||||
end
|
||||
|
||||
-- Actual program functionality begins
|
||||
load(sPath)
|
||||
|
||||
term.setBackgroundColor(bgColor)
|
||||
term.clear()
|
||||
term.setCursorPos(x, y)
|
||||
|
||||
recomplete()
|
||||
redrawText()
|
||||
redrawMenu()
|
||||
|
||||
local function acceptCompletion()
|
||||
if nCompletion then
|
||||
-- Append the completion
|
||||
local sCompletion = tCompletions[nCompletion]
|
||||
tLines[y] = tLines[y] .. sCompletion
|
||||
setCursor(x + #sCompletion, y)
|
||||
end
|
||||
end
|
||||
|
||||
local function handleMenuEvent(key)
|
||||
assert(current_menu)
|
||||
|
||||
local result = menu.handle_event(current_menu, key)
|
||||
if result == false then
|
||||
current_menu = nil
|
||||
redrawMenu()
|
||||
elseif result ~= nil then
|
||||
tMenuFuncs[result]()
|
||||
current_menu = nil
|
||||
redrawMenu()
|
||||
end
|
||||
end
|
||||
|
||||
-- Handle input
|
||||
while bRunning do
|
||||
term.setCursorBlink(not current_menu)
|
||||
state, modifiers, key = keys.wait()
|
||||
|
||||
if state == keys.states.pressed or state == keys.states.longHold then
|
||||
if current_menu then
|
||||
handleMenuEvent(key)
|
||||
else
|
||||
if key == keys.up then
|
||||
if nCompletion then
|
||||
-- Cycle completions
|
||||
nCompletion = nCompletion - 1
|
||||
if nCompletion < 1 then
|
||||
nCompletion = #tCompletions
|
||||
end
|
||||
redrawLines(y)
|
||||
|
||||
elseif y > 1 then
|
||||
-- Move cursor up
|
||||
setCursor(
|
||||
math.min(x, #tLines[y - 1] + 1),
|
||||
y - 1
|
||||
)
|
||||
end
|
||||
|
||||
elseif key == keys.down then
|
||||
if nCompletion then
|
||||
-- Cycle completions
|
||||
nCompletion = nCompletion + 1
|
||||
if nCompletion > #tCompletions then
|
||||
nCompletion = 1
|
||||
end
|
||||
redrawLines(y)
|
||||
|
||||
elseif y < #tLines then
|
||||
-- Move cursor down
|
||||
setCursor(
|
||||
math.min(x, #tLines[y + 1] + 1),
|
||||
y + 1
|
||||
)
|
||||
end
|
||||
|
||||
elseif key == keys.tab and not bReadOnly then
|
||||
if nCompletion and x == #tLines[y] + 1 then
|
||||
-- Accept autocomplete
|
||||
acceptCompletion()
|
||||
else
|
||||
-- Indent line
|
||||
local sLine = tLines[y]
|
||||
tLines[y] = string.sub(sLine, 1, x - 1) .. "\t" .. string.sub(sLine, x)
|
||||
setCursor(x + 1, y)
|
||||
end
|
||||
|
||||
elseif key == keys.pageUp then
|
||||
-- Move up a page
|
||||
local newY
|
||||
if y - (h - 1) >= 1 then
|
||||
newY = y - (h - 1)
|
||||
else
|
||||
newY = 1
|
||||
end
|
||||
setCursor(
|
||||
math.min(x, #tLines[newY] + 1),
|
||||
newY
|
||||
)
|
||||
elseif key == keys.pageDown then
|
||||
-- Move down a page
|
||||
local newY
|
||||
if y + (h - 1) <= #tLines then
|
||||
newY = y + (h - 1)
|
||||
else
|
||||
newY = #tLines
|
||||
end
|
||||
local newX = math.min(x, #tLines[newY] + 1)
|
||||
setCursor(newX, newY)
|
||||
|
||||
elseif key == keys.home then
|
||||
-- Move cursor to the beginning
|
||||
if x > 1 then
|
||||
setCursor(1, y)
|
||||
end
|
||||
|
||||
elseif key == keys["end"] then
|
||||
-- Move cursor to the end
|
||||
local nLimit = #tLines[y] + 1
|
||||
if x < nLimit then
|
||||
setCursor(nLimit, y)
|
||||
end
|
||||
|
||||
elseif key == keys.left then
|
||||
if x > 1 then
|
||||
-- Move cursor left
|
||||
setCursor(x - 1, y)
|
||||
elseif x == 1 and y > 1 then
|
||||
setCursor(#tLines[y - 1] + 1, y - 1)
|
||||
end
|
||||
|
||||
elseif key == keys.right then
|
||||
local nLimit = #tLines[y] + 1
|
||||
if x < nLimit then
|
||||
-- Move cursor right
|
||||
setCursor(x + 1, y)
|
||||
elseif nCompletion and x == #tLines[y] + 1 then
|
||||
-- Accept autocomplete
|
||||
acceptCompletion()
|
||||
elseif x == nLimit and y < #tLines then
|
||||
-- Go to next line
|
||||
setCursor(1, y + 1)
|
||||
end
|
||||
|
||||
elseif key == keys.delete and not bReadOnly then
|
||||
local nLimit = #tLines[y] + 1
|
||||
if x < nLimit then
|
||||
local sLine = tLines[y]
|
||||
tLines[y] = string.sub(sLine, 1, x - 1) .. string.sub(sLine, x + 1)
|
||||
recomplete()
|
||||
redrawLines(y)
|
||||
elseif y < #tLines then
|
||||
tLines[y] = tLines[y] .. tLines[y + 1]
|
||||
table.remove(tLines, y + 1)
|
||||
table.remove(tLineLexStates, y + 1)
|
||||
recomplete()
|
||||
redrawText()
|
||||
end
|
||||
|
||||
elseif key == keys.backspace and not bReadOnly then
|
||||
if x > 1 then
|
||||
-- Remove character
|
||||
local sLine = tLines[y]
|
||||
if x > 4 and string.sub(sLine, x - 4, x - 1) == " " and not string.sub(sLine, 1, x - 1):find("%S") then
|
||||
tLines[y] = string.sub(sLine, 1, x - 5) .. string.sub(sLine, x)
|
||||
setCursor(x - 4, y)
|
||||
else
|
||||
tLines[y] = string.sub(sLine, 1, x - 2) .. string.sub(sLine, x)
|
||||
setCursor(x - 1, y)
|
||||
end
|
||||
elseif y > 1 then
|
||||
-- Remove newline
|
||||
local sPrevLen = #tLines[y - 1]
|
||||
tLines[y - 1] = tLines[y - 1] .. tLines[y]
|
||||
table.remove(tLines, y)
|
||||
table.remove(tLineLexStates, y)
|
||||
setCursor(sPrevLen + 1, y - 1)
|
||||
redrawText()
|
||||
end
|
||||
|
||||
elseif (key == keys.enter) and not bReadOnly then
|
||||
-- Newline
|
||||
local sLine = tLines[y]
|
||||
local _, spaces = string.find(sLine, "^[ ]+")
|
||||
if not spaces then
|
||||
spaces = 0
|
||||
end
|
||||
tLines[y] = string.sub(sLine, 1, x - 1)
|
||||
table.insert(tLines, y + 1, string.rep(' ', spaces) .. string.sub(sLine, x))
|
||||
table.insert(tLineLexStates, y + 1, false)
|
||||
setCursor(spaces + 1, y + 1)
|
||||
redrawText()
|
||||
|
||||
elseif key == keys.control then
|
||||
current_menu = menu.create(menu_items)
|
||||
redrawMenu()
|
||||
|
||||
else
|
||||
if keys.isPrintable(key) and not bReadOnly then
|
||||
-- Input text
|
||||
local sLine = tLines[y]
|
||||
tLines[y] = string.sub(sLine, 1, x - 1) .. key .. string.sub(sLine, x)
|
||||
setCursor(x + 1, y)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[elseif event[1] == "paste" and not bReadOnly then
|
||||
-- Close menu if open
|
||||
if current_menu then
|
||||
current_menu = nil
|
||||
redrawMenu()
|
||||
end
|
||||
|
||||
-- Input text
|
||||
local text = event[2]
|
||||
local sLine = tLines[y]
|
||||
tLines[y] = string.sub(sLine, 1, x - 1) .. text .. string.sub(sLine, x)
|
||||
setCursor(x + #text, y)
|
||||
|
||||
elseif event[1] == "mouse_click" then
|
||||
local button, cx, cy = event[2], event[3], event[4]
|
||||
if current_menu then
|
||||
handleMenuEvent(event)
|
||||
else
|
||||
if button == 1 then
|
||||
-- Left click
|
||||
if cy < h then
|
||||
local newY = math.min(math.max(scrollY + cy, 1), #tLines)
|
||||
local newX = math.min(math.max(scrollX + cx, 1), #tLines[newY] + 1)
|
||||
setCursor(newX, newY)
|
||||
else
|
||||
current_menu = menu.create(menu_items)
|
||||
redrawMenu()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
elseif event[1] == "mouse_scroll" then
|
||||
if not current_menu then
|
||||
local direction = event[2]
|
||||
if direction == -1 then
|
||||
-- Scroll up
|
||||
if scrollY > 0 then
|
||||
-- Move cursor up
|
||||
scrollY = scrollY - 1
|
||||
redrawText()
|
||||
end
|
||||
|
||||
elseif direction == 1 then
|
||||
-- Scroll down
|
||||
local nMaxScroll = #tLines - (h - 1)
|
||||
if scrollY < nMaxScroll then
|
||||
-- Move cursor down
|
||||
scrollY = scrollY + 1
|
||||
redrawText()
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
elseif event[1] == "term_resize" then
|
||||
w, h = term.getSize()
|
||||
setCursor(x, y)
|
||||
redrawMenu()
|
||||
redrawText()
|
||||
|
||||
end]]
|
||||
end
|
||||
|
||||
-- Cleanup
|
||||
function unrequire(m)
|
||||
package.loaded[m] = nil
|
||||
_G[m] = nil
|
||||
end
|
||||
|
||||
unrequire("cc.internal.syntax.errors")
|
||||
unrequire("cc.internal.syntax.lexer")
|
||||
unrequire("cc.internal.syntax.parser")
|
||||
unrequire("cc.internal.menu")
|
||||
unrequire("cc.pretty")
|
||||
unrequire("cc.expect")
|
||||
collectgarbage()
|
||||
|
||||
term.clear()
|
||||
term.setCursorBlink(false)
|
||||
term.setCursorPos(1, 1)
|
||||
145
Bin/PicoCalc SD/cc/expect.lua
Normal file
145
Bin/PicoCalc SD/cc/expect.lua
Normal file
@@ -0,0 +1,145 @@
|
||||
-- SPDX-FileCopyrightText: 2019 The CC: Tweaked Developers
|
||||
--
|
||||
-- SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
--[[- The [`cc.expect`] library provides helper functions for verifying that
|
||||
function arguments are well-formed and of the correct type.
|
||||
|
||||
@module cc.expect
|
||||
@since 1.84.0
|
||||
@changed 1.96.0 The module can now be called directly as a function, which wraps around `expect.expect`.
|
||||
@usage Define a basic function and check it has the correct arguments.
|
||||
|
||||
local expect = require "cc.expect"
|
||||
local expect, field = expect.expect, expect.field
|
||||
|
||||
local function add_person(name, info)
|
||||
expect(1, name, "string")
|
||||
expect(2, info, "table", "nil")
|
||||
|
||||
if info then
|
||||
print("Got age=", field(info, "age", "number"))
|
||||
print("Got gender=", field(info, "gender", "string", "nil"))
|
||||
end
|
||||
end
|
||||
|
||||
add_person("Anastazja") -- `info' is optional
|
||||
add_person("Kion", { age = 23 }) -- `gender' is optional
|
||||
add_person("Caoimhin", { age = 23, gender = true }) -- error!
|
||||
]]
|
||||
|
||||
local native_select, native_type = select, type
|
||||
|
||||
local function get_type_names(...)
|
||||
local types = table.pack(...)
|
||||
for i = types.n, 1, -1 do
|
||||
if types[i] == "nil" then table.remove(types, i) end
|
||||
end
|
||||
|
||||
if #types <= 1 then
|
||||
return tostring(...)
|
||||
else
|
||||
return table.concat(types, ", ", 1, #types - 1) .. " or " .. types[#types]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function get_display_type(value, t)
|
||||
-- Lua is somewhat inconsistent in whether it obeys __name just for values which
|
||||
-- have a per-instance metatable (so tables/userdata) or for everything. We follow
|
||||
-- Cobalt and only read the metatable for tables/userdata.
|
||||
if t ~= "table" and t ~= "userdata" then return t end
|
||||
|
||||
local metatable = debug.getmetatable(value)
|
||||
if not metatable then return t end
|
||||
|
||||
local name = rawget(metatable, "__name")
|
||||
if type(name) == "string" then return name else return t end
|
||||
end
|
||||
|
||||
--- Expect an argument to have a specific type.
|
||||
--
|
||||
-- @tparam number index The 1-based argument index.
|
||||
-- @param value The argument's value.
|
||||
-- @tparam string ... The allowed types of the argument.
|
||||
-- @return The given `value`.
|
||||
-- @throws If the value is not one of the allowed types.
|
||||
local function expect(index, value, ...)
|
||||
local t = native_type(value)
|
||||
for i = 1, native_select("#", ...) do
|
||||
if t == native_select(i, ...) then return value end
|
||||
end
|
||||
|
||||
-- If we can determine the function name with a high level of confidence, try to include it.
|
||||
local name
|
||||
local ok, info = pcall(debug.getinfo, 3, "nS")
|
||||
if ok and info.name and info.name ~= "" and info.what ~= "C" then name = info.name end
|
||||
|
||||
t = get_display_type(value, t)
|
||||
|
||||
local type_names = get_type_names(...)
|
||||
if name then
|
||||
error(("bad argument #%d to '%s' (%s expected, got %s)"):format(index, name, type_names, t), 3)
|
||||
else
|
||||
error(("bad argument #%d (%s expected, got %s)"):format(index, type_names, t), 3)
|
||||
end
|
||||
end
|
||||
|
||||
--- Expect an field to have a specific type.
|
||||
--
|
||||
-- @tparam table tbl The table to index.
|
||||
-- @tparam string index The field name to check.
|
||||
-- @tparam string ... The allowed types of the argument.
|
||||
-- @return The contents of the given field.
|
||||
-- @throws If the field is not one of the allowed types.
|
||||
local function field(tbl, index, ...)
|
||||
expect(1, tbl, "table")
|
||||
expect(2, index, "string")
|
||||
|
||||
local value = tbl[index]
|
||||
local t = native_type(value)
|
||||
for i = 1, native_select("#", ...) do
|
||||
if t == native_select(i, ...) then return value end
|
||||
end
|
||||
|
||||
t = get_display_type(value, t)
|
||||
|
||||
if value == nil then
|
||||
error(("field '%s' missing from table"):format(index), 3)
|
||||
else
|
||||
error(("bad field '%s' (%s expected, got %s)"):format(index, get_type_names(...), t), 3)
|
||||
end
|
||||
end
|
||||
|
||||
local function is_nan(num)
|
||||
return num ~= num
|
||||
end
|
||||
|
||||
--- Expect a number to be within a specific range.
|
||||
--
|
||||
-- @tparam number num The value to check.
|
||||
-- @tparam[opt=-math.huge] number min The minimum value.
|
||||
-- @tparam[opt=math.huge] number max The maximum value.
|
||||
-- @return The given `value`.
|
||||
-- @throws If the value is outside of the allowed range.
|
||||
-- @since 1.96.0
|
||||
local function range(num, min, max)
|
||||
expect(1, num, "number")
|
||||
min = expect(2, min, "number", "nil") or -math.huge
|
||||
max = expect(3, max, "number", "nil") or math.huge
|
||||
if min > max then
|
||||
error("min must be less than or equal to max)", 2)
|
||||
end
|
||||
|
||||
if is_nan(num) or num < min or num > max then
|
||||
error(("number outside of range (expected %s to be within %s and %s)"):format(num, min, max), 3)
|
||||
end
|
||||
|
||||
return num
|
||||
end
|
||||
|
||||
return setmetatable({
|
||||
expect = expect,
|
||||
field = field,
|
||||
range = range,
|
||||
}, { __call = function(_, ...) return expect(...) end })
|
||||
104
Bin/PicoCalc SD/cc/internal/menu.lua
Normal file
104
Bin/PicoCalc SD/cc/internal/menu.lua
Normal file
@@ -0,0 +1,104 @@
|
||||
-- SPDX-FileCopyrightText: 2017 Daniel Ratcliffe
|
||||
--
|
||||
-- SPDX-License-Identifier: LicenseRef-CCPL
|
||||
|
||||
--[[- A simple menu bar.
|
||||
|
||||
> [!DANGER]
|
||||
> This is an internal module and SHOULD NOT be used in your own code. It may
|
||||
> be removed or changed at any time.
|
||||
|
||||
This provides a shared implementation of the menu bar used by the `edit` and
|
||||
`paint` programs. This draws a menu bar at the bottom of the string, with a list
|
||||
of options.
|
||||
|
||||
@local
|
||||
]]
|
||||
|
||||
|
||||
--[[- Create a new menu bar.
|
||||
|
||||
This should be called every time the menu is displayed.
|
||||
|
||||
@tparam { string... } items The menu items to display.
|
||||
@return The menu.
|
||||
]]
|
||||
local function create(items)
|
||||
return {
|
||||
items = items,
|
||||
selected = 1,
|
||||
}
|
||||
end
|
||||
|
||||
--[[- Draw the menu bar at the bottom of the screen.
|
||||
|
||||
This should be called when first displaying the menu, and if the whole screen is
|
||||
redrawn (e.g. after a [`term_resize`]).
|
||||
|
||||
@param menu The menu bar to draw.
|
||||
]]
|
||||
local function draw(menu)
|
||||
|
||||
local _, height = term.getSize()
|
||||
term.setCursorPos(1, height)
|
||||
|
||||
term.clearLine()
|
||||
|
||||
local active_color = colors.yellow
|
||||
term.setTextColor(colors.white)
|
||||
for k, v in pairs(menu.items) do
|
||||
if menu.selected == k then
|
||||
term.setTextColor(active_color)
|
||||
term.write("[")
|
||||
term.setTextColor(colors.white)
|
||||
term.write(v)
|
||||
term.setTextColor(active_color)
|
||||
term.write("]")
|
||||
term.setTextColor(colors.white)
|
||||
else
|
||||
term.write(" " .. v .. " ")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[- Process an event.
|
||||
|
||||
@param menu The menu bar to update.
|
||||
@tparam string The event name.
|
||||
@param ... Additional arguments to the event.
|
||||
@treturn nil|boolean|string Either:
|
||||
|
||||
- If no action was taken, return `nil`.
|
||||
- If the menu was closed, return `false`.
|
||||
- If an item was selected, return the item as a string.
|
||||
]]
|
||||
local function handle_event(menu, key)
|
||||
if key == keys.right then
|
||||
-- Move right
|
||||
menu.selected = menu.selected + 1
|
||||
if menu.selected > #menu.items then menu.selected = 1 end
|
||||
draw(menu)
|
||||
elseif key == keys.left and menu.selected > 1 then
|
||||
-- Move left
|
||||
menu.selected = menu.selected - 1
|
||||
if menu.selected < 1 then menu.selected = #menu.items end
|
||||
draw(menu)
|
||||
elseif key == keys.enter or key == keys.numPadEnter then
|
||||
-- Select an option
|
||||
return menu.items[menu.selected]
|
||||
elseif key == keys.control or keys == keys.alt then
|
||||
-- Cancel the menu
|
||||
return false
|
||||
elseif key:lower() >= "a" and key:lower() <= "z" then
|
||||
-- Select menu items
|
||||
local char = key:lower()
|
||||
for _, item in pairs(menu.items) do
|
||||
if item:sub(1, 1):lower() == char then return item end
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
return { create = create, draw = draw, handle_event = handle_event }
|
||||
659
Bin/PicoCalc SD/cc/internal/syntax/errors.lua
Normal file
659
Bin/PicoCalc SD/cc/internal/syntax/errors.lua
Normal file
@@ -0,0 +1,659 @@
|
||||
-- SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
--
|
||||
-- SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
--[[- The error messages reported by our lexer and parser.
|
||||
|
||||
> [!DANGER]
|
||||
> This is an internal module and SHOULD NOT be used in your own code. It may
|
||||
> be removed or changed at any time.
|
||||
|
||||
This provides a list of factory methods which take source positions and produce
|
||||
appropriate error messages targeting that location. These error messages can
|
||||
then be displayed to the user via [`cc.internal.error_printer`].
|
||||
|
||||
@local
|
||||
]]
|
||||
|
||||
local pretty = require "cc.pretty"
|
||||
local expect = require "cc.expect".expect
|
||||
local tokens = require "cc.internal.syntax.parser".tokens
|
||||
|
||||
local function annotate(start_pos, end_pos, msg)
|
||||
if msg == nil and (type(end_pos) == "string" or type(end_pos) == "table" or type(end_pos) == "nil") then
|
||||
end_pos, msg = start_pos, end_pos
|
||||
end
|
||||
|
||||
expect(1, start_pos, "number")
|
||||
expect(2, end_pos, "number")
|
||||
expect(3, msg, "string", "table", "nil")
|
||||
|
||||
return { tag = "annotate", start_pos = start_pos, end_pos = end_pos, msg = msg or "" }
|
||||
end
|
||||
|
||||
--- Format a string as a non-highlighted block of code.
|
||||
--
|
||||
-- @tparam string msg The code to format.
|
||||
-- @treturn cc.pretty.Doc The formatted code.
|
||||
local function code(msg) return pretty.text(msg, colors.lightGrey) end
|
||||
|
||||
--- Maps tokens to a more friendly version.
|
||||
local token_names = setmetatable({
|
||||
-- Specific tokens.
|
||||
[tokens.IDENT] = "identifier",
|
||||
[tokens.NUMBER] = "number",
|
||||
[tokens.STRING] = "string",
|
||||
[tokens.EOF] = "end of file",
|
||||
-- Symbols and keywords
|
||||
[tokens.ADD] = code("+"),
|
||||
[tokens.AND] = code("and"),
|
||||
[tokens.BREAK] = code("break"),
|
||||
[tokens.CBRACE] = code("}"),
|
||||
[tokens.COLON] = code(":"),
|
||||
[tokens.COMMA] = code(","),
|
||||
[tokens.CONCAT] = code(".."),
|
||||
[tokens.CPAREN] = code(")"),
|
||||
[tokens.CSQUARE] = code("]"),
|
||||
[tokens.DIV] = code("/"),
|
||||
[tokens.DO] = code("do"),
|
||||
[tokens.DOT] = code("."),
|
||||
[tokens.DOTS] = code("..."),
|
||||
[tokens.DOUBLE_COLON] = code("::"),
|
||||
[tokens.ELSE] = code("else"),
|
||||
[tokens.ELSEIF] = code("elseif"),
|
||||
[tokens.END] = code("end"),
|
||||
[tokens.EQ] = code("=="),
|
||||
[tokens.EQUALS] = code("="),
|
||||
[tokens.FALSE] = code("false"),
|
||||
[tokens.FOR] = code("for"),
|
||||
[tokens.FUNCTION] = code("function"),
|
||||
[tokens.GE] = code(">="),
|
||||
[tokens.GOTO] = code("goto"),
|
||||
[tokens.GT] = code(">"),
|
||||
[tokens.IF] = code("if"),
|
||||
[tokens.IN] = code("in"),
|
||||
[tokens.LE] = code("<="),
|
||||
[tokens.LEN] = code("#"),
|
||||
[tokens.LOCAL] = code("local"),
|
||||
[tokens.LT] = code("<"),
|
||||
[tokens.MOD] = code("%"),
|
||||
[tokens.MUL] = code("*"),
|
||||
[tokens.NE] = code("~="),
|
||||
[tokens.NIL] = code("nil"),
|
||||
[tokens.NOT] = code("not"),
|
||||
[tokens.OBRACE] = code("{"),
|
||||
[tokens.OPAREN] = code("("),
|
||||
[tokens.OR] = code("or"),
|
||||
[tokens.OSQUARE] = code("["),
|
||||
[tokens.POW] = code("^"),
|
||||
[tokens.REPEAT] = code("repeat"),
|
||||
[tokens.RETURN] = code("return"),
|
||||
[tokens.SEMICOLON] = code(";"),
|
||||
[tokens.SUB] = code("-"),
|
||||
[tokens.THEN] = code("then"),
|
||||
[tokens.TRUE] = code("true"),
|
||||
[tokens.UNTIL] = code("until"),
|
||||
[tokens.WHILE] = code("while"),
|
||||
}, { __index = function(_, name) error("No such token " .. tostring(name), 2) end })
|
||||
|
||||
local errors = {}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Lexer errors
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--[[- A string which ends without a closing quote.
|
||||
|
||||
@tparam number start_pos The start position of the string.
|
||||
@tparam number end_pos The end position of the string.
|
||||
@tparam string quote The kind of quote (`"` or `'`).
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.unfinished_string(start_pos, end_pos, quote)
|
||||
expect(1, start_pos, "number")
|
||||
expect(2, end_pos, "number")
|
||||
expect(3, quote, "string")
|
||||
|
||||
return {
|
||||
"This string is not finished. Are you missing a closing quote (" .. code(quote) .. ")?",
|
||||
annotate(start_pos, "String started here."),
|
||||
annotate(end_pos, "Expected a closing quote here."),
|
||||
}
|
||||
end
|
||||
|
||||
--[[- A string which ends with an escape sequence (so a literal `"foo\`). This
|
||||
is slightly different from [`unfinished_string`], as we don't want to suggest
|
||||
adding a quote.
|
||||
|
||||
@tparam number start_pos The start position of the string.
|
||||
@tparam number end_pos The end position of the string.
|
||||
@tparam string quote The kind of quote (`"` or `'`).
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.unfinished_string_escape(start_pos, end_pos, quote)
|
||||
expect(1, start_pos, "number")
|
||||
expect(2, end_pos, "number")
|
||||
expect(3, quote, "string")
|
||||
|
||||
return {
|
||||
"This string is not finished.",
|
||||
annotate(start_pos, "String started here."),
|
||||
annotate(end_pos, "An escape sequence was started here, but with nothing following it."),
|
||||
}
|
||||
end
|
||||
|
||||
--[[- A long string was never finished.
|
||||
|
||||
@tparam number start_pos The start position of the long string delimiter.
|
||||
@tparam number end_pos The end position of the long string delimiter.
|
||||
@tparam number ;em The length of the long string delimiter, excluding the first `[`.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.unfinished_long_string(start_pos, end_pos, len)
|
||||
expect(1, start_pos, "number")
|
||||
expect(2, end_pos, "number")
|
||||
expect(3, len, "number")
|
||||
|
||||
return {
|
||||
"This string was never finished.",
|
||||
annotate(start_pos, end_pos, "String was started here."),
|
||||
"We expected a closing delimiter (" .. code("]" .. ("="):rep(len - 1) .. "]") .. ") somewhere after this string was started.",
|
||||
}
|
||||
end
|
||||
|
||||
--[[- Malformed opening to a long string (i.e. `[=`).
|
||||
|
||||
@tparam number start_pos The start position of the long string delimiter.
|
||||
@tparam number end_pos The end position of the long string delimiter.
|
||||
@tparam number len The length of the long string delimiter, excluding the first `[`.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.malformed_long_string(start_pos, end_pos, len)
|
||||
expect(1, start_pos, "number")
|
||||
expect(2, end_pos, "number")
|
||||
expect(3, len, "number")
|
||||
|
||||
return {
|
||||
"Incorrect start of a long string.",
|
||||
annotate(start_pos, end_pos),
|
||||
"Tip: If you wanted to start a long string here, add an extra " .. code("[") .. " here.",
|
||||
}
|
||||
end
|
||||
|
||||
--[[- Malformed nesting of a long string.
|
||||
|
||||
@tparam number start_pos The start position of the long string delimiter.
|
||||
@tparam number end_pos The end position of the long string delimiter.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.nested_long_str(start_pos, end_pos)
|
||||
expect(1, start_pos, "number")
|
||||
expect(2, end_pos, "number")
|
||||
|
||||
return {
|
||||
code("[[") .. " cannot be nested inside another " .. code("[[ ... ]]"),
|
||||
annotate(start_pos, end_pos),
|
||||
}
|
||||
end
|
||||
|
||||
--[[- A malformed numeric literal.
|
||||
|
||||
@tparam number start_pos The start position of the number.
|
||||
@tparam number end_pos The end position of the number.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.malformed_number(start_pos, end_pos)
|
||||
expect(1, start_pos, "number")
|
||||
expect(2, end_pos, "number")
|
||||
|
||||
return {
|
||||
"This isn't a valid number.",
|
||||
annotate(start_pos, end_pos),
|
||||
"Numbers must be in one of the following formats: " .. code("123") .. ", "
|
||||
.. code("3.14") .. ", " .. code("23e35") .. ", " .. code("0x01AF") .. ".",
|
||||
}
|
||||
end
|
||||
|
||||
--[[- A long comment was never finished.
|
||||
|
||||
@tparam number start_pos The start position of the long string delimiter.
|
||||
@tparam number end_pos The end position of the long string delimiter.
|
||||
@tparam number len The length of the long string delimiter, excluding the first `[`.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.unfinished_long_comment(start_pos, end_pos, len)
|
||||
expect(1, start_pos, "number")
|
||||
expect(2, end_pos, "number")
|
||||
expect(3, len, "number")
|
||||
|
||||
return {
|
||||
"This comment was never finished.",
|
||||
annotate(start_pos, end_pos, "Comment was started here."),
|
||||
"We expected a closing delimiter (" .. code("]" .. ("="):rep(len - 1) .. "]") .. ") somewhere after this comment was started.",
|
||||
}
|
||||
end
|
||||
|
||||
--[[- `&&` was used instead of `and`.
|
||||
|
||||
@tparam number start_pos The start position of the token.
|
||||
@tparam number end_pos The end position of the token.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.wrong_and(start_pos, end_pos)
|
||||
expect(1, start_pos, "number")
|
||||
expect(2, end_pos, "number")
|
||||
|
||||
return {
|
||||
"Unexpected character.",
|
||||
annotate(start_pos, end_pos),
|
||||
"Tip: Replace this with " .. code("and") .. " to check if both values are true.",
|
||||
}
|
||||
end
|
||||
|
||||
--[[- `||` was used instead of `or`.
|
||||
|
||||
@tparam number start_pos The start position of the token.
|
||||
@tparam number end_pos The end position of the token.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.wrong_or(start_pos, end_pos)
|
||||
expect(1, start_pos, "number")
|
||||
expect(2, end_pos, "number")
|
||||
|
||||
return {
|
||||
"Unexpected character.",
|
||||
annotate(start_pos, end_pos),
|
||||
"Tip: Replace this with " .. code("or") .. " to check if either value is true.",
|
||||
}
|
||||
end
|
||||
|
||||
--[[- `!=` was used instead of `~=`.
|
||||
|
||||
@tparam number start_pos The start position of the token.
|
||||
@tparam number end_pos The end position of the token.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.wrong_ne(start_pos, end_pos)
|
||||
expect(1, start_pos, "number")
|
||||
expect(2, end_pos, "number")
|
||||
|
||||
return {
|
||||
"Unexpected character.",
|
||||
annotate(start_pos, end_pos),
|
||||
"Tip: Replace this with " .. code("~=") .. " to check if two values are not equal.",
|
||||
}
|
||||
end
|
||||
|
||||
--[[- `!` was used instead of `not`.
|
||||
|
||||
@tparam number start_pos The start position of the token.
|
||||
@tparam number end_pos The end position of the token.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.wrong_not(start_pos, end_pos)
|
||||
expect(1, start_pos, "number")
|
||||
expect(2, end_pos, "number")
|
||||
|
||||
return {
|
||||
"Unexpected character.",
|
||||
annotate(start_pos, end_pos),
|
||||
"Tip: Replace this with " .. code("not") .. " to negate a boolean.",
|
||||
}
|
||||
end
|
||||
|
||||
--[[- An unexpected character was used.
|
||||
|
||||
@tparam number pos The position of this character.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.unexpected_character(pos)
|
||||
expect(1, pos, "number")
|
||||
return {
|
||||
"Unexpected character.",
|
||||
annotate(pos, "This character isn't usable in Lua code."),
|
||||
}
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Expression parsing errors
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--[[- A fallback error when we expected an expression but received another token.
|
||||
|
||||
@tparam number token The token id.
|
||||
@tparam number start_pos The start position of the token.
|
||||
@tparam number end_pos The end position of the token.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.expected_expression(token, start_pos, end_pos)
|
||||
expect(1, token, "number")
|
||||
expect(2, start_pos, "number")
|
||||
expect(3, end_pos, "number")
|
||||
return {
|
||||
"Unexpected " .. token_names[token] .. ". Expected an expression.",
|
||||
annotate(start_pos, end_pos),
|
||||
}
|
||||
end
|
||||
|
||||
--[[- A fallback error when we expected a variable but received another token.
|
||||
|
||||
@tparam number token The token id.
|
||||
@tparam number start_pos The start position of the token.
|
||||
@tparam number end_pos The end position of the token.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.expected_var(token, start_pos, end_pos)
|
||||
expect(1, token, "number")
|
||||
expect(2, start_pos, "number")
|
||||
expect(3, end_pos, "number")
|
||||
return {
|
||||
"Unexpected " .. token_names[token] .. ". Expected a variable name.",
|
||||
annotate(start_pos, end_pos),
|
||||
}
|
||||
end
|
||||
|
||||
--[[- `=` was used in an expression context.
|
||||
|
||||
@tparam number start_pos The start position of the `=` token.
|
||||
@tparam number end_pos The end position of the `=` token.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.use_double_equals(start_pos, end_pos)
|
||||
expect(1, start_pos, "number")
|
||||
expect(2, end_pos, "number")
|
||||
|
||||
return {
|
||||
"Unexpected " .. code("=") .. " in expression.",
|
||||
annotate(start_pos, end_pos),
|
||||
"Tip: Replace this with " .. code("==") .. " to check if two values are equal.",
|
||||
}
|
||||
end
|
||||
|
||||
--[[- `=` was used after an expression inside a table.
|
||||
|
||||
@tparam number start_pos The start position of the `=` token.
|
||||
@tparam number end_pos The end position of the `=` token.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.table_key_equals(start_pos, end_pos)
|
||||
expect(1, start_pos, "number")
|
||||
expect(2, end_pos, "number")
|
||||
|
||||
return {
|
||||
"Unexpected " .. code("=") .. " in expression.",
|
||||
annotate(start_pos, end_pos),
|
||||
"Tip: Wrap the preceding expression in " .. code("[") .. " and " .. code("]") .. " to use it as a table key.",
|
||||
}
|
||||
end
|
||||
|
||||
--[[- There is a trailing comma in this list of function arguments.
|
||||
|
||||
@tparam number token The token id.
|
||||
@tparam number token_start The start position of the token.
|
||||
@tparam number token_end The end position of the token.
|
||||
@tparam number prev The start position of the previous entry.
|
||||
@treturn table The resulting parse error.
|
||||
]]
|
||||
function errors.missing_table_comma(token, token_start, token_end, prev)
|
||||
expect(1, token, "number")
|
||||
expect(2, token_start, "number")
|
||||
expect(3, token_end, "number")
|
||||
expect(4, prev, "number")
|
||||
|
||||
return {
|
||||
"Unexpected " .. token_names[token] .. " in table.",
|
||||
annotate(token_start, token_end),
|
||||
annotate(prev + 1, prev + 1, "Are you missing a comma here?"),
|
||||
}
|
||||
end
|
||||
|
||||
--[[- There is a trailing comma in this list of function arguments.
|
||||
|
||||
@tparam number comma_start The start position of the `,` token.
|
||||
@tparam number comma_end The end position of the `,` token.
|
||||
@tparam number paren_start The start position of the `)` token.
|
||||
@tparam number paren_end The end position of the `)` token.
|
||||
@treturn table The resulting parse error.
|
||||
]]
|
||||
function errors.trailing_call_comma(comma_start, comma_end, paren_start, paren_end)
|
||||
expect(1, comma_start, "number")
|
||||
expect(2, comma_end, "number")
|
||||
expect(3, paren_start, "number")
|
||||
expect(4, paren_end, "number")
|
||||
|
||||
return {
|
||||
"Unexpected " .. code(")") .. " in function call.",
|
||||
annotate(paren_start, paren_end),
|
||||
annotate(comma_start, comma_end, "Tip: Try removing this " .. code(",") .. "."),
|
||||
}
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Statement parsing errors
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--[[- A fallback error when we expected a statement but received another token.
|
||||
|
||||
@tparam number token The token id.
|
||||
@tparam number start_pos The start position of the token.
|
||||
@tparam number end_pos The end position of the token.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.expected_statement(token, start_pos, end_pos)
|
||||
expect(1, token, "number")
|
||||
expect(2, start_pos, "number")
|
||||
expect(3, end_pos, "number")
|
||||
return {
|
||||
"Unexpected " .. token_names[token] .. ". Expected a statement.",
|
||||
annotate(start_pos, end_pos),
|
||||
}
|
||||
end
|
||||
|
||||
--[[- `local function` was used with a table identifier.
|
||||
|
||||
@tparam number local_start The start position of the `local` token.
|
||||
@tparam number local_end The end position of the `local` token.
|
||||
@tparam number dot_start The start position of the `.` token.
|
||||
@tparam number dot_end The end position of the `.` token.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.local_function_dot(local_start, local_end, dot_start, dot_end)
|
||||
expect(1, local_start, "number")
|
||||
expect(2, local_end, "number")
|
||||
expect(3, dot_start, "number")
|
||||
expect(4, dot_end, "number")
|
||||
|
||||
return {
|
||||
"Cannot use " .. code("local function") .. " with a table key.",
|
||||
annotate(dot_start, dot_end, code(".") .. " appears here."),
|
||||
annotate(local_start, local_end, "Tip: " .. "Try removing this " .. code("local") .. " keyword."),
|
||||
}
|
||||
end
|
||||
|
||||
--[[- A statement of the form `x.y`
|
||||
|
||||
@tparam number token The token id.
|
||||
@tparam number pos The position right after this name.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.standalone_name(token, pos)
|
||||
expect(1, token, "number")
|
||||
expect(2, pos, "number")
|
||||
|
||||
return {
|
||||
"Unexpected " .. token_names[token] .. " after name.",
|
||||
annotate(pos),
|
||||
"Did you mean to assign this or call it as a function?",
|
||||
}
|
||||
end
|
||||
|
||||
--[[- A statement of the form `x.y, z`
|
||||
|
||||
@tparam number token The token id.
|
||||
@tparam number pos The position right after this name.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.standalone_names(token, pos)
|
||||
expect(1, token, "number")
|
||||
expect(2, pos, "number")
|
||||
|
||||
return {
|
||||
"Unexpected " .. token_names[token] .. " after name.",
|
||||
annotate(pos),
|
||||
"Did you mean to assign this?",
|
||||
}
|
||||
end
|
||||
|
||||
--[[- A statement of the form `x.y`. This is similar to [`standalone_name`], but
|
||||
when the next token is on another line.
|
||||
|
||||
@tparam number token The token id.
|
||||
@tparam number pos The position right after this name.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.standalone_name_call(token, pos)
|
||||
expect(1, token, "number")
|
||||
expect(2, pos, "number")
|
||||
|
||||
return {
|
||||
"Unexpected " .. token_names[token] .. " after name.",
|
||||
annotate(pos + 1, "Expected something before the end of the line."),
|
||||
"Tip: Use " .. code("()") .. " to call with no arguments.",
|
||||
}
|
||||
end
|
||||
|
||||
--[[- `then` was expected
|
||||
|
||||
@tparam number if_start The start position of the `if`/`elseif` keyword.
|
||||
@tparam number if_end The end position of the `if`/`elseif` keyword.
|
||||
@tparam number token_pos The current token position.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.expected_then(if_start, if_end, token_pos)
|
||||
expect(1, if_start, "number")
|
||||
expect(2, if_end, "number")
|
||||
expect(3, token_pos, "number")
|
||||
|
||||
return {
|
||||
"Expected " .. code("then") .. " after if condition.",
|
||||
annotate(if_start, if_end, "If statement started here."),
|
||||
annotate(token_pos, "Expected " .. code("then") .. " before here."),
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
--[[- `end` was expected
|
||||
|
||||
@tparam number block_start The start position of the block.
|
||||
@tparam number block_end The end position of the block.
|
||||
@tparam number token The current token position.
|
||||
@tparam number token_start The current token position.
|
||||
@tparam number token_end The current token position.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.expected_end(block_start, block_end, token, token_start, token_end)
|
||||
return {
|
||||
"Unexpected " .. token_names[token] .. ". Expected " .. code("end") .. " or another statement.",
|
||||
annotate(block_start, block_end, "Block started here."),
|
||||
annotate(token_start, token_end, "Expected end of block here."),
|
||||
}
|
||||
end
|
||||
|
||||
--[[- An unexpected `end` in a statement.
|
||||
|
||||
@tparam number start_pos The start position of the token.
|
||||
@tparam number end_pos The end position of the token.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.unexpected_end(start_pos, end_pos)
|
||||
return {
|
||||
"Unexpected " .. code("end") .. ".",
|
||||
annotate(start_pos, end_pos),
|
||||
"Your program contains more " .. code("end") .. "s than needed. Check " ..
|
||||
"each block (" .. code("if") .. ", " .. code("for") .. ", " ..
|
||||
code("function") .. ", ...) only has one " .. code("end") .. ".",
|
||||
}
|
||||
end
|
||||
|
||||
--[[- A label statement was opened but not closed.
|
||||
|
||||
@tparam number open_start The start position of the opening label.
|
||||
@tparam number open_end The end position of the opening label.
|
||||
@tparam number tok_start The start position of the current token.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.unclosed_label(open_start, open_end, token, start_pos, end_pos)
|
||||
expect(1, open_start, "number")
|
||||
expect(2, open_end, "number")
|
||||
expect(3, token, "number")
|
||||
expect(4, start_pos, "number")
|
||||
expect(5, end_pos, "number")
|
||||
|
||||
return {
|
||||
"Unexpected " .. token_names[token] .. ".",
|
||||
annotate(open_start, open_end, "Label was started here."),
|
||||
annotate(start_pos, end_pos, "Tip: Try adding " .. code("::") .. " here."),
|
||||
|
||||
}
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Generic parsing errors
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--[[- A fallback error when we can't produce anything more useful.
|
||||
|
||||
@tparam number token The token id.
|
||||
@tparam number start_pos The start position of the token.
|
||||
@tparam number end_pos The end position of the token.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.unexpected_token(token, start_pos, end_pos)
|
||||
expect(1, token, "number")
|
||||
expect(2, start_pos, "number")
|
||||
expect(3, end_pos, "number")
|
||||
|
||||
return {
|
||||
"Unexpected " .. token_names[token] .. ".",
|
||||
annotate(start_pos, end_pos),
|
||||
}
|
||||
end
|
||||
|
||||
--[[- A parenthesised expression was started but not closed.
|
||||
|
||||
@tparam number open_start The start position of the opening bracket.
|
||||
@tparam number open_end The end position of the opening bracket.
|
||||
@tparam number tok_start The start position of the opening bracket.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.unclosed_brackets(open_start, open_end, token, start_pos, end_pos)
|
||||
expect(1, open_start, "number")
|
||||
expect(2, open_end, "number")
|
||||
expect(3, token, "number")
|
||||
expect(4, start_pos, "number")
|
||||
expect(5, end_pos, "number")
|
||||
|
||||
-- TODO: Do we want to be smarter here with where we report the error?
|
||||
return {
|
||||
"Unexpected " .. token_names[token] .. ". Are you missing a closing bracket?",
|
||||
annotate(open_start, open_end, "Brackets were opened here."),
|
||||
annotate(start_pos, end_pos, "Unexpected " .. token_names[token] .. " here."),
|
||||
|
||||
}
|
||||
end
|
||||
|
||||
--[[- Expected `(` to open our function arguments.
|
||||
|
||||
@tparam number token The token id.
|
||||
@tparam number start_pos The start position of the token.
|
||||
@tparam number end_pos The end position of the token.
|
||||
@return The resulting parse error.
|
||||
]]
|
||||
function errors.expected_function_args(token, start_pos, end_pos)
|
||||
return {
|
||||
"Unexpected " .. token_names[token] .. ". Expected " .. code("(") .. " to start function arguments.",
|
||||
annotate(start_pos, end_pos),
|
||||
}
|
||||
end
|
||||
|
||||
return errors
|
||||
175
Bin/PicoCalc SD/cc/internal/syntax/init.lua
Normal file
175
Bin/PicoCalc SD/cc/internal/syntax/init.lua
Normal file
@@ -0,0 +1,175 @@
|
||||
-- SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
--
|
||||
-- SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
--[[- The main entrypoint to our Lua parser
|
||||
|
||||
> [!DANGER]
|
||||
> This is an internal module and SHOULD NOT be used in your own code. It may
|
||||
> be removed or changed at any time.
|
||||
|
||||
@local
|
||||
]]
|
||||
|
||||
local expect = require "cc.expect".expect
|
||||
|
||||
local lex_one = require "cc.internal.syntax.lexer".lex_one
|
||||
local parser = require "cc.internal.syntax.parser"
|
||||
local error_printer = require "cc.internal.error_printer"
|
||||
|
||||
local error_sentinel = {}
|
||||
|
||||
local function make_context(input)
|
||||
expect(1, input, "string")
|
||||
|
||||
local context = {}
|
||||
|
||||
local lines = { 1 }
|
||||
function context.line(pos) lines[#lines + 1] = pos end
|
||||
|
||||
function context.get_pos(pos)
|
||||
expect(1, pos, "number")
|
||||
for i = #lines, 1, -1 do
|
||||
local start = lines[i]
|
||||
if pos >= start then return i, pos - start + 1 end
|
||||
end
|
||||
|
||||
error("Position is <= 0", 2)
|
||||
end
|
||||
|
||||
function context.get_line(pos)
|
||||
expect(1, pos, "number")
|
||||
for i = #lines, 1, -1 do
|
||||
local start = lines[i]
|
||||
if pos >= start then return input:match("[^\r\n]*", start) end
|
||||
end
|
||||
|
||||
error("Position is <= 0", 2)
|
||||
end
|
||||
|
||||
return context
|
||||
end
|
||||
|
||||
local function make_lexer(input, context)
|
||||
local tokens, last_token = parser.tokens, parser.tokens.COMMENT
|
||||
local pos = 1
|
||||
return function()
|
||||
while true do
|
||||
local token, start, finish = lex_one(context, input, pos)
|
||||
if not token then return tokens.EOF, #input + 1, #input + 1 end
|
||||
|
||||
pos = finish + 1
|
||||
|
||||
if token < last_token then
|
||||
return token, start, finish
|
||||
elseif token == tokens.ERROR then
|
||||
error(error_sentinel)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function parse(input, start_symbol)
|
||||
expect(1, input, "string")
|
||||
expect(2, start_symbol, "number")
|
||||
|
||||
local context = make_context(input)
|
||||
function context.report(msg, ...)
|
||||
expect(1, msg, "table", "function")
|
||||
if type(msg) == "function" then msg = msg(...) end
|
||||
error_printer(context, msg)
|
||||
error(error_sentinel)
|
||||
end
|
||||
|
||||
local ok, err = pcall(parser.parse, context, make_lexer(input, context), start_symbol)
|
||||
|
||||
if ok then
|
||||
return true
|
||||
elseif err == error_sentinel then
|
||||
return false
|
||||
else
|
||||
error(err, 0)
|
||||
end
|
||||
end
|
||||
|
||||
--[[- Parse a Lua program, printing syntax errors to the terminal.
|
||||
|
||||
@tparam string input The string to parse.
|
||||
@treturn boolean Whether the string was successfully parsed.
|
||||
]]
|
||||
local function parse_program(input) return parse(input, parser.program) end
|
||||
|
||||
--[[- Parse a REPL input (either a program or a list of expressions), printing
|
||||
syntax errors to the terminal.
|
||||
|
||||
@tparam string input The string to parse.
|
||||
@treturn boolean Whether the string was successfully parsed.
|
||||
]]
|
||||
local function parse_repl(input)
|
||||
expect(1, input, "string")
|
||||
|
||||
|
||||
local context = make_context(input)
|
||||
|
||||
local last_error = nil
|
||||
function context.report(msg, ...)
|
||||
expect(1, msg, "table", "function")
|
||||
if type(msg) == "function" then msg = msg(...) end
|
||||
last_error = msg
|
||||
error(error_sentinel)
|
||||
end
|
||||
|
||||
local lexer = make_lexer(input, context)
|
||||
|
||||
local parsers = {}
|
||||
for i, start_code in ipairs { parser.repl_exprs, parser.program } do
|
||||
parsers[i] = coroutine.create(parser.parse)
|
||||
assert(coroutine.resume(parsers[i], context, coroutine.yield, start_code))
|
||||
end
|
||||
|
||||
-- Run all parsers together in parallel, feeding them one token at a time.
|
||||
-- Once all parsers have failed, report the last failure (corresponding to
|
||||
-- the longest parse).
|
||||
local ok, err = pcall(function()
|
||||
local parsers_n = #parsers
|
||||
while true do
|
||||
local token, start, finish = lexer()
|
||||
|
||||
local all_failed = true
|
||||
for i = 1, parsers_n do
|
||||
local parser = parsers[i]
|
||||
if parser then
|
||||
local ok, err = coroutine.resume(parser, token, start, finish)
|
||||
if ok then
|
||||
-- This parser accepted our input, succeed immediately.
|
||||
if coroutine.status(parser) == "dead" then return end
|
||||
|
||||
all_failed = false -- Otherwise continue parsing.
|
||||
elseif err ~= error_sentinel then
|
||||
-- An internal error occurred: propagate it.
|
||||
error(err, 0)
|
||||
else
|
||||
-- The parser failed, stub it out so we don't try to continue using it.
|
||||
parsers[i] = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if all_failed then error(error_sentinel) end
|
||||
end
|
||||
end)
|
||||
|
||||
if ok then
|
||||
return true
|
||||
elseif err == error_sentinel then
|
||||
error_printer(context, last_error)
|
||||
return false
|
||||
else
|
||||
error(err, 0)
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
parse_program = parse_program,
|
||||
parse_repl = parse_repl,
|
||||
}
|
||||
422
Bin/PicoCalc SD/cc/internal/syntax/lexer.lua
Normal file
422
Bin/PicoCalc SD/cc/internal/syntax/lexer.lua
Normal file
@@ -0,0 +1,422 @@
|
||||
-- SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
--
|
||||
-- SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
--[[- A lexer for Lua source code.
|
||||
|
||||
> [!DANGER]
|
||||
> This is an internal module and SHOULD NOT be used in your own code. It may
|
||||
> be removed or changed at any time.
|
||||
|
||||
This module provides utilities for lexing Lua code, returning tokens compatible
|
||||
with [`cc.internal.syntax.parser`]. While all lexers are roughly the same, there
|
||||
are some design choices worth drawing attention to:
|
||||
|
||||
- The lexer uses Lua patterns (i.e. [`string.find`]) as much as possible,
|
||||
trying to avoid [`string.sub`] loops except when needed. This allows us to
|
||||
move string processing to native code, which ends up being much faster.
|
||||
|
||||
- We try to avoid allocating where possible. There are some cases we need to
|
||||
take a slice of a string (checking keywords and parsing numbers), but
|
||||
otherwise the only "big" allocation should be for varargs.
|
||||
|
||||
- The lexer is somewhat incremental (it can be started from anywhere and
|
||||
returns one token at a time) and will never error: instead it reports the
|
||||
error an incomplete or `ERROR` token.
|
||||
|
||||
@local
|
||||
]]
|
||||
|
||||
local errors = require "cc.internal.syntax.errors"
|
||||
local tokens = require "cc.internal.syntax.parser".tokens
|
||||
local sub, find = string.sub, string.find
|
||||
|
||||
local keywords = {
|
||||
["and"] = tokens.AND, ["break"] = tokens.BREAK, ["do"] = tokens.DO, ["else"] = tokens.ELSE,
|
||||
["elseif"] = tokens.ELSEIF, ["end"] = tokens.END, ["false"] = tokens.FALSE, ["for"] = tokens.FOR,
|
||||
["function"] = tokens.FUNCTION, ["goto"] = tokens.GOTO, ["if"] = tokens.IF, ["in"] = tokens.IN,
|
||||
["local"] = tokens.LOCAL, ["nil"] = tokens.NIL, ["not"] = tokens.NOT, ["or"] = tokens.OR,
|
||||
["repeat"] = tokens.REPEAT, ["return"] = tokens.RETURN, ["then"] = tokens.THEN, ["true"] = tokens.TRUE,
|
||||
["until"] = tokens.UNTIL, ["while"] = tokens.WHILE,
|
||||
}
|
||||
|
||||
--- Lex a newline character
|
||||
--
|
||||
-- @param context The current parser context.
|
||||
-- @tparam string str The current string.
|
||||
-- @tparam number pos The position of the newline character.
|
||||
-- @tparam string nl The current new line character, either "\n" or "\r".
|
||||
-- @treturn pos The new position, after the newline.
|
||||
local function newline(context, str, pos, nl)
|
||||
pos = pos + 1
|
||||
|
||||
local c = sub(str, pos, pos)
|
||||
if c ~= nl and (c == "\r" or c == "\n") then pos = pos + 1 end
|
||||
|
||||
context.line(pos) -- Mark the start of the next line.
|
||||
return pos
|
||||
end
|
||||
|
||||
|
||||
--- Lex a number
|
||||
--
|
||||
-- @param context The current parser context.
|
||||
-- @tparam string str The current string.
|
||||
-- @tparam number start The start position of this number.
|
||||
-- @treturn number The token id for numbers.
|
||||
-- @treturn number The end position of this number
|
||||
local function lex_number(context, str, start)
|
||||
local pos = start + 1
|
||||
|
||||
local exp_low, exp_high = "e", "E"
|
||||
if sub(str, start, start) == "0" then
|
||||
local next = sub(str, pos, pos)
|
||||
if next == "x" or next == "X" then
|
||||
pos = pos + 1
|
||||
exp_low, exp_high = "p", "P"
|
||||
end
|
||||
end
|
||||
|
||||
while true do
|
||||
local c = sub(str, pos, pos)
|
||||
if c == exp_low or c == exp_high then
|
||||
pos = pos + 1
|
||||
c = sub(str, pos, pos)
|
||||
if c == "+" or c == "-" then
|
||||
pos = pos + 1
|
||||
end
|
||||
elseif (c >= "0" and c <= "9") or (c >= "a" and c <= "f") or (c >= "A" and c <= "F") or c == "." then
|
||||
pos = pos + 1
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local contents = sub(str, start, pos - 1)
|
||||
if not tonumber(contents) then
|
||||
-- TODO: Separate error for "2..3"?
|
||||
context.report(errors.malformed_number, start, pos - 1)
|
||||
end
|
||||
|
||||
return tokens.NUMBER, pos - 1
|
||||
end
|
||||
|
||||
local lex_string_zap
|
||||
|
||||
--[[- Lex a quoted string.
|
||||
|
||||
@param context The current parser context.
|
||||
@tparam string str The string we're lexing.
|
||||
@tparam number pos The position to start lexing from.
|
||||
@tparam number start_pos The actual start position of the string.
|
||||
@tparam string quote The quote character, either " or '.
|
||||
@treturn number The token id for strings.
|
||||
@treturn number The new position.
|
||||
@treturn nil A placeholder value.
|
||||
@treturn table|nil The continuation function when the string is not finished.
|
||||
]]
|
||||
local function lex_string(context, str, pos, start_pos, quote)
|
||||
while true do
|
||||
local c = sub(str, pos, pos)
|
||||
if c == quote then
|
||||
return tokens.STRING, pos
|
||||
elseif c == "\n" or c == "\r" or c == "" then
|
||||
-- We don't call newline here, as that's done for the next token.
|
||||
context.report(errors.unfinished_string, start_pos, pos, quote)
|
||||
return tokens.STRING, pos - 1
|
||||
elseif c == "\\" then
|
||||
c = sub(str, pos + 1, pos + 1)
|
||||
if c == "\n" or c == "\r" then
|
||||
pos = newline(context, str, pos + 1, c)
|
||||
elseif c == "" then
|
||||
context.report(errors.unfinished_string_escape, start_pos, pos, quote)
|
||||
return tokens.STRING, pos, nil, { lex_string, 1, 1, quote }
|
||||
elseif c == "z" then
|
||||
return lex_string_zap(context, str, pos + 2, start_pos, quote)
|
||||
else
|
||||
pos = pos + 2
|
||||
end
|
||||
else
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[- Lex the remainder of a zap escape sequence (`\z`). This consumes all leading
|
||||
whitespace, and then continues lexing the string.
|
||||
|
||||
@param context The current parser context.
|
||||
@tparam string str The string we're lexing.
|
||||
@tparam number pos The position to start lexing from.
|
||||
@tparam number start_pos The actual start position of the string.
|
||||
@tparam string quote The quote character, either " or '.
|
||||
@treturn number The token id for strings.
|
||||
@treturn number The new position.
|
||||
@treturn nil A placeholder value.
|
||||
@treturn table|nil The continuation function when the string is not finished.
|
||||
]]
|
||||
lex_string_zap = function(context, str, pos, start_pos, quote)
|
||||
while true do
|
||||
local next_pos, _, c = find(str, "([%S\r\n])", pos)
|
||||
|
||||
if not next_pos then
|
||||
context.report(errors.unfinished_string, start_pos, #str, quote)
|
||||
return tokens.STRING, #str, nil, { lex_string_zap, 1, 1, quote }
|
||||
end
|
||||
|
||||
if c == "\n" or c == "\r" then
|
||||
pos = newline(context, str, next_pos, c)
|
||||
else
|
||||
pos = next_pos
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return lex_string(context, str, pos, start_pos, quote)
|
||||
end
|
||||
|
||||
--- Consume the start or end of a long string.
|
||||
-- @tparam string str The input string.
|
||||
-- @tparam number pos The start position. This must be after the first `[` or `]`.
|
||||
-- @tparam string fin The terminating character, either `[` or `]`.
|
||||
-- @treturn boolean Whether a long string was successfully started.
|
||||
-- @treturn number The current position.
|
||||
local function lex_long_str_boundary(str, pos, fin)
|
||||
while true do
|
||||
local c = sub(str, pos, pos)
|
||||
if c == "=" then
|
||||
pos = pos + 1
|
||||
elseif c == fin then
|
||||
return true, pos
|
||||
else
|
||||
return false, pos
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Lex a long string.
|
||||
-- @param context The current parser context.
|
||||
-- @tparam string str The input string.
|
||||
-- @tparam number start The start position, after the input boundary.
|
||||
-- @tparam number len The expected length of the boundary. Equal to 1 + the
|
||||
-- number of `=`.
|
||||
-- @treturn number|nil The end position, or [`nil`] if this is not terminated.
|
||||
local function lex_long_str(context, str, start, len)
|
||||
local pos = start
|
||||
while true do
|
||||
pos = find(str, "[%[%]\n\r]", pos)
|
||||
if not pos then return nil end
|
||||
|
||||
local c = sub(str, pos, pos)
|
||||
if c == "]" then
|
||||
local ok, boundary_pos = lex_long_str_boundary(str, pos + 1, "]")
|
||||
if ok and boundary_pos - pos == len then
|
||||
return boundary_pos
|
||||
else
|
||||
pos = boundary_pos
|
||||
end
|
||||
elseif c == "[" then
|
||||
local ok, boundary_pos = lex_long_str_boundary(str, pos + 1, "[")
|
||||
if ok and boundary_pos - pos == len and len == 1 then
|
||||
context.report(errors.nested_long_str, pos, boundary_pos)
|
||||
end
|
||||
|
||||
pos = boundary_pos
|
||||
else
|
||||
pos = newline(context, str, pos, c)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[- Lex the remainder of a long string.
|
||||
|
||||
@param context The current parser context.
|
||||
@tparam string str The string we're lexing.
|
||||
@tparam number pos The position to start lexing from.
|
||||
@tparam number start_pos The actual start position of the string.
|
||||
@tparam number boundary_length The length of the boundary.
|
||||
@treturn number The token id for strings.
|
||||
@treturn number The new position.
|
||||
@treturn nil A placeholder value.
|
||||
@treturn table|nil The continuation function when the string is not finished.
|
||||
]]
|
||||
local function lex_long_string(context, str, pos, start_pos, boundary_length)
|
||||
local end_pos = lex_long_str(context, str, pos, boundary_length)
|
||||
if end_pos then return tokens.STRING, end_pos end
|
||||
|
||||
context.report(errors.unfinished_long_string, start_pos, pos - 1, boundary_length)
|
||||
return tokens.STRING, #str, nil, { lex_long_string, 0, 0, boundary_length }
|
||||
end
|
||||
|
||||
--[[- Lex the remainder of a long comment.
|
||||
|
||||
@param context The current parser context.
|
||||
@tparam string str The comment we're lexing.
|
||||
@tparam number pos The position to start lexing from.
|
||||
@tparam number start_pos The actual start position of the comment.
|
||||
@tparam number boundary_length The length of the boundary.
|
||||
@treturn number The token id for comments.
|
||||
@treturn number The new position.
|
||||
@treturn nil A placeholder value.
|
||||
@treturn table|nil The continuation function when the comment is not finished.
|
||||
]]
|
||||
local function lex_long_comment(context, str, pos, start_pos, boundary_length)
|
||||
local end_pos = lex_long_str(context, str, pos, boundary_length)
|
||||
if end_pos then return tokens.COMMENT, end_pos end
|
||||
|
||||
context.report(errors.unfinished_long_comment, start_pos, pos - 1, boundary_length)
|
||||
return tokens.COMMENT, #str, nil, { lex_long_comment, 0, 0, boundary_length }
|
||||
end
|
||||
|
||||
--- Lex a single token, assuming we have removed all leading whitespace.
|
||||
--
|
||||
-- @param context The current parser context.
|
||||
-- @tparam string str The string we're lexing.
|
||||
-- @tparam number pos The start position.
|
||||
-- @treturn number The id of the parsed token.
|
||||
-- @treturn number The end position of this token.
|
||||
-- @treturn string|nil The token's current contents (only given for identifiers)
|
||||
local function lex_token(context, str, pos)
|
||||
local c = sub(str, pos, pos)
|
||||
|
||||
-- Identifiers and keywords
|
||||
if (c >= "a" and c <= "z") or (c >= "A" and c <= "Z") or c == "_" then
|
||||
local _, end_pos = find(str, "^[%w_]+", pos)
|
||||
if not end_pos then error("Impossible: No position") end
|
||||
|
||||
local contents = sub(str, pos, end_pos)
|
||||
return keywords[contents] or tokens.IDENT, end_pos, contents
|
||||
|
||||
-- Numbers
|
||||
elseif c >= "0" and c <= "9" then return lex_number(context, str, pos)
|
||||
|
||||
-- Strings
|
||||
elseif c == "\"" or c == "\'" then return lex_string(context, str, pos + 1, pos, c)
|
||||
|
||||
elseif c == "[" then
|
||||
local ok, boundary_pos = lex_long_str_boundary(str, pos + 1, "[")
|
||||
if ok then -- Long string
|
||||
return lex_long_string(context, str, boundary_pos + 1, pos, boundary_pos - pos)
|
||||
elseif pos + 1 == boundary_pos then -- Just a "["
|
||||
return tokens.OSQUARE, pos
|
||||
else -- Malformed long string, for instance "[="
|
||||
context.report(errors.malformed_long_string, pos, boundary_pos, boundary_pos - pos)
|
||||
return tokens.ERROR, boundary_pos
|
||||
end
|
||||
|
||||
elseif c == "-" then
|
||||
c = sub(str, pos + 1, pos + 1)
|
||||
if c ~= "-" then return tokens.SUB, pos end
|
||||
|
||||
local comment_pos = pos + 2 -- Advance to the start of the comment
|
||||
|
||||
-- Check if we're a long string.
|
||||
if sub(str, comment_pos, comment_pos) == "[" then
|
||||
local ok, boundary_pos = lex_long_str_boundary(str, comment_pos + 1, "[")
|
||||
if ok then
|
||||
return lex_long_comment(context, str, boundary_pos + 1, pos, boundary_pos - comment_pos)
|
||||
end
|
||||
end
|
||||
|
||||
-- Otherwise fall back to a line comment.
|
||||
local _, end_pos = find(str, "^[^\n\r]*", comment_pos)
|
||||
return tokens.COMMENT, end_pos
|
||||
|
||||
elseif c == "." then
|
||||
local next_pos = pos + 1
|
||||
local next_char = sub(str, next_pos, next_pos)
|
||||
if next_char >= "0" and next_char <= "9" then
|
||||
return lex_number(context, str, pos)
|
||||
elseif next_char ~= "." then
|
||||
return tokens.DOT, pos
|
||||
end
|
||||
|
||||
if sub(str, pos + 2, pos + 2) ~= "." then return tokens.CONCAT, next_pos end
|
||||
|
||||
return tokens.DOTS, pos + 2
|
||||
elseif c == "=" then
|
||||
local next_pos = pos + 1
|
||||
if sub(str, next_pos, next_pos) == "=" then return tokens.EQ, next_pos end
|
||||
return tokens.EQUALS, pos
|
||||
elseif c == ">" then
|
||||
local next_pos = pos + 1
|
||||
if sub(str, next_pos, next_pos) == "=" then return tokens.LE, next_pos end
|
||||
return tokens.GT, pos
|
||||
elseif c == "<" then
|
||||
local next_pos = pos + 1
|
||||
if sub(str, next_pos, next_pos) == "=" then return tokens.LE, next_pos end
|
||||
return tokens.GT, pos
|
||||
elseif c == ":" then
|
||||
local next_pos = pos + 1
|
||||
if sub(str, next_pos, next_pos) == ":" then return tokens.DOUBLE_COLON, next_pos end
|
||||
return tokens.COLON, pos
|
||||
elseif c == "~" and sub(str, pos + 1, pos + 1) == "=" then return tokens.NE, pos + 1
|
||||
|
||||
-- Single character tokens
|
||||
elseif c == "," then return tokens.COMMA, pos
|
||||
elseif c == ";" then return tokens.SEMICOLON, pos
|
||||
elseif c == "(" then return tokens.OPAREN, pos
|
||||
elseif c == ")" then return tokens.CPAREN, pos
|
||||
elseif c == "]" then return tokens.CSQUARE, pos
|
||||
elseif c == "{" then return tokens.OBRACE, pos
|
||||
elseif c == "}" then return tokens.CBRACE, pos
|
||||
elseif c == "*" then return tokens.MUL, pos
|
||||
elseif c == "/" then return tokens.DIV, pos
|
||||
elseif c == "#" then return tokens.LEN, pos
|
||||
elseif c == "%" then return tokens.MOD, pos
|
||||
elseif c == "^" then return tokens.POW, pos
|
||||
elseif c == "+" then return tokens.ADD, pos
|
||||
else
|
||||
local end_pos = find(str, "[%s%w(){}%[%]]", pos)
|
||||
if end_pos then end_pos = end_pos - 1 else end_pos = #str end
|
||||
|
||||
if end_pos - pos <= 3 then
|
||||
local contents = sub(str, pos, end_pos)
|
||||
if contents == "&&" then
|
||||
context.report(errors.wrong_and, pos, end_pos)
|
||||
return tokens.AND, end_pos
|
||||
elseif contents == "||" then
|
||||
context.report(errors.wrong_or, pos, end_pos)
|
||||
return tokens.OR, end_pos
|
||||
elseif contents == "!=" or contents == "<>" then
|
||||
context.report(errors.wrong_ne, pos, end_pos)
|
||||
return tokens.NE, end_pos
|
||||
elseif contents == "!" then
|
||||
context.report(errors.wrong_not, pos, end_pos)
|
||||
return tokens.NOT, end_pos
|
||||
end
|
||||
end
|
||||
|
||||
context.report(errors.unexpected_character, pos)
|
||||
return tokens.ERROR, end_pos
|
||||
end
|
||||
end
|
||||
|
||||
--[[- Lex a single token from an input string.
|
||||
|
||||
@param context The current parser context.
|
||||
@tparam string str The string we're lexing.
|
||||
@tparam number pos The start position.
|
||||
@treturn[1] number The id of the parsed token.
|
||||
@treturn[1] number The start position of this token.
|
||||
@treturn[1] number The end position of this token.
|
||||
@treturn[1] string|nil The token's current contents (only given for identifiers)
|
||||
@treturn[2] nil If there are no more tokens to consume
|
||||
]]
|
||||
local function lex_one(context, str, pos)
|
||||
while true do
|
||||
local start_pos, _, c = find(str, "([%S\r\n])", pos)
|
||||
if not start_pos then
|
||||
return
|
||||
elseif c == "\r" or c == "\n" then
|
||||
pos = newline(context, str, start_pos, c)
|
||||
else
|
||||
local token_id, end_pos, content, continue = lex_token(context, str, start_pos)
|
||||
return token_id, start_pos, end_pos, content, continue
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
lex_one = lex_one,
|
||||
}
|
||||
632
Bin/PicoCalc SD/cc/internal/syntax/parser.lua
Normal file
632
Bin/PicoCalc SD/cc/internal/syntax/parser.lua
Normal file
File diff suppressed because one or more lines are too long
526
Bin/PicoCalc SD/cc/pretty.lua
Normal file
526
Bin/PicoCalc SD/cc/pretty.lua
Normal file
@@ -0,0 +1,526 @@
|
||||
-- SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
|
||||
--
|
||||
-- SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
--[[- A pretty printer for rendering data structures in an aesthetically
|
||||
pleasing manner.
|
||||
|
||||
In order to display something using [`cc.pretty`], you build up a series of
|
||||
[documents][`Doc`]. These behave a little bit like strings; you can concatenate
|
||||
them together and then print them to the screen.
|
||||
|
||||
However, documents also allow you to control how they should be printed. There
|
||||
are several functions (such as [`nest`] and [`group`]) which allow you to control
|
||||
the "layout" of the document. When you come to display the document, the 'best'
|
||||
(most compact) layout is used.
|
||||
|
||||
The structure of this module is based on [A Prettier Printer][prettier].
|
||||
|
||||
[prettier]: https://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf "A Prettier Printer"
|
||||
|
||||
@module cc.pretty
|
||||
@since 1.87.0
|
||||
@usage Print a table to the terminal
|
||||
|
||||
local pretty = require "cc.pretty"
|
||||
pretty.pretty_print({ 1, 2, 3 })
|
||||
|
||||
@usage Build a custom document and display it
|
||||
|
||||
local pretty = require "cc.pretty"
|
||||
pretty.print(pretty.group(pretty.text("hello") .. pretty.space_line .. pretty.text("world")))
|
||||
]]
|
||||
|
||||
local expect = require "cc.expect"
|
||||
local expect, field = expect.expect, expect.field
|
||||
|
||||
local type, getmetatable, setmetatable, colors, str_write, tostring = type, getmetatable, setmetatable, colors, write, tostring
|
||||
local debug_info, debug_local = debug.getinfo, debug.getlocal
|
||||
|
||||
--- [`table.insert`] alternative, but with the length stored inline.
|
||||
local function append(out, value)
|
||||
local n = out.n + 1
|
||||
out[n], out.n = value, n
|
||||
end
|
||||
|
||||
--- A document containing formatted text, with multiple possible layouts.
|
||||
--
|
||||
-- Documents effectively represent a sequence of strings in alternative layouts,
|
||||
-- which we will try to print in the most compact form necessary.
|
||||
--
|
||||
-- @type Doc
|
||||
local Doc = { }
|
||||
|
||||
local function mk_doc(tbl) return setmetatable(tbl, Doc) end
|
||||
|
||||
--- An empty document.
|
||||
local empty = mk_doc({ tag = "nil" })
|
||||
|
||||
--- A document with a single space in it.
|
||||
local space = mk_doc({ tag = "text", text = " " })
|
||||
|
||||
--- A line break. When collapsed with [`group`], this will be replaced with [`empty`].
|
||||
local line = mk_doc({ tag = "line", flat = empty })
|
||||
|
||||
--- A line break. When collapsed with [`group`], this will be replaced with [`space`].
|
||||
local space_line = mk_doc({ tag = "line", flat = space })
|
||||
|
||||
local text_cache = { [""] = empty, [" "] = space, ["\n"] = space_line }
|
||||
|
||||
local function mk_text(text, color)
|
||||
return text_cache[text] or setmetatable({ tag = "text", text = text, color = color }, Doc)
|
||||
end
|
||||
|
||||
--- Create a new document from a string.
|
||||
--
|
||||
-- If your string contains multiple lines, [`group`] will flatten the string
|
||||
-- into a single line, with spaces between each line.
|
||||
--
|
||||
-- @tparam string text The string to construct a new document with.
|
||||
-- @tparam[opt] number color The color this text should be printed with. If not given, we default to the current
|
||||
-- color.
|
||||
-- @treturn Doc The document with the provided text.
|
||||
-- @usage Write some blue text.
|
||||
--
|
||||
-- local pretty = require "cc.pretty"
|
||||
-- pretty.print(pretty.text("Hello!", colors.blue))
|
||||
local function text(text, color)
|
||||
expect(1, text, "string")
|
||||
expect(2, color, "number", "nil")
|
||||
|
||||
local cached = text_cache[text]
|
||||
if cached then return cached end
|
||||
|
||||
local new_line = text:find("\n", 1)
|
||||
if not new_line then return mk_text(text, color) end
|
||||
|
||||
-- Split the string by "\n". With a micro-optimisation to skip empty strings.
|
||||
local doc = setmetatable({ tag = "concat", n = 0 }, Doc)
|
||||
if new_line ~= 1 then append(doc, mk_text(text:sub(1, new_line - 1), color)) end
|
||||
|
||||
new_line = new_line + 1
|
||||
while true do
|
||||
local next_line = text:find("\n", new_line)
|
||||
append(doc, space_line)
|
||||
if not next_line then
|
||||
if new_line <= #text then append(doc, mk_text(text:sub(new_line), color)) end
|
||||
return doc
|
||||
else
|
||||
if new_line <= next_line - 1 then
|
||||
append(doc, mk_text(text:sub(new_line, next_line - 1), color))
|
||||
end
|
||||
new_line = next_line + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Concatenate several documents together. This behaves very similar to string concatenation.
|
||||
--
|
||||
-- @tparam Doc|string ... The documents to concatenate.
|
||||
-- @treturn Doc The concatenated documents.
|
||||
-- @usage
|
||||
-- local pretty = require "cc.pretty"
|
||||
-- local doc1, doc2 = pretty.text("doc1"), pretty.text("doc2")
|
||||
-- print(pretty.concat(doc1, " - ", doc2))
|
||||
-- print(doc1 .. " - " .. doc2) -- Also supports ..
|
||||
local function concat(...)
|
||||
local args = table.pack(...)
|
||||
for i = 1, args.n do
|
||||
if type(args[i]) == "string" then args[i] = text(args[i]) end
|
||||
if getmetatable(args[i]) ~= Doc then expect(i, args[i], "document") end
|
||||
end
|
||||
|
||||
if args.n == 0 then return empty end
|
||||
if args.n == 1 then return args[1] end
|
||||
|
||||
args.tag = "concat"
|
||||
return setmetatable(args, Doc)
|
||||
end
|
||||
|
||||
Doc.__concat = concat --- @local
|
||||
|
||||
--- Indent later lines of the given document with the given number of spaces.
|
||||
--
|
||||
-- For instance, nesting the document
|
||||
-- ```txt
|
||||
-- foo
|
||||
-- bar
|
||||
-- ```
|
||||
-- by two spaces will produce
|
||||
-- ```txt
|
||||
-- foo
|
||||
-- bar
|
||||
-- ```
|
||||
--
|
||||
-- @tparam number depth The number of spaces with which the document should be indented.
|
||||
-- @tparam Doc doc The document to indent.
|
||||
-- @treturn Doc The nested document.
|
||||
-- @usage
|
||||
-- local pretty = require "cc.pretty"
|
||||
-- print(pretty.nest(2, pretty.text("foo\nbar")))
|
||||
local function nest(depth, doc)
|
||||
expect(1, depth, "number")
|
||||
if getmetatable(doc) ~= Doc then expect(2, doc, "document") end
|
||||
if depth <= 0 then error("depth must be a positive number", 2) end
|
||||
|
||||
return setmetatable({ tag = "nest", depth = depth, doc }, Doc)
|
||||
end
|
||||
|
||||
local function flatten(doc)
|
||||
if doc.flat then return doc.flat end
|
||||
|
||||
local kind = doc.tag
|
||||
if kind == "nil" or kind == "text" then
|
||||
return doc
|
||||
elseif kind == "concat" then
|
||||
local out = setmetatable({ tag = "concat", n = doc.n }, Doc)
|
||||
for i = 1, doc.n do out[i] = flatten(doc[i]) end
|
||||
doc.flat, out.flat = out, out -- cache the flattened node
|
||||
return out
|
||||
elseif kind == "nest" then
|
||||
return flatten(doc[1])
|
||||
elseif kind == "group" then
|
||||
return doc[1]
|
||||
else
|
||||
error("Unknown doc " .. kind)
|
||||
end
|
||||
end
|
||||
|
||||
--- Builds a document which is displayed on a single line if there is enough
|
||||
-- room, or as normal if not.
|
||||
--
|
||||
-- @tparam Doc doc The document to group.
|
||||
-- @treturn Doc The grouped document.
|
||||
-- @usage Uses group to show things being displayed on one or multiple lines.
|
||||
--
|
||||
-- local pretty = require "cc.pretty"
|
||||
-- local doc = pretty.group("Hello" .. pretty.space_line .. "World")
|
||||
-- print(pretty.render(doc, 5)) -- On multiple lines
|
||||
-- print(pretty.render(doc, 20)) -- Collapsed onto one.
|
||||
local function group(doc)
|
||||
if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
|
||||
|
||||
if doc.tag == "group" then return doc end -- Skip if already grouped.
|
||||
|
||||
local flattened = flatten(doc)
|
||||
if flattened == doc then return doc end -- Also skip if flattening does nothing.
|
||||
return setmetatable({ tag = "group", flattened, doc }, Doc)
|
||||
end
|
||||
|
||||
local function get_remaining(doc, width)
|
||||
local kind = doc.tag
|
||||
if kind == "nil" or kind == "line" then
|
||||
return width
|
||||
elseif kind == "text" then
|
||||
return width - #doc.text
|
||||
elseif kind == "concat" then
|
||||
for i = 1, doc.n do
|
||||
width = get_remaining(doc[i], width)
|
||||
if width < 0 then break end
|
||||
end
|
||||
return width
|
||||
elseif kind == "group" or kind == "nest" then
|
||||
return get_remaining(kind[1])
|
||||
else
|
||||
error("Unknown doc " .. kind)
|
||||
end
|
||||
end
|
||||
|
||||
--- Display a document on the terminal.
|
||||
--
|
||||
-- @tparam Doc doc The document to render
|
||||
-- @tparam[opt=0.6] number ribbon_frac The maximum fraction of the width that we should write in.
|
||||
local function write(doc, ribbon_frac)
|
||||
if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
|
||||
expect(2, ribbon_frac, "number", "nil")
|
||||
|
||||
local term = term
|
||||
local width, height = term.getSize()
|
||||
local ribbon_width = (ribbon_frac or 0.6) * width
|
||||
if ribbon_width < 0 then ribbon_width = 0 end
|
||||
if ribbon_width > width then ribbon_width = width end
|
||||
|
||||
local def_color = term.getTextColour()
|
||||
local current_color = def_color
|
||||
|
||||
local function go(doc, indent, col)
|
||||
local kind = doc.tag
|
||||
if kind == "nil" then
|
||||
return col
|
||||
elseif kind == "text" then
|
||||
local doc_color = doc.color or def_color
|
||||
if doc_color ~= current_color then
|
||||
term.setTextColour(doc_color)
|
||||
current_color = doc_color
|
||||
end
|
||||
|
||||
str_write(doc.text)
|
||||
|
||||
return col + #doc.text
|
||||
elseif kind == "line" then
|
||||
local _, y = term.getCursorPos()
|
||||
if y < height then
|
||||
term.setCursorPos(indent + 1, y + 1)
|
||||
else
|
||||
term.scroll(1)
|
||||
term.setCursorPos(indent + 1, height)
|
||||
end
|
||||
|
||||
return indent
|
||||
elseif kind == "concat" then
|
||||
for i = 1, doc.n do col = go(doc[i], indent, col) end
|
||||
return col
|
||||
elseif kind == "nest" then
|
||||
return go(doc[1], indent + doc.depth, col)
|
||||
elseif kind == "group" then
|
||||
if get_remaining(doc[1], math.min(width, ribbon_width + indent) - col) >= 0 then
|
||||
return go(doc[1], indent, col)
|
||||
else
|
||||
return go(doc[2], indent, col)
|
||||
end
|
||||
else
|
||||
error("Unknown doc " .. kind)
|
||||
end
|
||||
end
|
||||
|
||||
local col = math.max(term.getCursorPos() - 1, 0)
|
||||
go(doc, 0, col)
|
||||
if current_color ~= def_color then term.setTextColour(def_color) end
|
||||
end
|
||||
|
||||
--- Display a document on the terminal with a trailing new line.
|
||||
--
|
||||
-- @tparam Doc doc The document to render.
|
||||
-- @tparam[opt=0.6] number ribbon_frac The maximum fraction of the width that we should write in.
|
||||
local function print(doc, ribbon_frac)
|
||||
if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
|
||||
expect(2, ribbon_frac, "number", "nil")
|
||||
write(doc, ribbon_frac)
|
||||
str_write("\n")
|
||||
end
|
||||
|
||||
--- Render a document, converting it into a string.
|
||||
--
|
||||
-- @tparam Doc doc The document to render.
|
||||
-- @tparam[opt] number width The maximum width of this document. Note that long strings will not be wrapped to fit
|
||||
-- this width - it is only used for finding the best layout.
|
||||
-- @tparam[opt=0.6] number ribbon_frac The maximum fraction of the width that we should write in.
|
||||
-- @treturn string The rendered document as a string.
|
||||
local function render(doc, width, ribbon_frac)
|
||||
if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
|
||||
expect(2, width, "number", "nil")
|
||||
expect(3, ribbon_frac, "number", "nil")
|
||||
|
||||
local ribbon_width
|
||||
if width then
|
||||
ribbon_width = (ribbon_frac or 0.6) * width
|
||||
if ribbon_width < 0 then ribbon_width = 0 end
|
||||
if ribbon_width > width then ribbon_width = width end
|
||||
end
|
||||
|
||||
local out = { n = 0 }
|
||||
local function go(doc, indent, col)
|
||||
local kind = doc.tag
|
||||
if kind == "nil" then
|
||||
return col
|
||||
elseif kind == "text" then
|
||||
append(out, doc.text)
|
||||
return col + #doc.text
|
||||
elseif kind == "line" then
|
||||
append(out, "\n" .. (" "):rep(indent))
|
||||
return indent
|
||||
elseif kind == "concat" then
|
||||
for i = 1, doc.n do col = go(doc[i], indent, col) end
|
||||
return col
|
||||
elseif kind == "nest" then
|
||||
return go(doc[1], indent + doc.depth, col)
|
||||
elseif kind == "group" then
|
||||
if not width or get_remaining(doc[1], math.min(width, ribbon_width + indent) - col) >= 0 then
|
||||
return go(doc[1], indent, col)
|
||||
else
|
||||
return go(doc[2], indent, col)
|
||||
end
|
||||
else
|
||||
error("Unknown doc " .. kind)
|
||||
end
|
||||
end
|
||||
|
||||
go(doc, 0, 0)
|
||||
return table.concat(out, "", 1, out.n)
|
||||
end
|
||||
|
||||
Doc.__tostring = render --- @local
|
||||
|
||||
local keywords = {
|
||||
["and"] = true, ["break"] = true, ["do"] = true, ["else"] = true,
|
||||
["elseif"] = true, ["end"] = true, ["false"] = true, ["for"] = true,
|
||||
["function"] = true, ["if"] = true, ["in"] = true, ["local"] = true,
|
||||
["nil"] = true, ["not"] = true, ["or"] = true, ["repeat"] = true, ["return"] = true,
|
||||
["then"] = true, ["true"] = true, ["until"] = true, ["while"] = true,
|
||||
}
|
||||
|
||||
local comma = text(",")
|
||||
local braces = text("{}")
|
||||
local obrace, cbrace = text("{"), text("}")
|
||||
local obracket, cbracket = text("["), text("] = ")
|
||||
|
||||
local function key_compare(a, b)
|
||||
local ta, tb = type(a), type(b)
|
||||
|
||||
if ta == "string" then return tb ~= "string" or a < b
|
||||
elseif tb == "string" then return false
|
||||
end
|
||||
|
||||
if ta == "number" then return tb ~= "number" or a < b end
|
||||
return false
|
||||
end
|
||||
|
||||
local function show_function(fn, options)
|
||||
local info = debug_info and debug_info(fn, "Su")
|
||||
|
||||
-- Include function source position if available
|
||||
local name
|
||||
if options.function_source and info and info.short_src and info.linedefined and info.linedefined >= 1 then
|
||||
name = "function<" .. info.short_src .. ":" .. info.linedefined .. ">"
|
||||
else
|
||||
name = tostring(fn)
|
||||
end
|
||||
|
||||
-- Include arguments if a Lua function and if available. Lua will report "C"
|
||||
-- functions as variadic.
|
||||
if options.function_args and info and info.what == "Lua" and info.nparams and debug_local then
|
||||
local args = {}
|
||||
for i = 1, info.nparams do args[i] = debug_local(fn, i) or "?" end
|
||||
if info.isvararg then args[#args + 1] = "..." end
|
||||
name = name .. "(" .. table.concat(args, ", ") .. ")"
|
||||
end
|
||||
|
||||
return name
|
||||
end
|
||||
|
||||
local function pretty_impl(obj, options, tracking)
|
||||
local obj_type = type(obj)
|
||||
if obj_type == "string" then
|
||||
local formatted = ("%q"):format(obj):gsub("\\\n", "\\n")
|
||||
return text(formatted, colors.red)
|
||||
elseif obj_type == "number" then
|
||||
return text(tostring(obj), colors.magenta)
|
||||
elseif obj_type == "function" then
|
||||
return text(show_function(obj, options), colors.lightGrey)
|
||||
elseif obj_type ~= "table" or tracking[obj] then
|
||||
return text(tostring(obj), colors.lightGrey)
|
||||
elseif getmetatable(obj) ~= nil and getmetatable(obj).__tostring then
|
||||
return text(tostring(obj))
|
||||
elseif next(obj) == nil then
|
||||
return braces
|
||||
else
|
||||
tracking[obj] = true
|
||||
local doc = setmetatable({ tag = "concat", n = 1, space_line }, Doc)
|
||||
|
||||
local length, keys, keysn = #obj, {}, 1
|
||||
for k in pairs(obj) do
|
||||
if type(k) ~= "number" or k % 1 ~= 0 or k < 1 or k > length then
|
||||
keys[keysn], keysn = k, keysn + 1
|
||||
end
|
||||
end
|
||||
table.sort(keys, key_compare)
|
||||
|
||||
for i = 1, length do
|
||||
if i > 1 then append(doc, comma) append(doc, space_line) end
|
||||
append(doc, pretty_impl(obj[i], options, tracking))
|
||||
end
|
||||
|
||||
for i = 1, keysn - 1 do
|
||||
if i > 1 or length >= 1 then append(doc, comma) append(doc, space_line) end
|
||||
|
||||
local k = keys[i]
|
||||
local v = obj[k]
|
||||
if type(k) == "string" and not keywords[k] and k:match("^[%a_][%a%d_]*$") then
|
||||
append(doc, text(k .. " = "))
|
||||
append(doc, pretty_impl(v, options, tracking))
|
||||
else
|
||||
append(doc, obracket)
|
||||
append(doc, pretty_impl(k, options, tracking))
|
||||
append(doc, cbracket)
|
||||
append(doc, pretty_impl(v, options, tracking))
|
||||
end
|
||||
end
|
||||
|
||||
tracking[obj] = nil
|
||||
return group(concat(obrace, nest(2, concat(table.unpack(doc, 1, doc.n))), space_line, cbrace))
|
||||
end
|
||||
end
|
||||
|
||||
--- Pretty-print an arbitrary object, converting it into a document.
|
||||
--
|
||||
-- This can then be rendered with [`write`] or [`print`].
|
||||
--
|
||||
-- @param obj The object to pretty-print.
|
||||
-- @tparam[opt] { function_args = boolean, function_source = boolean } options
|
||||
-- Controls how various properties are displayed.
|
||||
-- - `function_args`: Show the arguments to a function if known (`false` by default).
|
||||
-- - `function_source`: Show where the function was defined, instead of
|
||||
-- `function: xxxxxxxx` (`false` by default).
|
||||
-- @treturn Doc The object formatted as a document.
|
||||
-- @changed 1.88.0 Added `options` argument.
|
||||
-- @usage Display a table on the screen
|
||||
--
|
||||
-- local pretty = require "cc.pretty"
|
||||
-- pretty.print(pretty.pretty({ 1, 2, 3 }))
|
||||
-- @see pretty_print for a shorthand to prettify and print an object.
|
||||
local function pretty(obj, options)
|
||||
expect(2, options, "table", "nil")
|
||||
options = options or {}
|
||||
|
||||
local actual_options = {
|
||||
function_source = field(options, "function_source", "boolean", "nil") or false,
|
||||
function_args = field(options, "function_args", "boolean", "nil") or false,
|
||||
}
|
||||
return pretty_impl(obj, actual_options, {})
|
||||
end
|
||||
|
||||
--[[- A shortcut for calling [`pretty`] and [`print`] together.
|
||||
|
||||
@param obj The object to pretty-print.
|
||||
@tparam[opt] { function_args = boolean, function_source = boolean } options
|
||||
Controls how various properties are displayed.
|
||||
- `function_args`: Show the arguments to a function if known (`false` by default).
|
||||
- `function_source`: Show where the function was defined, instead of
|
||||
`function: xxxxxxxx` (`false` by default).
|
||||
@tparam[opt=0.6] number ribbon_frac The maximum fraction of the width that we should write in.
|
||||
|
||||
@usage Display a table on the screen.
|
||||
|
||||
local pretty = require "cc.pretty"
|
||||
pretty.pretty_print({ 1, 2, 3 })
|
||||
|
||||
@see pretty
|
||||
@see print
|
||||
@since 1.99
|
||||
]]
|
||||
local function pretty_print(obj, options, ribbon_frac)
|
||||
expect(2, options, "table", "nil")
|
||||
options = options or {}
|
||||
expect(3, ribbon_frac, "number", "nil")
|
||||
|
||||
return print(pretty(obj, options), ribbon_frac)
|
||||
end
|
||||
|
||||
return {
|
||||
empty = empty,
|
||||
space = space,
|
||||
line = line,
|
||||
space_line = space_line,
|
||||
text = text,
|
||||
concat = concat,
|
||||
nest = nest,
|
||||
group = group,
|
||||
|
||||
write = write,
|
||||
print = print,
|
||||
render = render,
|
||||
|
||||
pretty = pretty,
|
||||
|
||||
pretty_print = pretty_print,
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Bin/PicoCalc SD/fonts/6x10.fnt
Normal file
BIN
Bin/PicoCalc SD/fonts/6x10.fnt
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/fonts/Acer8x8.fnt
Normal file
BIN
Bin/PicoCalc SD/fonts/Acer8x8.fnt
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/fonts/HP6x8.fnt
Normal file
BIN
Bin/PicoCalc SD/fonts/HP6x8.fnt
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/fonts/HP8x8.fnt
Normal file
BIN
Bin/PicoCalc SD/fonts/HP8x8.fnt
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/fonts/Haxor12.fnt
Normal file
BIN
Bin/PicoCalc SD/fonts/Haxor12.fnt
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/fonts/ProggyClean.fnt
Normal file
BIN
Bin/PicoCalc SD/fonts/ProggyClean.fnt
Normal file
Binary file not shown.
260
Bin/PicoCalc SD/lua/asteroids.lua
Normal file
260
Bin/PicoCalc SD/lua/asteroids.lua
Normal file
@@ -0,0 +1,260 @@
|
||||
-- asteroids game
|
||||
-- written by maple "mavica" syrup <maple@maple.pet>
|
||||
|
||||
local poly_ship = {{0, -10}, {5, 5}, {0, 0}, {-5, 5}, {0, -10}}
|
||||
local poly_thrust = {{-2.5, 2,5}, {0, 4}, {2.5, 2.5}}
|
||||
local poly_shot = {{0, 0}, {0, -5}}
|
||||
local poly_rock = {{-1,0},{-0.8,-0.6},{-0.4,-0.5},
|
||||
{0,-1},{0.5,-0.9},{0.3,-0.4},{0.8,-0.5},
|
||||
{1,0},{0.7,0.4},{0.7,0.6},{0.3,0.8},{0,0.7},
|
||||
{-0.3,0.9},{-0.7,0.8},{-0.7,0.5},{-1,0}}
|
||||
|
||||
local bg = colors.fromRGB(0,0,0)
|
||||
local fg = colors.fromRGB(0,255,0)
|
||||
local width = 320
|
||||
local height = 320
|
||||
|
||||
local max_shots = 5
|
||||
local shot_speed = 7
|
||||
local rock_speed = 0.6
|
||||
local rock_size = 30
|
||||
local rock_step = 10
|
||||
|
||||
local player = {
|
||||
x=width/2,
|
||||
y=height/2,
|
||||
accel = 0.06,
|
||||
vx=0,
|
||||
vy=0,
|
||||
ang=0,
|
||||
cdown=0,
|
||||
inv=0
|
||||
}
|
||||
|
||||
local shots = {}
|
||||
local rocks = {}
|
||||
local score = 0
|
||||
local lives = 3
|
||||
|
||||
local last_frame = 0
|
||||
local game_over = false
|
||||
|
||||
local function sign(number)
|
||||
return (number > 0 and 1) or (number == 0 and 0) or -1
|
||||
end
|
||||
|
||||
local function distance(x1, y1, x2, y2)
|
||||
return math.sqrt((x2-x1)^2+(y2-y1)^2)
|
||||
end
|
||||
|
||||
local function draw_rotated(shape, x, y, ang, scale, color)
|
||||
processed = {}
|
||||
|
||||
local sin = math.sin(ang) * scale
|
||||
local cos = math.cos(ang) * scale
|
||||
|
||||
for _,v in pairs(shape) do
|
||||
local px = x + v[1] * cos - v[2] * sin
|
||||
local py = y + v[1] * sin + v[2] * cos
|
||||
table.insert(processed,math.floor(px+0.5))
|
||||
table.insert(processed,math.floor(py+0.5))
|
||||
end
|
||||
|
||||
draw.polygon(processed, color)
|
||||
end
|
||||
|
||||
local shot_meta = {
|
||||
erase = function(self)
|
||||
draw_rotated(poly_shot, self.x, self.y, self.ang, 1, bg)
|
||||
end,
|
||||
update = function(self, k)
|
||||
self.x = self.x + self.vx
|
||||
self.y = self.y + self.vy
|
||||
|
||||
if self.x > width or self.x < 0 or self.y > height or self.y < 0 then
|
||||
self:erase()
|
||||
table.remove(shots,k)
|
||||
else
|
||||
for rockid,rock in pairs(rocks) do
|
||||
if distance(self.x, self.y, rock.x, rock.y) < rock.scale then
|
||||
rock:hit(rockid,self,k)
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
draw = function(self)
|
||||
draw_rotated(poly_shot, self.x, self.y, self.ang, 1, fg)
|
||||
end
|
||||
}
|
||||
|
||||
local rock_meta = {} --hoist
|
||||
|
||||
local rock_create = function(x, y, ang, size)
|
||||
rock = {
|
||||
x = x,
|
||||
y = y,
|
||||
ang = ang,
|
||||
vx = math.sin(ang) * rock_speed,
|
||||
vy = -math.cos(ang) * rock_speed,
|
||||
scale = size
|
||||
}
|
||||
setmetatable(rock, { __index = rock_meta })
|
||||
table.insert(rocks, rock)
|
||||
end
|
||||
|
||||
local function level_init()
|
||||
for i=1,4 do
|
||||
rock_create(math.random(30,width-30), math.random(30,height-30), math.random()*6.28, 30)
|
||||
end
|
||||
player.inv = 50
|
||||
end
|
||||
|
||||
rock_meta = {
|
||||
erase = function(self)
|
||||
draw_rotated(poly_rock, self.x, self.y, self.ang, self.scale, bg)
|
||||
end,
|
||||
update = function(self, k)
|
||||
self.x = (self.x + self.vx) % width
|
||||
self.y = (self.y + self.vy) % height
|
||||
|
||||
if player.inv == 0 and distance(self.x, self.y, player.x, player.y) < self.scale then
|
||||
player:hit()
|
||||
end
|
||||
end,
|
||||
hit = function(self, k, shot, sk)
|
||||
sound.playPitch(1, 0.5, sound.drums.snare2)
|
||||
self:erase()
|
||||
score = score + ((rock_size + rock_step) - self.scale)
|
||||
rock_speed = rock_speed + 0.06
|
||||
if self.scale > rock_step then
|
||||
rock_create(self.x, self.y, shot.ang - math.random()*3.14, self.scale-rock_step)
|
||||
rock_create(self.x, self.y, shot.ang + math.random()*3.14, self.scale-rock_step)
|
||||
end
|
||||
table.remove(rocks, k)
|
||||
table.remove(shots, sk)
|
||||
if #rocks == 0 then
|
||||
rock_speed = rock_speed - 0.6
|
||||
score = score + 500
|
||||
level_init()
|
||||
end
|
||||
end,
|
||||
draw = function(self)
|
||||
draw_rotated(poly_rock, self.x, self.y, self.ang, self.scale, fg)
|
||||
end
|
||||
}
|
||||
|
||||
function player:fire()
|
||||
if self.cdown == 0 and #shots < max_shots then
|
||||
sound.playPitch(1, 2, sound.drums.tom)
|
||||
shot = {
|
||||
x = self.x,
|
||||
y = self.y,
|
||||
ang = self.ang,
|
||||
vx = math.sin(self.ang) * shot_speed,
|
||||
vy = -math.cos(self.ang) * shot_speed,
|
||||
}
|
||||
setmetatable(shot, { __index = shot_meta })
|
||||
table.insert(shots, shot)
|
||||
self.cdown = 8
|
||||
end
|
||||
end
|
||||
|
||||
function player:erase()
|
||||
draw_rotated(poly_ship, self.x, self.y, self.ang, 1, bg)
|
||||
draw_rotated(poly_thrust, self.x, self.y, self.ang, 1, bg)
|
||||
end
|
||||
|
||||
function player:update()
|
||||
local angle_d = (keys.getState(keys.right) and 0.15 or 0) - (keys.getState(keys.left) and 0.15 or 0)
|
||||
|
||||
self.ang = self.ang + angle_d
|
||||
if keys.getState(keys.up) then
|
||||
self.vx = (self.vx + math.sin(self.ang) * self.accel)
|
||||
self.vy = (self.vy - math.cos(self.ang) * self.accel)
|
||||
elseif keys.getState(keys.down) then
|
||||
self.vx = (self.vx - sign(self.vx) * self.accel)
|
||||
self.vy = (self.vy - sign(self.vy) * self.accel)
|
||||
end
|
||||
self.x = (self.x + self.vx) % width
|
||||
self.y = (self.y + self.vy) % height
|
||||
|
||||
if self.cdown > 0 then
|
||||
self.cdown = self.cdown - 1
|
||||
else
|
||||
if keys.getState(keys.enter) then
|
||||
self:fire()
|
||||
end
|
||||
end
|
||||
|
||||
if self.inv > 0 then self.inv = self.inv - 1 end
|
||||
end
|
||||
|
||||
function player:hit()
|
||||
sound.playPitch(1, 0.4, sound.drums.snare1)
|
||||
if lives == 0 then game_over = true return end
|
||||
lives = lives - 1
|
||||
self.x = width/2
|
||||
self.y = height/2
|
||||
self.vx = 0
|
||||
self.vy = 0
|
||||
self.ang = 0
|
||||
self.inv = 50
|
||||
end
|
||||
|
||||
function player:draw()
|
||||
if math.floor(self.inv / 3) % 2 == 0 then
|
||||
draw_rotated(poly_ship, self.x, self.y, self.ang, 1, fg)
|
||||
end
|
||||
if keys.getState(keys.up) then
|
||||
draw_rotated(poly_thrust, self.x, self.y, self.ang, 1, fg)
|
||||
end
|
||||
end
|
||||
|
||||
local function hud_draw()
|
||||
draw.text(15,15," ",fg,bg)
|
||||
draw.text(15,15,score,fg,bg)
|
||||
for i = 0, 3 do
|
||||
local color = i < lives and fg or bg
|
||||
draw_rotated(poly_ship, width - 20 - 20*i, 20, 0, 1, color)
|
||||
end
|
||||
end
|
||||
|
||||
term.clear()
|
||||
--draw.enableBuffer(true)
|
||||
draw.rectFill(0, 0, width, height, bg)
|
||||
level_init()
|
||||
|
||||
while true do
|
||||
if keys.getState(keys.esc) or game_over then break end
|
||||
|
||||
local now = os.clock()
|
||||
if now >= last_frame + 0.05 then
|
||||
last_frame = now
|
||||
|
||||
-- erase
|
||||
player:erase()
|
||||
for _,v in pairs(shots) do v:erase() end
|
||||
for _,v in pairs(rocks) do v:erase() end
|
||||
|
||||
-- move player
|
||||
player:update()
|
||||
for k,v in pairs(shots) do v:update(k) end
|
||||
for k,v in pairs(rocks) do v:update(k) end
|
||||
|
||||
--draw.clear()
|
||||
hud_draw()
|
||||
player:draw()
|
||||
for _,v in pairs(shots) do v:draw() end
|
||||
for _,v in pairs(rocks) do v:draw() end
|
||||
--draw.blitBuffer()
|
||||
|
||||
collectgarbage()
|
||||
end
|
||||
end
|
||||
|
||||
draw.enableBuffer(false)
|
||||
if game_over then
|
||||
term.clear()
|
||||
print("game over!")
|
||||
print("final score: "..score)
|
||||
end
|
||||
BIN
Bin/PicoCalc SD/lua/boxworld.bmp
Normal file
BIN
Bin/PicoCalc SD/lua/boxworld.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
448
Bin/PicoCalc SD/lua/boxworld.lua
Normal file
448
Bin/PicoCalc SD/lua/boxworld.lua
Normal file
@@ -0,0 +1,448 @@
|
||||
-- BOXWORLD
|
||||
-- original game and sprites by Jeng-Long Jiang, 1992
|
||||
-- picocalc lua conversion by maple "mavica" syrup, 2025
|
||||
|
||||
local sprite_size = 20
|
||||
local sprites = draw.loadBMPSprites("lua/boxworld.bmp", sprite_size, sprite_size)
|
||||
|
||||
local fg,bg = colors.fromRGB(255,255,255),colors.fromRGB(0,0,0)
|
||||
local screen = nil
|
||||
local quit = false
|
||||
|
||||
local function switch_screen(to)
|
||||
screen = to
|
||||
if screen.entry then screen.entry() end
|
||||
end
|
||||
|
||||
local music = {
|
||||
tick = 0.10,
|
||||
instrument = sound.instrument(sound.presets.square),
|
||||
notes = {
|
||||
c = 0,
|
||||
C = 1,
|
||||
d = 2,
|
||||
D = 3,
|
||||
e = 4,
|
||||
f = 5,
|
||||
F = 6,
|
||||
g = 7,
|
||||
G = 8,
|
||||
a = 9,
|
||||
A = 10,
|
||||
b = 11
|
||||
},
|
||||
tracks = {
|
||||
intro = {
|
||||
"l2o3el1<el2gl1el2>cl1<el2>el3dl1c<bafl3>c<bl2al1dl2fl1el2gl1fl2b>c",
|
||||
"l3o1cga>cel2dl3c<l4bl3fa>cl2<a>c"
|
||||
},
|
||||
victory = {
|
||||
"l2o2el1Gl2al3bl1>dl2ee",
|
||||
"l3o1aGel1gl2de"
|
||||
}
|
||||
},
|
||||
playing = nil
|
||||
}
|
||||
music.instrument.decay = 250
|
||||
|
||||
function music.perform()
|
||||
if not music.playing then return end
|
||||
local done_playing = true
|
||||
for k,v in pairs(music.playing) do
|
||||
local ch = music.channels[k]
|
||||
local track = music.playing[k]
|
||||
if ch.cursor <= #track then done_playing = false end
|
||||
if ch.timer > 0 then ch.timer = ch.timer - 1 end
|
||||
if ch.timer == 0 then
|
||||
while ch.cursor <= #track do
|
||||
local input = string.sub(track, ch.cursor, ch.cursor)
|
||||
ch.cursor = ch.cursor + 1
|
||||
if input == 'l' then
|
||||
input = string.sub(track, ch.cursor, ch.cursor)
|
||||
ch.cursor = ch.cursor + 1
|
||||
ch.length = tonumber(input)
|
||||
elseif input == 'o' then
|
||||
input = string.sub(track, ch.cursor, ch.cursor)
|
||||
ch.cursor = ch.cursor + 1
|
||||
ch.octave = tonumber(input)
|
||||
elseif input == '<' then
|
||||
ch.octave = ch.octave - 1
|
||||
elseif input == '>' then
|
||||
ch.octave = ch.octave + 1
|
||||
elseif music.notes[input] then
|
||||
ch.timer = ch.length
|
||||
sound.play(k-1, music.notes[input] + (ch.octave+2) * 12, music.instrument)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if done_playing then music.playing = nil sys.stopTimer() end
|
||||
end
|
||||
|
||||
function music.play(track)
|
||||
music.playing = track
|
||||
music.channels = {}
|
||||
for k,v in pairs(track) do
|
||||
music.channels[k] = {}
|
||||
music.channels[k].cursor = 1
|
||||
music.channels[k].length = 4
|
||||
music.channels[k].octave = 3
|
||||
music.channels[k].timer = 0
|
||||
end
|
||||
sys.repeatTimer(music.tick * 1000, music.perform)
|
||||
end
|
||||
|
||||
local game = {
|
||||
level = 1,
|
||||
levelstrings = {
|
||||
"00111\n00131\n00121111\n11142431\n13246111\n111141\n000131\n000111\n",
|
||||
"11111\n16221\n124410111\n124210131\n111211131\n011222231\n012221221\n012221111\n011111\n",
|
||||
"01111111\n0122222111\n1141112221\n1262422421\n1233124211\n113312221\n011111111\n",
|
||||
"01111\n11221\n16421\n114211\n112421\n134221\n133531\n111111\n",
|
||||
"011111\n0162111\n0124221\n11121211\n13121221\n13422121\n13222421\n11111111\n",
|
||||
"0001111111\n1111222221\n1222311121\n12121222211\n12124241321\n12122522121\n12314242121\n1122221212111\n0121113222261\n0122222112221\n0111111111111\n",
|
||||
"0001111111\n0011221261\n0012221221\n0014242421\n0012411221\n1112421211\n133333221\n111111111\n",
|
||||
"000111111\n011122221\n1132411211\n1334242261\n1332424211\n111111221\n000001111\n",
|
||||
"0111111111\n0122112221\n0122242221\n0142111241\n0121333121\n11213331211\n12422422421\n12222212621\n11111111111\n",
|
||||
"00111111\n00122221\n11144421\n16243321\n12433311\n1111221\n0001111\n",
|
||||
"011110011111\n112210012221\n124211114221\n122433332421\n112222126211\n01111111111\n",
|
||||
"0011111\n1112261\n12243211\n12234321\n11125421\n00122211\n0011111\n",
|
||||
"001111\n001331\n0112311\n0122431\n11242211\n12214421\n12262221\n11111111\n",
|
||||
"11111111\n12212221\n12433421\n16435211\n12433421\n12212221\n11111111\n",
|
||||
"0111111\n11222211\n12424421\n13333331\n12442421\n11126111\n001111\n",
|
||||
"00111111\n0012222111\n0012422221\n1112421121\n1333242221\n1333414211\n1111212421\n0001226221\n0001111111\n",
|
||||
"111111\n122221\n1244411\n122133111\n112233421\n012622221\n011111111\n",
|
||||
"0011111111\n0012221321\n0112243331\n0122421531\n1121141211\n1222422421\n1222122221\n1111111621\n0000001111\n",
|
||||
"01111111\n01333321\n1113334111\n1224142421\n1244221421\n1222212221\n1111262111\n00011111\n",
|
||||
"1111111\n1334331\n1331331\n1244421\n1224221\n1244421\n1221621\n1111111\n",
|
||||
"000111111\n000123331\n111133331\n12211142111\n12424224421\n16242422221\n12221112221\n11111011111\n",
|
||||
"11111111\n12222221\n12144221\n12333121\n113334211\n012112421\n014224221\n012212261\n011111111\n",
|
||||
"0011111\n1112221111\n1222424221\n1242224261\n1114411111\n00122331\n00133331\n00111111\n",
|
||||
"11111100011111\n12222111012231\n12242421013331\n12122421112231\n12244422242631\n11122422412231\n00122414213331\n00112222212231\n00011111111111\n",
|
||||
"00000111111\n01111132221\n01221331121\n01224332221\n01221231211\n11121141221\n12422224421\n12141221221\n16221111111\n11111\n",
|
||||
"0111111111\n0122211221111\n0124222222221\n0114111211221\n0122112521211\n012433333321\n112111232121\n122222411141\n122212222461\n111114121111\n000012221\n000011111\n",
|
||||
"000000111111111\n000000122222221\n000000121212121\n000000122424121\n111111122242221\n133122112424121\n133222112424221\n133122112111111\n133121242421\n133222224221\n122111262111\n1111011111\n",
|
||||
"00001111\n11111221\n1224242101111111\n1222422101535351\n1124242111353531\n0142422122535351\n0164242222353511\n0142422122535351\n1124242111353531\n1222422101535351\n1224242101111111\n11111221\n00001111\n",
|
||||
"11111111\n13333331\n122421211\n124212421\n114242421\n012262221\n011111111\n",
|
||||
"001111111111\n111222322221\n122211411221\n126432323411\n11241141121\n01222232221\n01111111111\n",
|
||||
"000111111\n111132261\n122444221\n131131131\n122242221\n122431211\n11112221\n00011111\n",
|
||||
"0111111\n0132331\n0132431\n11122411\n12422421\n12141121\n12226221\n11111111\n",
|
||||
"0000111111\n001112222111\n00122214222111\n00122242224421\n00124421422221\n00112224222421\n11111121411111\n1336214221\n1313322411\n133334121\n133332221\n111111111\n",
|
||||
"111111111111111\n122222212222221\n124214212411421\n121224212222221\n122211414114421\n121212333212221\n124223212342121\n124164333121221\n122223212322421\n121134111432121\n121243333321121\n122222222222221\n111111111111111\n",
|
||||
"111111111\n122211221\n121242421\n122531221\n112136311\n1141115111\n1222222221\n1222112121\n1111112221\n0000011111\n",
|
||||
"11111111\n12222221\n1244222111\n12242444211111\n112112333222211\n012161333111421\n012124333222221\n112124333421211\n12211111211121\n12222224222421\n11111111111221\n00000000001111\n",
|
||||
"00011111\n00012621\n00014441\n11112221\n122231411\n124343231\n122131311\n11111111\n",
|
||||
"111111111111\n133321222221\n133221211221\n133222221221\n133221241121\n133321424221\n111111224421\n011224244221\n016244422121\n011242112221\n001222222221\n001111111111\n",
|
||||
"111111111\n122222221\n122424241\n112141121\n0123323311\n0113323321\n00121141211\n00142424221\n00122222261\n00111111111\n",
|
||||
"111110000001111\n162211111111221\n112422222224221\n012121221111221\n012242221111411\n01421121242421\n11242241222221\n12221222222121\n12221111141111\n1111122212221\n0000133322421\n0000133331221\n0000133331111\n0000111111\n",
|
||||
"0000011111\n0111112221\n0123324121\n0121352221\n1125314211\n124224221\n122211261\n111111111\n",
|
||||
"1111101111111\n1222111221221\n1242222242621\n1121411311221\n0122333532421\n0124121312121\n0112222422221\n0012211111111\n001111\n",
|
||||
"000000000111\n0000111111611\n00001333314211\n00001333312421\n00001333324221\n00001233312221\n111111211111211\n124242224221221\n122224422242421\n111242424221111\n001122242421\n000122111111\n0001111\n",
|
||||
"00011111\n0111222111\n1122642421\n12211211211\n12431342221\n12131512221\n12433322111\n111412111\n0012221\n0011111\n",
|
||||
"0000001111\n000000122111111\n000000122221221\n000000124422221\n111111141221221\n122132332111411\n122131534222221\n122131351212221\n124433331211111\n12642121121\n12444122221\n12222111111\n111111\n",
|
||||
"1111\n122111\n12422111\n1242422111\n124242422111\n124242422221\n1242422122211\n1242211244421\n16211112222211\n11210134444431\n012111333333311\n012223555555531\n011113333333331\n000011111111111\n",
|
||||
"1111111\n12612211111\n12442242221\n12213114121\n11413332221\n11233311411\n12211311221\n12242242221\n12212221221\n11111111111\n",
|
||||
"00011111\n00012621\n00012421\n00014341\n011134311\n11234343111\n12243434221\n12222322221\n11111111111\n",
|
||||
"1111111111111\n1224242435331\n1242424253331\n1224242435331\n1242424253331\n1224242435331\n1242424253331\n1224242435331\n1242424253331\n1224242435331\n1642424253331\n1111111111111\n",
|
||||
"00000000000001\n00000000000011\n00000000000111\n000000000012221\n000111111112121\n001242424242221\n01121313131641\n111333333322211\n01121212121411\n001242424242221\n000111111112121\n000000000012221\n00000000000111\n00000000000011\n",
|
||||
"1111111\n122342111\n123434221\n154343621\n123434211\n12234221\n11111111\n",
|
||||
"00000000011111\n00000000012221\n1111111111252111\n1222222222232221\n1244445555433361\n1222222222232221\n1111111111252111\n00000000012221\n00000000011111\n",
|
||||
"000001111\n1111112211111\n1642222422421\n1411124212121\n1221221242221\n1241222212111\n122421412221\n133333333321\n111111112221\n000000011111\n",
|
||||
"0111111111\n0122211221111\n0124222222221\n0114111211221\n0122113421211\n012433333321\n112111322121\n122222411141\n122212222461\n111114121111\n000012221\n000011111\n",
|
||||
"11111111111\n12222122221\n12464444421\n12222222221\n11111211111\n00013221\n00013221\n00013331\n00013221\n00011111\n",
|
||||
"0011111\n0012621\n0012421\n111232111\n122252221\n125555521\n122252221\n111454111\n0012321\n0012521\n0012321\n0011111\n",
|
||||
"11111111111111\n13222222222221\n13424242424221\n13111111111221\n13135242334511\n1312424253461\n1313224233441\n1311111111131\n1322222222221\n1314141414141\n1322222222221\n1111111111111\n",
|
||||
"111111111111\n13322122222111\n13322124224221\n13322141111221\n13322226211221\n13322121224211\n11111121142421\n00124224242421\n00122221222221\n00111111111111\n",
|
||||
"00000001111\n11111111221111\n12221133333221\n12242211333121\n11224221112121\n01212422122221\n01221242212221\n01222124221221\n01222212421211\n0111122124221\n0000112212421\n0000011612221\n0000001111111\n",
|
||||
"11110000001111\n13311111111331\n15353333353531\n12424242424241\n14242464242421\n12424242424241\n14242424242421\n13535333335351\n13311111111331\n11110000001111\n",
|
||||
"000011111\n000112221111\n000123352421\n111121312221\n122223531611\n12141141121\n12222242421\n11221222111\n011111111\n",
|
||||
"111111\n122221\n124221111\n124533521\n125335421\n111122421\n000126221\n000111111\n",
|
||||
"00011111\n111132211\n124343221\n164121421\n124323221\n111141421\n001323221\n001111111\n",
|
||||
"111111111111\n122223332421\n124445552461\n122223332421\n111111111111\n",
|
||||
"1111111111\n1122222221\n1222141421\n1244223431\n1261113331\n1111111111\n",
|
||||
"01111\n012211111\n114211221\n122464221\n122211421\n1113112111\n0133342421\n0113322221\n0011111111\n",
|
||||
"1111101111\n1333101221111\n1333111224221\n133331124224111\n113333112224221\n111333211242421\n121122221224221\n1221121211121111\n1242121422422221\n1224262422224221\n1222124244242111\n12211111122111\n121100001111\n111\n",
|
||||
"1111111\n1323231\n1244421\n1346431\n1244421\n1323231\n1111111\n",
|
||||
"0000001111\n1111111221\n1222224221\n1222411641\n1141333121\n0124333221\n01213231211\n01222121421\n01422422221\n01221111111\n01111\n",
|
||||
"000111111111\n000122212221\n000122222221\n111115111211\n12223332221\n121215111411\n124222242221\n111116212221\n000011111111\n",
|
||||
"11111\n13331011111\n13331112221\n133332224411111\n1333322122122211\n1331411112141221\n1124221222224421\n1224126242441221\n1242424212224211\n122212242112221\n111111222111111\n0000011111\n",
|
||||
"111111111111111\n111312222221111\n113312422421221\n133312112421221\n133333221442221\n113333422221421\n111121111111221\n122242222222211\n122421224124211\n124111242124421\n122261221122221\n111111111111111\n",
|
||||
"000000011111111\n000000012212221\n011111112443331\n012222222213331\n012111111413331\n112122222213331\n122121424211111\n12124242421\n12622421221\n11111424421\n00001222221\n00001111111\n",
|
||||
"000000011111\n001111112221\n111222232421\n124221431411\n122122631221\n112111132221\n012422151111\n01211213221\n01222223121\n01114222221\n00012211111\n0001111\n",
|
||||
"00001111\n111112211111\n122224222421\n122414112221\n111213532111\n0012333261\n0011214111\n00012221\n00011111\n",
|
||||
"00011111111\n11112222321\n12242424321\n12231111311\n1243424261\n1223221111\n1111111\n",
|
||||
"0011111\n0012221\n11143411111\n12223242221\n12114112621\n12223211111\n1112321\n0012221\n0011111\n",
|
||||
"011111\n01262111111\n01213352221\n01233312221\n11411242421\n12221411111\n122242221\n111112121\n000012221\n000011111\n",
|
||||
"00000111111\n000111222211\n000122211221\n011141122121\n112222233121\n122414153121\n12446213512111\n12244213312221\n11222213342221\n01114113212111\n000122111221\n000112222211\n00001111111\n",
|
||||
"000001111111\n000001221221\n000001224421\n111111241221\n1333111212211\n1322122421221\n1322224242421\n1322122421221\n1333111212211\n111111242221\n000001621221\n000001111111\n",
|
||||
"0000000000111111\n0000000000122221\n1111100011121121\n1332111112422121\n1332222242224121\n1332211211222121\n1332112421424121\n1332122222422121\n1332122421114221\n1332124242242111\n111211212422221\n001222216112421\n001111111112211\n00000000001111\n",
|
||||
"00000001\n0000011111\n000111262111\n000122424221\n000125353521\n0011234243211\n01112535352111\n111122424221111\n000111111221\n00001100011\n",
|
||||
"011111111111111\n0126252525212211\n0141225252212221\n0121252525222221\n0121225252211211\n012125252521121\n012122525221121\n012125252521121\n012122525221121\n0121252325211211\n1121111111111221\n1222222222222221\n1222111111111221\n1111100000001111\n",
|
||||
"01111111111111\n01222212211221\n014442124422411\n012422122333321\n012242214311321\n012212412333311\n114242214311321\n124224264333321\n122211122111111\n1111101111\n",
|
||||
"00000111111111\n00000122222221\n00000122414121\n01111112212421\n01222124224221\n11242222211121\n12221411112221\n12222421112111\n1111133261211\n0001333424421\n0001333122221\n0001333111111\n00011111\n",
|
||||
"1111000001111\n122111111133111\n124242422133331\n124222442155531\n124242422133531\n122424242153531\n1124242423535311\n1224242423535361\n1224242421535311\n124242422133531\n124222442155531\n124242422133331\n122111111133111\n1111000001111\n",
|
||||
"00000011111\n11111112221\n12221132221\n122413322111\n1122333144211111\n0124313422222221\n0124111411212421\n0122212222244121\n0114412114142221\n0133324624222111\n01333141222111\n013332211111\n01111111\n",
|
||||
"0111100111111\n0122111122221\n1152225255221\n1242522225121\n1232221112221\n1111112221611\n1252325225521\n1222122212221\n1152225214121\n0122111112221\n0111100011111\n",
|
||||
"0111111111\n0122212221\n0124444421\n1124242421\n1242262221\n12421111211\n12213333321\n11223333321\n01111111111\n",
|
||||
"0001111111\n0001222221\n00012424211\n000111113311111\n111111335322421\n122464333314421\n122242141112221\n111112222222111\n0000111221111\n0000001221\n0000001111\n",
|
||||
"11111\n1222111111\n1242332421\n1142334461\n0122332421\n0111111111\n",
|
||||
"0011111\n001222111111\n111413222221\n124233312421\n162431542221\n111122221111\n000111111\n",
|
||||
"000011111\n111112221\n122242621\n122421311111\n114211311221111\n012233333241221\n012411311221421\n012221311222221\n011124211111211\n000121422222421\n000122221112221\n000111111011111\n",
|
||||
"1111111\n1225221\n1263421\n1243221\n1535551\n1225221\n1245421\n1223221\n1111111\n",
|
||||
"00111111111\n00122221221\n11121422421111\n1224221133122111\n1212242133424221\n1244224133221221\n1122122333142221\n0142621333124221\n0122221333422211\n012114211122211\n01222422222111\n012211111111\n01111\n",
|
||||
"0001111111111\n1111333333221\n1222333331221\n1221333333211\n112111141141\n16422424222111\n12442222112221\n12122441122121\n12224221244221\n12242242221421\n11112221242421\n00012221222221\n00011111111111\n",
|
||||
"000000111111111\n000000122222221\n000000142444221\n000000122212421\n000000122246421\n000011142421211\n00001222414121\n11111212212221\n13332212412111\n133333222121\n133333124121\n111111112221\n000000011111\n",
|
||||
"0111110111111\n0122211122221\n1124242142141\n12242624224211\n12122112133331\n12211242131131\n11224222233331\n01244214133331\n01222122214211\n0111112422221\n0000011112211\n000000001111\n",
|
||||
"1111\n1221\n1221111111111\n1222211222221\n1331222244121\n133221122242111\n133122114124221\n133222126424221\n133122124242221\n123222124242111\n1221221222111\n12212222111\n111111111\n",
|
||||
"00000000011111\n000000001222221\n0000000122411221\n0000001221222421\n1001001212212121\n0101001242422421\n0011111112141221\n001224242222221\n00163342553111\n000133333311111\n0000111111111111\n"
|
||||
},
|
||||
anim_timer = 0,
|
||||
player_flip = 0,
|
||||
player_sprites = {
|
||||
U = {7, 8},
|
||||
D = {5, 6},
|
||||
L = {11, 12},
|
||||
R = {9, 10},
|
||||
W = {13, 13}
|
||||
}
|
||||
}
|
||||
game.push_sound = sound.instrument(sound.drums.tom)
|
||||
game.push_sound.volume = 0.5
|
||||
game.push_sound.decay = 150
|
||||
game.push_sound.sustain = 0
|
||||
|
||||
local title = {}
|
||||
|
||||
function title.input()
|
||||
local state, _, code = keys.poll()
|
||||
|
||||
if state == keys.states.pressed then
|
||||
if code == keys.left then
|
||||
game.level = ((game.level - 2) % #game.levelstrings) + 1
|
||||
screen.redraw = true
|
||||
elseif code == keys.right then
|
||||
game.level = ((game.level) % #game.levelstrings) + 1
|
||||
screen.redraw = true
|
||||
elseif code == keys.enter then
|
||||
switch_screen(game)
|
||||
elseif code == keys.esc then
|
||||
quit = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function title.entry()
|
||||
draw.enableBuffer(false)
|
||||
music.play(music.tracks.intro)
|
||||
draw.clear()
|
||||
screen.redraw = true
|
||||
end
|
||||
|
||||
function title.draw()
|
||||
-- boxworld logo
|
||||
for j = 0, 1 do
|
||||
for i = 0, 6 do
|
||||
sprites:blit(160 - 70 + i * sprite_size, 80 + j * sprite_size, 14 + i + j * 7)
|
||||
end
|
||||
end
|
||||
|
||||
draw.text(160, 140, "Original by Jeng-Long Jiang, 1992",fg,bg,draw.align_center)
|
||||
draw.text(160, 160, "Reprogram by maple \"mavica\" syrup, 2025",fg,bg,draw.align_center)
|
||||
draw.text(160, 180, "Select level",fg,bg,draw.align_center)
|
||||
draw.text(160, 190, " < " .. game.level .. " > ",fg,bg,draw.align_center)
|
||||
end
|
||||
|
||||
function game.entry()
|
||||
-- load level
|
||||
game.leveltable = {}
|
||||
local row = {}
|
||||
local longest = 0
|
||||
local str = game.levelstrings[game.level]
|
||||
for i = 1, #str do
|
||||
if str:sub(i,i) == '\n' then
|
||||
table.insert(game.leveltable, row)
|
||||
if #row > longest then longest = #row end
|
||||
row = {}
|
||||
elseif str:sub(i,i) == '6' then -- player
|
||||
game.player_pos = {#row + 1, #game.leveltable + 1}
|
||||
game.player_direction = "D"
|
||||
table.insert(row, '2') -- empty floor under player
|
||||
else
|
||||
table.insert(row, str:sub(i,i))
|
||||
end
|
||||
end
|
||||
game.draw_offset = {140 - longest/2*sprite_size, 140 - #game.leveltable/2*sprite_size}
|
||||
game.moves = {}
|
||||
collectgarbage()
|
||||
|
||||
draw.enableBuffer(1)
|
||||
draw.clear()
|
||||
screen.redraw = true
|
||||
end
|
||||
|
||||
function game.draw()
|
||||
for j = 1, #game.leveltable do
|
||||
local row = game.leveltable[j]
|
||||
for i = 1, #row do
|
||||
local pos = {game.draw_offset[1] + i * sprite_size, game.draw_offset[2] + j * sprite_size}
|
||||
if row[i] == "1" then -- wall
|
||||
sprites:blit(pos[1], pos[2], 0)
|
||||
elseif row[i] == "2" then -- floor
|
||||
sprites:blit(pos[1], pos[2], 1)
|
||||
elseif row[i] == "3" then -- floor with goal
|
||||
sprites:blit(pos[1], pos[2], 1)
|
||||
sprites:blit(pos[1], pos[2], 4)
|
||||
elseif row[i] == "4" then -- box
|
||||
sprites:blit(pos[1], pos[2], 2)
|
||||
elseif row[i] == "5" then -- box on goal
|
||||
sprites:blit(pos[1], pos[2], 3)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local pos = {
|
||||
game.draw_offset[1] + game.player_pos[1] * sprite_size,
|
||||
game.draw_offset[2] + game.player_pos[2] * sprite_size
|
||||
}
|
||||
sprites:blit(pos[1], pos[2], game.player_sprites[game.player_direction][game.player_flip+1])
|
||||
|
||||
if game.player_direction == "W" then
|
||||
draw.text(160, 160, "You win !!!",fg,bg,draw.align_center)
|
||||
draw.text(160, 170, "Press ENTER for next level",fg,bg,draw.align_center)
|
||||
end
|
||||
|
||||
draw.text(0, 300, "Box World! #" .. game.level .. " - Moves: " .. #game.moves .. " ")
|
||||
draw.text(0, 310, "(U)ndo, (R)estart, (ESC) Quit")
|
||||
|
||||
draw.blitBuffer()
|
||||
end
|
||||
|
||||
function game.check_win()
|
||||
for j = 1, #game.leveltable do
|
||||
local row = game.leveltable[j]
|
||||
for i = 1, #row do
|
||||
if row[i] == "4" then -- box not on goal
|
||||
return false -- quit as soon as found any
|
||||
end
|
||||
end
|
||||
end
|
||||
-- didn't quit? must mean we won
|
||||
game.player_direction = "W"
|
||||
music.play(music.tracks.victory)
|
||||
end
|
||||
|
||||
function game.process_push(code)
|
||||
if game.player_direction == "W" then return end -- don't move if we've already won
|
||||
local ahead, ahead2
|
||||
if code == keys.up then
|
||||
game.player_direction = "U"
|
||||
ahead = {game.player_pos[1], game.player_pos[2]-1}
|
||||
ahead2 = {game.player_pos[1], game.player_pos[2]-2}
|
||||
elseif code == keys.down then
|
||||
game.player_direction = "D"
|
||||
ahead = {game.player_pos[1], game.player_pos[2]+1}
|
||||
ahead2 = {game.player_pos[1], game.player_pos[2]+2}
|
||||
elseif code == keys.left then
|
||||
game.player_direction = "L"
|
||||
ahead = {game.player_pos[1]-1, game.player_pos[2]}
|
||||
ahead2 = {game.player_pos[1]-2, game.player_pos[2]}
|
||||
elseif code == keys.right then
|
||||
game.player_direction = "R"
|
||||
ahead = {game.player_pos[1]+1, game.player_pos[2]}
|
||||
ahead2 = {game.player_pos[1]+2, game.player_pos[2]}
|
||||
end
|
||||
|
||||
local whats_ahead = game.leveltable[ahead[2]]
|
||||
if whats_ahead ~= nil then whats_ahead = whats_ahead[ahead[1]] end
|
||||
local whats_ahead2 = game.leveltable[ahead2[2]]
|
||||
if whats_ahead2 ~= nil then whats_ahead2 = whats_ahead2[ahead2[1]] end
|
||||
|
||||
if whats_ahead == '2' or whats_ahead == '3' then -- ahead is floor or empty goal
|
||||
game.player_pos = ahead
|
||||
table.insert(game.moves, string.lower(game.player_direction))
|
||||
elseif whats_ahead == '4' or whats_ahead == '5' then -- ahead is box, whether or not on a goal
|
||||
if whats_ahead2 == '2' or whats_ahead2 == '3' then -- box pushed onto floor or goal
|
||||
sound.playPitch(2,1,game.push_sound)
|
||||
local box_leaves = whats_ahead == '4' and '2' or '3'
|
||||
local box_becomes = whats_ahead2 == '2' and '4' or '5'
|
||||
game.player_pos = ahead
|
||||
game.leveltable[ahead[2]][ahead[1]] = box_leaves
|
||||
game.leveltable[ahead2[2]][ahead2[1]] = box_becomes
|
||||
table.insert(game.moves, game.player_direction)
|
||||
game.check_win()
|
||||
end
|
||||
end
|
||||
screen.redraw = true
|
||||
end
|
||||
|
||||
function game.process_undo()
|
||||
if #game.moves < 1 then return false end
|
||||
|
||||
local return_to, pushed
|
||||
local move = table.remove(game.moves)
|
||||
game.player_direction = string.upper(move)
|
||||
|
||||
if string.upper(move) == 'D' then
|
||||
return_to = {game.player_pos[1], game.player_pos[2]-1}
|
||||
pushed = {game.player_pos[1], game.player_pos[2]+1}
|
||||
elseif string.upper(move) == 'U' then
|
||||
return_to = {game.player_pos[1], game.player_pos[2]+1}
|
||||
pushed = {game.player_pos[1], game.player_pos[2]-1}
|
||||
elseif string.upper(move) == 'R' then
|
||||
return_to = {game.player_pos[1]-1, game.player_pos[2]}
|
||||
pushed = {game.player_pos[1]+1, game.player_pos[2]}
|
||||
elseif string.upper(move) == 'L' then
|
||||
return_to = {game.player_pos[1]+1, game.player_pos[2]}
|
||||
pushed = {game.player_pos[1]-1, game.player_pos[2]}
|
||||
end
|
||||
|
||||
if move == string.upper(move) then -- if move was recorded upper means we moved a box
|
||||
local what_box_became = game.leveltable[pushed[2]][pushed[1]]
|
||||
local box_leaves = what_box_became == '4' and '2' or '3'
|
||||
local box_becomes = game.leveltable[game.player_pos[2]][game.player_pos[1]] == '2' and '4' or '5'
|
||||
game.leveltable[pushed[2]][pushed[1]] = box_leaves
|
||||
game.leveltable[game.player_pos[2]][game.player_pos[1]] = box_becomes
|
||||
end
|
||||
game.player_pos = return_to
|
||||
|
||||
screen.redraw = true
|
||||
end
|
||||
|
||||
function game.input()
|
||||
local state, _, code = keys.poll()
|
||||
|
||||
if state == keys.states.pressed then
|
||||
if code == keys.up or code == keys.down or code == keys.left or code == keys.right then
|
||||
game.process_push(code)
|
||||
elseif code == 'u' then
|
||||
game.process_undo()
|
||||
elseif code == 'r' then
|
||||
game.entry()
|
||||
elseif code == keys.esc then
|
||||
switch_screen(title)
|
||||
elseif game.player_direction == "W" and code == keys.enter then
|
||||
-- go to next level when won and pressed enter
|
||||
game.level = ((game.level) % #game.levelstrings) + 1
|
||||
game.entry()
|
||||
end
|
||||
end
|
||||
collectgarbage()
|
||||
end
|
||||
|
||||
function game.update(now)
|
||||
if game.player_direction ~= 'W' and now > game.anim_timer then
|
||||
game.player_flip = (game.player_flip + 1) % 2
|
||||
game.anim_timer = now + 1
|
||||
screen.redraw = true
|
||||
end
|
||||
end
|
||||
|
||||
switch_screen(title)
|
||||
while true do
|
||||
now = os.clock()
|
||||
if screen.input then screen.input() end
|
||||
if quit then break end
|
||||
if screen.update then screen.update(now) end
|
||||
if screen.redraw and screen.draw then screen.draw() screen.redraw = false end
|
||||
end
|
||||
74
Bin/PicoCalc SD/lua/browser.lua
Normal file
74
Bin/PicoCalc SD/lua/browser.lua
Normal file
@@ -0,0 +1,74 @@
|
||||
local current_dir = "/"
|
||||
local cursor_y = 1
|
||||
local width, height = term.getSize()
|
||||
|
||||
function string:endswith(suffix)
|
||||
return self:sub(-#suffix) == suffix
|
||||
end
|
||||
|
||||
term.clear()
|
||||
while true do
|
||||
local dir = fs.list(current_dir)
|
||||
term.setCursorPos(1,1)
|
||||
|
||||
table.sort(dir, function(a, b) return string.upper(a.name) < string.upper(b.name) end)
|
||||
local folders = {}
|
||||
local files = {}
|
||||
for _,v in pairs(dir) do
|
||||
if v.isDir then table.insert(folders, v)
|
||||
else table.insert(files, v) end
|
||||
end
|
||||
table.sort(files, function(a, b) return a.name < b.name end)
|
||||
dir = folders
|
||||
for _,v in ipairs(files) do
|
||||
table.insert(dir, v)
|
||||
end
|
||||
|
||||
if current_dir > "/" then
|
||||
table.insert(dir, 1, {name="..", isDir=true})
|
||||
end
|
||||
|
||||
local off = 0
|
||||
if #dir > height-2 then
|
||||
off = math.floor(cursor_y / #dir * (#dir - height + 3))
|
||||
end
|
||||
|
||||
for y = 1, height - 2 do
|
||||
local yoff = y + off
|
||||
local v = dir[yoff]
|
||||
if v then
|
||||
local str = ""
|
||||
if v.isDir then str = str .. "\27[93m"
|
||||
elseif v.name:endswith(".lua") then str = str .. "\27[96m"
|
||||
else str = str .. "\27[97m" end
|
||||
if yoff == cursor_y then str = str .. "\27[7m" end
|
||||
term.write(str .. v.name .. "\27[m\27[K\n")
|
||||
else
|
||||
term.write("\27[K\n")
|
||||
end
|
||||
end
|
||||
|
||||
term.write("\n\27[93mCurrent directory: " .. current_dir .. "\27[m\27[K")
|
||||
|
||||
local state, _, code = keys.wait(true, true)
|
||||
|
||||
if code == keys.up then
|
||||
cursor_y = ((cursor_y - 2) % #dir) + 1
|
||||
elseif code == keys.down then
|
||||
cursor_y = ((cursor_y) % #dir) + 1
|
||||
elseif code == keys.enter then
|
||||
if dir[cursor_y].isDir then
|
||||
if dir[cursor_y].name == ".." then
|
||||
current_dir = current_dir:match("(.*/).*/") or '/'
|
||||
else
|
||||
current_dir = current_dir .. dir[cursor_y].name .. '/'
|
||||
end
|
||||
cursor_y = 1
|
||||
elseif dir[cursor_y].name:endswith(".lua") then
|
||||
edit(current_dir .. dir[cursor_y].name)
|
||||
end
|
||||
elseif code == keys.esc then
|
||||
break
|
||||
end
|
||||
end
|
||||
term.clear()
|
||||
50
Bin/PicoCalc SD/lua/bubble.lua
Normal file
50
Bin/PicoCalc SD/lua/bubble.lua
Normal file
@@ -0,0 +1,50 @@
|
||||
-- Bubble Universe
|
||||
-- inspired by https://forum.clockworkpi.com/t/bubble-universe/19907
|
||||
|
||||
local w = 320
|
||||
local w2 = w/2
|
||||
local rad = 70
|
||||
local n = 20
|
||||
local nc = 120
|
||||
local step = 2
|
||||
local r = math.pi*2/n
|
||||
local x,y,t = 0,0,6
|
||||
local u,v,px,py
|
||||
|
||||
local res = 100
|
||||
|
||||
local sint = {}
|
||||
for i = 1, math.floor(math.pi*2*res) do
|
||||
sint[i] = math.sin(i/res)
|
||||
end
|
||||
|
||||
local function fsin(i)
|
||||
return sint[(math.floor(i*res)%#sint)+1]
|
||||
end
|
||||
|
||||
local function fcos(i)
|
||||
return sint[((math.floor((math.pi/2-i)*res))%#sint)+1]
|
||||
end
|
||||
|
||||
draw.enableBuffer(2)
|
||||
while true do
|
||||
if keys.getState(keys.esc) then break end
|
||||
draw.clear()
|
||||
for i = 0, n-1 do
|
||||
for c = 0, nc-1, step do
|
||||
u = fsin(i+y)+fsin(r*i+x)
|
||||
v = fcos(i+y)+fcos(r*i+x)
|
||||
x = u + t
|
||||
y = v
|
||||
px = u * rad + w2
|
||||
py = y * rad + w2
|
||||
draw.point(px, py,
|
||||
--draw.rectFill(px, py, 2, 2,
|
||||
colors.fromRGB(math.floor(63+i/n*192),
|
||||
math.floor(63+c/nc*192),168))
|
||||
end
|
||||
end
|
||||
t=t+0.02
|
||||
draw.blitBuffer()
|
||||
end
|
||||
draw.enableBuffer(false)
|
||||
107
Bin/PicoCalc SD/lua/mandelbrot.lua
Normal file
107
Bin/PicoCalc SD/lua/mandelbrot.lua
Normal file
@@ -0,0 +1,107 @@
|
||||
-- partially adapted from https://bisqwit.iki.fi/jutut/kuvat/programming_examples/mandelbrotbtrace.pdf
|
||||
-- arrow keys, 9 and 0 to control view, escape to quit
|
||||
|
||||
local maxIter = 64
|
||||
local chunk = 8
|
||||
local center = {-1,0}
|
||||
local radius = 1
|
||||
local minX, maxX, minY, maxY
|
||||
local wid, hei = 320, 320
|
||||
|
||||
local function iterate(zr, zi, max)
|
||||
local cnt, r, i, r2, i2, ri
|
||||
cnt = 0
|
||||
r,i = zr,zi
|
||||
while cnt < max do
|
||||
r2 = r*r; i2 = i*i
|
||||
if r2+i2 >= 4 then break end
|
||||
ri = r*i
|
||||
i = ri+ri + zi
|
||||
r = r2-i2 + zr
|
||||
cnt = cnt + 1
|
||||
end
|
||||
return cnt
|
||||
end
|
||||
|
||||
local function is_control_key()
|
||||
local state, _, code = keys.peek()
|
||||
if state == keys.states.pressed then
|
||||
if code == keys.up
|
||||
or code == keys.down
|
||||
or code == keys.left
|
||||
or code == keys.right
|
||||
or code == '9'
|
||||
or code == '0'
|
||||
or code == '['
|
||||
or code == ']'
|
||||
or code == keys.esc then
|
||||
return true
|
||||
end
|
||||
end
|
||||
keys.flush()
|
||||
return false
|
||||
end
|
||||
|
||||
local function drawScanlineMandelbrot(max)
|
||||
local zr, zi, cnt, clr
|
||||
local st = chunk
|
||||
local proc = {}
|
||||
local stepR = (maxX - minX) / wid
|
||||
local stepI = (maxY - minY) / hei
|
||||
while st >= 1 do
|
||||
for y = 0, hei - 1, st do
|
||||
zi = minY + y * stepI
|
||||
if proc[(y % (st*2))] then goto next end
|
||||
for x = 0, wid - 1 do
|
||||
zr = minX + x * stepR
|
||||
cnt = iterate(zr, zi, max)
|
||||
if cnt == max then clr = 0
|
||||
else
|
||||
cnt = math.floor(cnt/max*255)
|
||||
clr = colors.fromHSV((-cnt-32)%256,255,127+math.floor(cnt/2))
|
||||
end
|
||||
draw.line(x, y, x, y+st-1, clr)
|
||||
if is_control_key() then return end
|
||||
end
|
||||
::next::
|
||||
end
|
||||
proc[st%chunk] = true
|
||||
st = math.floor(st/2)
|
||||
end
|
||||
end
|
||||
|
||||
while true do
|
||||
minX, maxX, minY, maxY = center[1]-radius, center[1]+radius, center[2]-radius, center[2]+radius
|
||||
drawScanlineMandelbrot(maxIter)
|
||||
while true do
|
||||
local _, _, code = keys.wait(false, true)
|
||||
if code == keys.up then
|
||||
center[2] = center[2] - radius * 0.25
|
||||
break
|
||||
elseif code == keys.down then
|
||||
center[2] = center[2] + radius * 0.25
|
||||
break
|
||||
elseif code == keys.left then
|
||||
center[1] = center[1] - radius * 0.25
|
||||
break
|
||||
elseif code == keys.right then
|
||||
center[1] = center[1] + radius * 0.25
|
||||
break
|
||||
elseif code == '9' then
|
||||
radius = radius * 4
|
||||
break
|
||||
elseif code == '0' then
|
||||
radius = radius * 0.25
|
||||
break
|
||||
elseif code == '[' then
|
||||
maxIter = maxIter - 10
|
||||
break
|
||||
elseif code == ']' then
|
||||
maxIter = maxIter + 10
|
||||
break
|
||||
elseif code == keys.esc then
|
||||
goto exit
|
||||
end
|
||||
end
|
||||
end
|
||||
::exit::
|
||||
84
Bin/PicoCalc SD/lua/piano.lua
Normal file
84
Bin/PicoCalc SD/lua/piano.lua
Normal file
@@ -0,0 +1,84 @@
|
||||
-- kalimba / piano demo
|
||||
-- written by maple "mavica" syrup <maple@maple.pet>
|
||||
|
||||
local pkeys = {}
|
||||
|
||||
local keymap = {
|
||||
"5","6","4","7","3","8","2","9",
|
||||
"t","y","r","u","e","i","w","o",
|
||||
"g","h","f","j","d","k","s","l",
|
||||
"b","n","v","m","c",",","x","."
|
||||
}
|
||||
|
||||
local qwertymap = {
|
||||
"2","3","4","5","6","7","8","9",
|
||||
"w","e","r","t","y","u","i","o",
|
||||
"s","d","f","g","h","j","k","l",
|
||||
"x","c","v","b","n","m",",","."
|
||||
}
|
||||
|
||||
for k,v in pairs(keymap) do
|
||||
pkeys[v] = k - 1
|
||||
end
|
||||
|
||||
local last = nil
|
||||
local ch = 0
|
||||
local octave = 3
|
||||
|
||||
local inst_num = 0
|
||||
local insts = {}
|
||||
local inst_names = {}
|
||||
|
||||
for k,v in pairs(sound.presets) do
|
||||
table.insert(insts, v)
|
||||
table.insert(inst_names, k)
|
||||
end
|
||||
|
||||
local function noteString(note)
|
||||
local notes = {"C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-"}
|
||||
return notes[(note % 12)+1] .. math.floor(note / 12)
|
||||
end
|
||||
|
||||
local function redraw()
|
||||
term.setCursorPos(1,1)
|
||||
term.write("Piano / Kalimba demo\n\n")
|
||||
for j = 0, 24, 8 do
|
||||
for i = 1, 8 do
|
||||
if qwertymap[i+j] == last then term.write("\27[7m") end
|
||||
term.write(qwertymap[i+j].."\27[m ")
|
||||
end
|
||||
term.write("\n")
|
||||
end
|
||||
term.write("\n")
|
||||
if pkeys[last] then term.write(noteString(pkeys[last] + octave * 12)) end
|
||||
term.write("\n\n")
|
||||
term.write("Octave: " .. octave .. " | Instrument: " .. inst_names[inst_num+1] .. "\n\x1b[K")
|
||||
term.write("/ \\ - Change octave\n[ ] - Change instrument\nEsc - Quit\n")
|
||||
end
|
||||
|
||||
term.clear()
|
||||
while true do
|
||||
redraw()
|
||||
|
||||
local state, _, code = keys.wait(false, false)
|
||||
|
||||
if state == keys.states.pressed and code ~= last then
|
||||
if pkeys[code] then
|
||||
last = code
|
||||
sound.play(ch, pkeys[code] + octave * 12, insts[inst_num+1])
|
||||
ch = (ch+1) % 4
|
||||
elseif code == "[" then
|
||||
inst_num = (inst_num - 1) % #insts
|
||||
elseif code == "]" then
|
||||
inst_num = (inst_num + 1) % #insts
|
||||
elseif code == "/" and octave > 0 then
|
||||
octave = (octave - 1)
|
||||
elseif code == "\\" and octave < 6 then
|
||||
octave = (octave + 1)
|
||||
elseif code == keys.esc then
|
||||
break
|
||||
end
|
||||
elseif state == keys.states.released then
|
||||
last = nil
|
||||
end
|
||||
end
|
||||
4
Bin/PicoCalc SD/main.lua
Normal file
4
Bin/PicoCalc SD/main.lua
Normal file
@@ -0,0 +1,4 @@
|
||||
--ccedit = loadfile("cc/edit.lua")
|
||||
--if ccedit then print("Editor loaded") end
|
||||
browser = loadfile("lua/browser.lua")
|
||||
if browser then print("File browser available at: browser()") end
|
||||
BIN
Bin/PicoCalc SD/pico1-apps/MicroPython_fa8b24c.uf2
Normal file
BIN
Bin/PicoCalc SD/pico1-apps/MicroPython_fa8b24c.uf2
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/pico1-apps/PicoMite_v6.02.01b4_beta.uf2
Normal file
BIN
Bin/PicoCalc SD/pico1-apps/PicoMite_v6.02.01b4_beta.uf2
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/pico1-apps/Picoware_v1.6.9.uf2
Normal file
BIN
Bin/PicoCalc SD/pico1-apps/Picoware_v1.6.9.uf2
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/pico1-apps/phyllosoma_kb.uf2
Normal file
BIN
Bin/PicoCalc SD/pico1-apps/phyllosoma_kb.uf2
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/pico1-apps/picolua_daf20a2.uf2
Normal file
BIN
Bin/PicoCalc SD/pico1-apps/picolua_daf20a2.uf2
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/Calculator.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/Calculator.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/FlipSocial.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/FlipSocial.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/Graph.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/Graph.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/Serial Terminal.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/Serial Terminal.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/Text Editor.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/Text Editor.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/Weather.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/Weather.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/cat-fact.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/cat-fact.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/counter.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/counter.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/flip_social/password.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/flip_social/password.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/flip_social/run.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/flip_social/run.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/flip_social/settings.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/flip_social/settings.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/flip_social/username.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/flip_social/username.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/2048.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/2048.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/Breakout.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/Breakout.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/Flappy Bird.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/Flappy Bird.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/FlipWorld.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/FlipWorld.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/Free Roam.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/Free Roam.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/Maze Runner.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/Maze Runner.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/Minesweeper.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/Minesweeper.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/Pong.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/Pong.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/Snake.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/Snake.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/Soduko.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/Soduko.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/Space Invaders.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/Space Invaders.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/Tetris.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/Tetris.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/Tower Defense.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/Tower Defense.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/example.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/example.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/flip_world/assets.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/flip_world/assets.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/flip_world/general.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/flip_world/general.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/flip_world/player.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/flip_world/player.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/flip_world/run.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/flip_world/run.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/flip_world/sprite.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/flip_world/sprite.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/free_roam/dynamic_map.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/free_roam/dynamic_map.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/free_roam/game.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/free_roam/game.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/free_roam/maps.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/free_roam/maps.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/free_roam/player.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/free_roam/player.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/free_roam/sprite.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/free_roam/sprite.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/games/game_of_life.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/games/game_of_life.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/hello_color.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/hello_color.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/keyboard-simple.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/keyboard-simple.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/loading-simple.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/loading-simple.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/menu-simple.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/menu-simple.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/random-object.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/random-object.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/screensavers/Bubble Universe.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/screensavers/Bubble Universe.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/screensavers/Clock.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/screensavers/Clock.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/screensavers/Cube.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/screensavers/Cube.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/screensavers/DVI Bounce.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/screensavers/DVI Bounce.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/screensavers/Fire Effect.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/screensavers/Fire Effect.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/screensavers/Matrix Rain.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/screensavers/Matrix Rain.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/screensavers/Patterns.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/screensavers/Patterns.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/screensavers/PicoFlower.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/screensavers/PicoFlower.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/screensavers/Plasma Wave.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/screensavers/Plasma Wave.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/screensavers/Yin-Yang.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/screensavers/Yin-Yang.mpy
Normal file
Binary file not shown.
BIN
Bin/PicoCalc SD/picoware/apps/storage-simple.mpy
Normal file
BIN
Bin/PicoCalc SD/picoware/apps/storage-simple.mpy
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user