mirror of
https://github.com/romychs/Ocean-240.2-Emulator.git
synced 2026-04-21 19:13:20 +03:00
Compare commits
No commits in common. "master" and "v1.0.0" have entirely different histories.
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,7 +1,4 @@
|
|||||||
*.bak
|
*.bak
|
||||||
*.lst
|
*.lst
|
||||||
*.tmp
|
*.tmp
|
||||||
*.sh
|
|
||||||
*.log
|
|
||||||
.idea/
|
.idea/
|
||||||
src/.idea/
|
|
||||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/z80em.iml" filepath="$PROJECT_DIR$/.idea/z80em.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
28
LICENSE
28
LICENSE
@ -1,28 +0,0 @@
|
|||||||
BSD 3-Clause License
|
|
||||||
|
|
||||||
Copyright (c) 2026, Roman Boykov
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
3. Neither the name of the copyright holder nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
88
README.md
88
README.md
@ -1,88 +0,0 @@
|
|||||||
# Эмулятор персонального компьютера Океан-240.2
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Зачем?
|
|
||||||
|
|
||||||
Эмулятор был разработан для удобства реверс-инжененринга программ монитора и приложений для этого старого компьютера.
|
|
||||||
Поэтому и имеет такой минималистичный интерфейс.
|
|
||||||
|
|
||||||
Если нужен более точный эмулятор Океана-240.2 и других компьютеров на безе процессора КР580ВМ80, рекомендую проект [Emu80](https://emu80.org/).
|
|
||||||
|
|
||||||
## Особенности реализации
|
|
||||||
|
|
||||||
- Микросхемы КР580ВВ55, ВИ53, ВИ51, ВТ59, КР1818ВГ93 эмулируются в степени, достаточной для работы операционной системы и монитора.
|
|
||||||
- В отличие от оригинала, который использует CPU КР580ВМ80, в эмуляторе использована эмуляция Z80.
|
|
||||||
Эмулятор Z80 у меня уже был написан, поэтому использовал его, возможно, позже, обрежу его до i8080.
|
|
||||||
Из за меньшего количества тактов у некоторых инструкций Z80, эмуляция работает несколько быстрее оригинала.
|
|
||||||
- Эмулируются 2 дисковода с дисками 720К, такой вариант используется с обазами ROM CP/M и Монитора R8.
|
|
||||||
- Работает под современными ОС Windows и Linux. Используется фреймворк [Fyne](https://fyne.io/), что позволяет скомпилировать код и под другие платформы, но я не пробовал.
|
|
||||||
- Поддерживает ZRCP - протокол отладки эмулятора [ZEsarUX](https://github.com/chernandezba/zesarux). Это позволяет использовать среду разработки VSCode с плагином DeZog
|
|
||||||
для отладки исходного кода прямо в эмуляторе.
|
|
||||||
|
|
||||||
## Возможности отладки
|
|
||||||
|
|
||||||
Работают все функции плагина Dezog и протокола: [документация плагина](https://github.com/maziac/DeZog/blob/main/documentation/Usage.md#remote-types).
|
|
||||||
|
|
||||||
- Выполнение кода по шагам, в том числе и назад (с ограничениями плагина DeZog)
|
|
||||||
- Просмотр и изменение памяти
|
|
||||||
- Просмотр и изменение значения регистров процессора
|
|
||||||
- Условные и безусловные брейкпоинты
|
|
||||||
- ASSERTION - остановка по несоблюдению указанного условия
|
|
||||||
- WPMEM - остановка при обращении к указанным ячейкам памяти (можно указать тип обращения r|w|rw)
|
|
||||||
- CodeCoverage - в коде, цветом помечаются строки, выполненные процессором.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Можно отлаживать код и без VSCode, подключившись телнетом к порту отладчика, по умолчанию это localhost:10001, далее, можно передавать отладчику команды.
|
|
||||||
Их список доступен по команде help.
|
|
||||||
|
|
||||||
telnet localhost 10001
|
|
||||||
|
|
||||||
### Список доступных команд отладчика
|
|
||||||
|
|
||||||
about Shows about message
|
|
||||||
clear-membreakpoints Clear all memory breakpoints
|
|
||||||
close-all-menus Close all visible dialogs
|
|
||||||
cpu-code-coverage Sets cpu code coverage parameters
|
|
||||||
cpu-history Runs cpu history actions
|
|
||||||
cpu-step Run single opcode cpu step
|
|
||||||
disable-breakpoint Disable specific breakpoint
|
|
||||||
disable-breakpoints Disable all breakpoints
|
|
||||||
disassemble Disassemble at address
|
|
||||||
enable-breakpoint Enable specific breakpoint
|
|
||||||
enable-breakpoints Enable breakpoints
|
|
||||||
enter-cpu-step Enter cpu step to step mode
|
|
||||||
evaluate Evaluate expression
|
|
||||||
exit-cpu-step Exit cpu step to step mode
|
|
||||||
extended-stack Sets extended stack parameters, which allows you to see what kind of values are in the stack
|
|
||||||
get-cpu-frequency Get cpu frequency in HZ
|
|
||||||
get-current-machine Returns current machine name
|
|
||||||
get-machines Returns list of emulated machines
|
|
||||||
get-membreakpoints Get memory breakpoints list
|
|
||||||
get-memory-pages Returns current state of memory pages
|
|
||||||
get-os Shows emulator operating system
|
|
||||||
get-registers Get CPU registers
|
|
||||||
get-tstates-partial Get the t-states partial counter
|
|
||||||
get-tstates Get the t-states counter
|
|
||||||
get-version Shows emulator version
|
|
||||||
hard-reset-cpu Hard resets the machine
|
|
||||||
help Shows help screen or command help
|
|
||||||
hexdump Dumps memory at address, showing hex and ascii
|
|
||||||
load-binary Load binary file "file" at address "addr" with length "len", on the current memory zone
|
|
||||||
quit Closes connection
|
|
||||||
read-memory Dumps memory at address
|
|
||||||
reset-tstates-partial Resets the t-states partial counter
|
|
||||||
run Run cpu when on cpu step mode
|
|
||||||
save-binary Save binary file "file" from address "addr" with length "len", from the current memory zone
|
|
||||||
set-breakpoint Sets a breakpoint at desired index entry with condition
|
|
||||||
set-breakpointaction Sets a breakpoint action at desired index entry
|
|
||||||
set-breakpointpasscount Set pass count for breakpoint
|
|
||||||
set-debug-settings Set debug settings on remote command protocol
|
|
||||||
set-machine Set machine
|
|
||||||
set-membreakpoint Sets a memory breakpoint starting at desired address entry for type
|
|
||||||
set-register Changes register value
|
|
||||||
snapshot-load Loads a snapshot
|
|
||||||
snapshot-save Saves a snapshot
|
|
||||||
write-memory Writes a sequence of bytes starting at desired address on memory
|
|
||||||
write-port Writes value at port
|
|
||||||
76
config/config.go
Normal file
76
config/config.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultMonitorFile = "okean240/MON_r6.BIN"
|
||||||
|
const defaultCPMFile = "okean240/CPM_r7.BIN"
|
||||||
|
|
||||||
|
type OkEmuConfig struct {
|
||||||
|
LogFile string `yaml:"logFile"`
|
||||||
|
LogLevel string `yaml:"logLevel"`
|
||||||
|
MonitorFile string `yaml:"monitorFile"`
|
||||||
|
CPMFile string `yaml:"cpmFile"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var config *OkEmuConfig
|
||||||
|
|
||||||
|
//func usage() {
|
||||||
|
// fmt.Printf("Use: %s --config <file_path>.yml\n", filepath.Base(os.Args[0]))
|
||||||
|
// os.Exit(2)
|
||||||
|
//}
|
||||||
|
|
||||||
|
func GetConfig() *OkEmuConfig {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadConfig() {
|
||||||
|
//args := os.Args
|
||||||
|
//if len(args) != 3 {
|
||||||
|
// usage()
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//if args[1] != "--config" {
|
||||||
|
// usage()
|
||||||
|
//}
|
||||||
|
// confFile := args[2]
|
||||||
|
confFile := "okemu.yml"
|
||||||
|
|
||||||
|
conf := OkEmuConfig{}
|
||||||
|
data, err := os.ReadFile(confFile)
|
||||||
|
if err == nil {
|
||||||
|
err := yaml.Unmarshal(data, &conf)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicf("Config file error: %v", err)
|
||||||
|
}
|
||||||
|
setDefaultConf(&conf)
|
||||||
|
checkConfig(&conf)
|
||||||
|
} else {
|
||||||
|
log.Panicf("Can not open config file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config = &conf
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkConfig(conf *OkEmuConfig) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func setDefaultConf(conf *OkEmuConfig) {
|
||||||
|
if conf.LogLevel == "" {
|
||||||
|
conf.LogLevel = "info"
|
||||||
|
}
|
||||||
|
if conf.LogFile == "" {
|
||||||
|
conf.LogFile = "okemu.log"
|
||||||
|
}
|
||||||
|
if conf.MonitorFile == "" {
|
||||||
|
conf.MonitorFile = defaultMonitorFile
|
||||||
|
}
|
||||||
|
if conf.CPMFile == "" {
|
||||||
|
conf.CPMFile = defaultCPMFile
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,109 +0,0 @@
|
|||||||
# Эмулятор персонального компьютера Океан-240.2
|
|
||||||
|
|
||||||
## Пример, кода для работы в среде VSCode.
|
|
||||||
|
|
||||||
1. Установите VSCode
|
|
||||||
2. Установите плагин DeZog для возможности отладки исходного кода в эмуляторе okemu
|
|
||||||
3. Откройте в VSCode папку examples/hello
|
|
||||||
4. Запустите эмулятор okemu
|
|
||||||
5. Запустите отладку через меню Run -> Start debuging в VSCode.
|
|
||||||
6. Используйте инструменты отладки VSCode для выполнения кода, просмотра регистров процессора и состояния памяти и т.д.
|
|
||||||
|
|
||||||
## Рекомендуемые плагины VSCode
|
|
||||||
- [mborik.z80-macroasm-vscode](https://github.com/mborik/z80-macroasm-vscode) - Раскраска синтаксиса ассемблера Z80, поддержка разных ассемблеров, переименование меток и т.п.
|
|
||||||
- [maziac.dezog](https://github.com/maziac/DeZog/) - Отладчик кода Z80 с помощью эмуляторов
|
|
||||||
- [maziac.hex-hover-converter](https://github.com/maziac/hex-hover-converter) - Перевод чисел в другие системы счисления при наведении курсора
|
|
||||||
- [maziac.z80-instruction-set](https://github.com/maziac/z80-instruction-set) - Подсказки по инструкциям Z80 при наведении курсора (коды, такты, влияние на флаги)
|
|
||||||
|
|
||||||
## Пример файла .vscode/tasks.json
|
|
||||||
Конфигурация задачи для компиляции исходного кода ассемблером [sjasmplus](https://github.com/z00m128/sjasmplus)
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"version": "2.0.0",
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"label": "make (sjasmplus)", // так задача будет называться в меню Terminal -> Run task... VSCode
|
|
||||||
"type": "shell",
|
|
||||||
"command": "sjasmplus",
|
|
||||||
"args": [
|
|
||||||
"--i8080", // используем только инструкции КР580ВМ80
|
|
||||||
"--sld=main.sld", // генерируем файл с таблицей символов для отладчика
|
|
||||||
"--raw=main.obj", // сохраняем результат компиляции в двоичный файл
|
|
||||||
"--fullpath",
|
|
||||||
"main.asm" // основной файл исходного кода программы
|
|
||||||
],
|
|
||||||
"problemMatcher": "$problem-matcher-sjasmplus",
|
|
||||||
"group": {
|
|
||||||
"kind": "build",
|
|
||||||
"isDefault": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Пример файла .vscode/launch.json
|
|
||||||
Конфигурация задачи для отладки исходного кода нашей программы с помощью эмулятора okemu. Эмулятор поддерживает подмножество протокола ZRCP (протокол отладки эмулятора [ZEsarUX](https://github.com/chernandezba/zesarux)).
|
|
||||||
Для отладки нужен плагин DeZog. Подробное описание настроек в [документации плагина](https://github.com/maziac/DeZog/blob/main/documentation/Usage.md#remote-types).
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"type": "dezog",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Simulator",
|
|
||||||
"remoteType": "zrcp", // используем протокол ZRCP для соединения с okemu
|
|
||||||
"zrcp": {
|
|
||||||
"port": 10001, // порт, на котором, по умолчанию, отвечает отладчик okemu
|
|
||||||
// если эмулятор запущен на другом компьютере, нужно указать его имя или адрес в параметре "host"
|
|
||||||
},
|
|
||||||
|
|
||||||
"sjasmplus": [
|
|
||||||
{
|
|
||||||
"path": "main.sld", // файл с символами для отладки, созданный при компиляции
|
|
||||||
},
|
|
||||||
// ниже, исходники и файлы для отладки ОС и монитора Океана, если мы хотим видеть их исходный в VSCode в процессе отладки
|
|
||||||
// их можно убрать. Тогда при трассировке вызовов ОС и монитора мы будем видеть дизасм кода.
|
|
||||||
{
|
|
||||||
"path": "cpm/cpm.sld",
|
|
||||||
"srcDirs": [
|
|
||||||
"cpm/"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "mon/monitor.sld",
|
|
||||||
"srcDirs": [
|
|
||||||
"mon/"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"smartDisassemblerArgs": {
|
|
||||||
"lowerCase": false
|
|
||||||
},
|
|
||||||
"history": {
|
|
||||||
"reverseDebugInstructionCount": 20, // количество инструкций, которые можно шагнуть назад
|
|
||||||
"spotCount": 10,
|
|
||||||
"codeCoverageEnabled": true //true Если мы хотим видеть, какие участки кода нашего приложения выполнялись
|
|
||||||
},
|
|
||||||
"startAutomatically": false,
|
|
||||||
"commandsAfterLaunch": [
|
|
||||||
//"-rmv", // открыть окно с памятью, на которую указывают 16-разрядные регистры BC,DE,HL,IX,IY
|
|
||||||
"-mv 0x100 0x80" // открыть окно для просмотра памяти с адреса 100h размером 80h байт
|
|
||||||
],
|
|
||||||
"rootFolder": "${workspaceFolder}",
|
|
||||||
"topOfStack": "stack",
|
|
||||||
"loadObjs": [
|
|
||||||
{
|
|
||||||
"path": "main.obj", // Этот файл будет загружен в эмулятор
|
|
||||||
"start": "0x0100" // Файл будет загружен с адреса 100h
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"execAddress": "0x0100", // Загруженный код начнет выполняться с адреса 100h
|
|
||||||
"smallValuesMaximum": 513,
|
|
||||||
"tmpDir": ".tmp"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
9
examples/hello/.vscode/extensions.json
vendored
9
examples/hello/.vscode/extensions.json
vendored
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"recommendations": [
|
|
||||||
"maziac.asm-code-lens",
|
|
||||||
"maziac.dezog",
|
|
||||||
"maziac.hex-hover-converter",
|
|
||||||
"maziac.z80-instruction-set",
|
|
||||||
"maziac.sna-fileviewer"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
60
examples/hello/.vscode/launch.json
vendored
60
examples/hello/.vscode/launch.json
vendored
@ -1,60 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"type": "dezog",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Simulator",
|
|
||||||
"remoteType": "zrcp",
|
|
||||||
"zrcp": {
|
|
||||||
"port": 10001,
|
|
||||||
},
|
|
||||||
|
|
||||||
"sjasmplus": [
|
|
||||||
{
|
|
||||||
"path": "main.sld",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"path": "cpm/cpm.sld",
|
|
||||||
"srcDirs": [
|
|
||||||
"cpm/"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "mon/monitor.sld",
|
|
||||||
"srcDirs": [
|
|
||||||
"mon/"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"smartDisassemblerArgs": {
|
|
||||||
"lowerCase": false
|
|
||||||
},
|
|
||||||
// "disassemblerArgs": {
|
|
||||||
// "numberOfLines": 10
|
|
||||||
// },
|
|
||||||
"history": {
|
|
||||||
"reverseDebugInstructionCount": 20,
|
|
||||||
"spotCount": 10,
|
|
||||||
"codeCoverageEnabled": true
|
|
||||||
},
|
|
||||||
"startAutomatically": false,
|
|
||||||
"commandsAfterLaunch": [
|
|
||||||
//"-rmv",
|
|
||||||
"-mv 0x100 0x80"
|
|
||||||
],
|
|
||||||
"rootFolder": "${workspaceFolder}",
|
|
||||||
"topOfStack": "stack",
|
|
||||||
"loadObjs": [
|
|
||||||
{
|
|
||||||
"path": "main.obj",
|
|
||||||
"start": "0x0100"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"execAddress": "0x0100",
|
|
||||||
"smallValuesMaximum": 513,
|
|
||||||
"tmpDir": ".tmp"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
3
examples/hello/.vscode/settings.json
vendored
3
examples/hello/.vscode/settings.json
vendored
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"mcu-debug.debug-tracker-vscode.debugLevel": 1
|
|
||||||
}
|
|
||||||
23
examples/hello/.vscode/tasks.json
vendored
23
examples/hello/.vscode/tasks.json
vendored
@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "2.0.0",
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"label": "make (sjasmplus)",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "sjasmplus",
|
|
||||||
"args": [
|
|
||||||
"--i8080",
|
|
||||||
"--sld=main.sld",
|
|
||||||
"--raw=main.obj",
|
|
||||||
"--fullpath",
|
|
||||||
"--lst",
|
|
||||||
"main.asm"
|
|
||||||
],
|
|
||||||
"problemMatcher": "$problem-matcher-sjasmplus",
|
|
||||||
"group": {
|
|
||||||
"kind": "build",
|
|
||||||
"isDefault": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
# Эмулятор персонального компьютера Океан-240.2
|
|
||||||
|
|
||||||
Простой пример программы для ОС CP/M.
|
|
||||||
|
|
||||||
В папке .vscode прилагаются файлы, необходимые для настройки среды VSCode.
|
|
||||||
|
|
||||||
Для компиляции используется [sjasmplus Z80 assembler](https://github.com/z00m128/sjasmplus).
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,832 +0,0 @@
|
|||||||
; =======================================================
|
|
||||||
; Ocean-240.2
|
|
||||||
;
|
|
||||||
; CP/M BIOS
|
|
||||||
;
|
|
||||||
; Disassembled by Romych 2025-09-09
|
|
||||||
; =======================================================
|
|
||||||
|
|
||||||
INCLUDE "equates.inc"
|
|
||||||
INCLUDE "ram.inc"
|
|
||||||
INCLUDE "mon_entries.inc"
|
|
||||||
|
|
||||||
DEFINE CHECK_INTEGRITY
|
|
||||||
|
|
||||||
IFNDEF BUILD_ROM
|
|
||||||
OUTPUT bios.bin
|
|
||||||
ENDIF
|
|
||||||
|
|
||||||
MODULE BIOS
|
|
||||||
|
|
||||||
ORG 0xD600
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; BIOS JUMP TABLE
|
|
||||||
; -------------------------------------------------------
|
|
||||||
boot_f:
|
|
||||||
JP bios_boot
|
|
||||||
wboot_f:
|
|
||||||
JP bios_wboot
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; console status to reg-a
|
|
||||||
; -------------------------------------------------------
|
|
||||||
const_f:
|
|
||||||
JP MON.non_con_status
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; console character to reg-a
|
|
||||||
; -------------------------------------------------------
|
|
||||||
conin_f:
|
|
||||||
JP MON.mon_con_in
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; console character from c to console out
|
|
||||||
; -------------------------------------------------------
|
|
||||||
conout_f:
|
|
||||||
JP MON.mon_con_out
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; list device out
|
|
||||||
; -------------------------------------------------------
|
|
||||||
list_f:
|
|
||||||
JP MON.mon_char_print
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; punch device out
|
|
||||||
; -------------------------------------------------------
|
|
||||||
punch_f:
|
|
||||||
JP MON.mon_serial_out
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; reader character in to reg-a
|
|
||||||
; -------------------------------------------------------
|
|
||||||
reader_f:
|
|
||||||
JP MON.mon_serial_in
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; move to home position, treat as track 00 seek
|
|
||||||
; -------------------------------------------------------
|
|
||||||
home_f:
|
|
||||||
JP home
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; select disk given by register c
|
|
||||||
; -------------------------------------------------------
|
|
||||||
seldsk_f:
|
|
||||||
JP sel_disk
|
|
||||||
settrk_f:
|
|
||||||
JP set_trk
|
|
||||||
setsec_f:
|
|
||||||
JP set_sec
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; Set DMA address from BC
|
|
||||||
; -------------------------------------------------------
|
|
||||||
setdma_f:
|
|
||||||
JP set_dma
|
|
||||||
read_f:
|
|
||||||
JP read
|
|
||||||
write_f:
|
|
||||||
JP write
|
|
||||||
listst_f:
|
|
||||||
JP list_st
|
|
||||||
sectran_f:
|
|
||||||
JP sec_tran
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; Reserved
|
|
||||||
; -------------------------------------------------------
|
|
||||||
JP warm_boot
|
|
||||||
JP warm_boot
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; Tape read
|
|
||||||
; -------------------------------------------------------
|
|
||||||
tape_read_f:
|
|
||||||
JP MON.mon_tape_read
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; Tape write
|
|
||||||
; -------------------------------------------------------
|
|
||||||
tape_write_f:
|
|
||||||
JP MON.mon_tape_write
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; Tape wait block
|
|
||||||
; -------------------------------------------------------
|
|
||||||
tape_wait_f:
|
|
||||||
JP MON.mon_tape_wait
|
|
||||||
|
|
||||||
JP warm_boot ; r8
|
|
||||||
|
|
||||||
disk_a_size DW 192 ; 192k disk A size
|
|
||||||
disk_b_size DW 720 ; 720 disk B size
|
|
||||||
disk_c_size DW 720 ; 720 disk C size
|
|
||||||
disk_b_tracks DB 80
|
|
||||||
disk_c_tracks DB 80
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; cold start
|
|
||||||
; -------------------------------------------------------
|
|
||||||
bios_boot:
|
|
||||||
LD HL, (bdos_ent_addr) ; 0xba06
|
|
||||||
LD DE, 0x10000-CCP_RAM.bdos_enter_jump ; 0x45fa
|
|
||||||
ADD HL, DE ; 0xba06+0x45fa=10000
|
|
||||||
LD A, H
|
|
||||||
OR L
|
|
||||||
JP Z, bios_signon
|
|
||||||
|
|
||||||
; >> r8
|
|
||||||
LD HL, (bios_var0) ; if bios_var0 = 0xaaaa (initialized)
|
|
||||||
LD DE, 0x5556
|
|
||||||
ADD HL, DE ; 0xaaaa+0x5556=0x10000 if initialized
|
|
||||||
LD A, H
|
|
||||||
OR L
|
|
||||||
JP Z, bios_signon ; if initialized, go to logon, skip init
|
|
||||||
; <<
|
|
||||||
|
|
||||||
; Init DMA buffer
|
|
||||||
LD HL, dma_buffer ; 0x8000
|
|
||||||
LD B, DMA_BUFF_SIZE ; 0x80
|
|
||||||
.init_dma_buff:
|
|
||||||
LD (HL), FILE_DELETED ; 0xE5
|
|
||||||
INC HL
|
|
||||||
DEC B
|
|
||||||
JP NZ, .init_dma_buff
|
|
||||||
|
|
||||||
; Init RAM disk
|
|
||||||
LD HL, dma_buffer
|
|
||||||
LD DE, 0x0000
|
|
||||||
LD B, 8
|
|
||||||
|
|
||||||
.init_ram_dsk:
|
|
||||||
PUSH BC
|
|
||||||
CALL MON.ram_disk_write
|
|
||||||
POP BC
|
|
||||||
INC DE
|
|
||||||
DEC B
|
|
||||||
JP NZ, .init_ram_dsk
|
|
||||||
|
|
||||||
; Init user to 0 and drive to 0
|
|
||||||
XOR A
|
|
||||||
LD (cur_user_drv), A
|
|
||||||
|
|
||||||
; init bios variables
|
|
||||||
CALL bios_init_ilv ; r8
|
|
||||||
|
|
||||||
bios_signon:
|
|
||||||
LD SP, bios_stack
|
|
||||||
; Print CP/M hello message
|
|
||||||
LD HL, msg_hello
|
|
||||||
CALL print_strz
|
|
||||||
JP bios_wboot
|
|
||||||
|
|
||||||
;bios_wboot:
|
|
||||||
; LD SP, bios_stack
|
|
||||||
|
|
||||||
; r8 >>
|
|
||||||
bios_init_ilv:
|
|
||||||
LD HL, bios_var0
|
|
||||||
LD DE, bios_ini_vals
|
|
||||||
LD C, 13
|
|
||||||
CALL mov_dlhe_c
|
|
||||||
LD A, (disk_c_tracks) ; 0x50
|
|
||||||
LD (disk_c_tracks), A
|
|
||||||
RET
|
|
||||||
; <<
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; BIOS Warm start entry
|
|
||||||
; -------------------------------------------------------
|
|
||||||
bios_wboot: ; r8
|
|
||||||
|
|
||||||
LD HL, (bios_var0)
|
|
||||||
LD DE, 0x5556 ; 0xaaaa + 0x5556 = 0x10000
|
|
||||||
ADD HL, DE
|
|
||||||
LD A, H
|
|
||||||
OR L
|
|
||||||
CALL NZ, bios_init_ilv ; init if not initialized before
|
|
||||||
LD SP, bios_stack
|
|
||||||
|
|
||||||
; Move CPP from 0xC000 to 0xB200
|
|
||||||
LD HL, CCP_DST_ADDR
|
|
||||||
LD DE, CCP_SRC_ADDR
|
|
||||||
LD BC, CCP_SIZE
|
|
||||||
.move_ccp:
|
|
||||||
LD A, (DE)
|
|
||||||
LD (HL), A
|
|
||||||
INC DE
|
|
||||||
INC HL
|
|
||||||
DEC BC
|
|
||||||
LD A, B
|
|
||||||
OR C
|
|
||||||
JP NZ, .move_ccp
|
|
||||||
|
|
||||||
; Init variables with zeroes
|
|
||||||
LD HL, CPM_VARS.cpm_vars_start
|
|
||||||
LD BC, CPM_VARS.ccp_vars_size ; 213
|
|
||||||
|
|
||||||
.clr_cpm_vars:
|
|
||||||
LD (HL), 0x0
|
|
||||||
INC HL
|
|
||||||
DEC BC
|
|
||||||
LD A, B
|
|
||||||
OR C
|
|
||||||
JP NZ, .clr_cpm_vars
|
|
||||||
|
|
||||||
LD A, FILE_DELETED
|
|
||||||
LD (CPM_VARS.bdos_efcb), A ; mark empty FCB
|
|
||||||
LD A, low dma_buffer ; 0x80
|
|
||||||
LD (CPM_VARS.bdos_dmaad), A ; 0x0080
|
|
||||||
|
|
||||||
; Move DPH + DPB to RAM
|
|
||||||
LD HL, CPM_VARS.DPH_RAM
|
|
||||||
LD DE, dph_disk_a
|
|
||||||
LD BC, DPB_END-dph_disk_a ; 0x39 -> 57d
|
|
||||||
|
|
||||||
.move_dph:
|
|
||||||
LD A, (DE)
|
|
||||||
LD (HL), A
|
|
||||||
INC HL
|
|
||||||
INC DE
|
|
||||||
DEC BC
|
|
||||||
LD A, B
|
|
||||||
OR C
|
|
||||||
JP NZ, .move_dph
|
|
||||||
|
|
||||||
LD BC, dma_buffer ; DMA default buffer addr
|
|
||||||
CALL setdma_f
|
|
||||||
|
|
||||||
; Setup JP to Warm boot after CPU reset
|
|
||||||
LD A, JP_OPCODE
|
|
||||||
LD (warm_boot), A
|
|
||||||
LD HL, wboot_f
|
|
||||||
LD (warm_boot_addr), HL
|
|
||||||
|
|
||||||
; Setup JP to BDOS entrance
|
|
||||||
LD (jp_bdos_enter), A
|
|
||||||
LD HL, CCP_RAM.bdos_enter_jump
|
|
||||||
LD (bdos_ent_addr), HL
|
|
||||||
|
|
||||||
; Disk A
|
|
||||||
LD HL, CPM_VARS.DPB_A_RAM
|
|
||||||
LD C, 0xf
|
|
||||||
LD DE, dpb_ram
|
|
||||||
LD A, (disk_a_size+1)
|
|
||||||
OR A
|
|
||||||
JP Z, .drv_a_192
|
|
||||||
LD DE, dpb_empty
|
|
||||||
.drv_a_192:
|
|
||||||
CALL mov_dlhe_c
|
|
||||||
|
|
||||||
; Disk B
|
|
||||||
LD HL, CPM_VARS.DPB_B_RAM
|
|
||||||
LD C, 0xf
|
|
||||||
LD DE, dpb_flop_360k
|
|
||||||
LD A, (disk_b_size+1)
|
|
||||||
CP 0x1
|
|
||||||
JP Z, .drv_b_360
|
|
||||||
LD DE, dpb_flop_720k
|
|
||||||
.drv_b_360:
|
|
||||||
CALL mov_dlhe_c
|
|
||||||
|
|
||||||
; Disk C
|
|
||||||
LD HL, CPM_VARS.DPB_C_RAM
|
|
||||||
LD C, 0xf
|
|
||||||
LD A, (disk_c_size+1)
|
|
||||||
CP 0x2 ; 720?
|
|
||||||
JP Z, .drv_c_720
|
|
||||||
LD DE, dpb_flop_360k
|
|
||||||
; bios_var3 != 0 -> move DPB 360k
|
|
||||||
LD A, (disk_sw_trk)
|
|
||||||
OR A
|
|
||||||
JP NZ, .drv_c_mov
|
|
||||||
; bios_var3 == 0 -> move DPB 720k
|
|
||||||
LD DE, dpb_flop_720k
|
|
||||||
JP .drv_c_mov
|
|
||||||
.drv_c_720:
|
|
||||||
LD DE, dpb_flop_720k
|
|
||||||
; bios_var3 != 0 -> move DPB 720k
|
|
||||||
LD A, (disk_sw_trk)
|
|
||||||
OR A
|
|
||||||
JP NZ, .drv_c_mov
|
|
||||||
; bios_var3 == 1 -> move DPB 360k
|
|
||||||
LD DE, dpb_flop_360k
|
|
||||||
.drv_c_mov:
|
|
||||||
CALL mov_dlhe_c
|
|
||||||
XOR A
|
|
||||||
LD (CPM_VARS.slicer_has_data),A
|
|
||||||
LD (CPM_VARS.slicer_uninited_count),A
|
|
||||||
LD A,(cur_user_drv)
|
|
||||||
LD C,A
|
|
||||||
; go to CCP
|
|
||||||
JP CCP_DST_ADDR
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; Move C bytes from [DE] to [HL]
|
|
||||||
; -------------------------------------------------------
|
|
||||||
mov_dlhe_c:
|
|
||||||
LD A,(DE) ; [DE]->[HL]
|
|
||||||
LD (HL),A
|
|
||||||
INC HL
|
|
||||||
INC DE
|
|
||||||
DEC C
|
|
||||||
JP NZ, mov_dlhe_c
|
|
||||||
RET
|
|
||||||
|
|
||||||
list_st:
|
|
||||||
XOR A
|
|
||||||
RET
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; Select disk
|
|
||||||
; Inp: C - disk,
|
|
||||||
; E - active drive flag
|
|
||||||
; Out: HL -> DPH
|
|
||||||
; -------------------------------------------------------
|
|
||||||
sel_disk:
|
|
||||||
LD HL, 0x00
|
|
||||||
LD A, C
|
|
||||||
CP CTRL_C
|
|
||||||
RET NC
|
|
||||||
|
|
||||||
LD (CPM_VARS.cur_disk), A
|
|
||||||
OR A
|
|
||||||
JP Z, .get_dph_addr ; skip next for disk 0 - RAM disk
|
|
||||||
LD A, E ; selected disk map
|
|
||||||
AND 0x1 ; bit 0 is set if disk already selected
|
|
||||||
JP NZ, .get_dph_addr
|
|
||||||
; Reset disk
|
|
||||||
LD (CPM_VARS.slicer_has_data), A
|
|
||||||
LD (CPM_VARS.slicer_uninited_count), A
|
|
||||||
; calc DPH address of new drive
|
|
||||||
.get_dph_addr:
|
|
||||||
LD L, C
|
|
||||||
LD H, 0x0
|
|
||||||
ADD HL, HL ; *2
|
|
||||||
ADD HL, HL ; *4
|
|
||||||
ADD HL, HL ; *8
|
|
||||||
ADD HL, HL ; *16 (size of DBH)
|
|
||||||
LD DE, CPM_VARS.DPH_RAM
|
|
||||||
ADD HL, DE
|
|
||||||
RET
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; move to track 00
|
|
||||||
; -------------------------------------------------------
|
|
||||||
home:
|
|
||||||
LD A, (CPM_VARS.cur_disk)
|
|
||||||
OR A
|
|
||||||
JP Z, .is_default
|
|
||||||
LD A, (CPM_VARS.slicer_need_save)
|
|
||||||
OR A
|
|
||||||
JP NZ, .is_default
|
|
||||||
LD (CPM_VARS.slicer_has_data), A ; set to 0, no data
|
|
||||||
|
|
||||||
.is_default:
|
|
||||||
LD C, 0 ; set track to 0
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; set track address (0..76) for subsequent read/write
|
|
||||||
; -------------------------------------------------------
|
|
||||||
set_trk:
|
|
||||||
LD HL, CPM_VARS.curr_track
|
|
||||||
LD (HL), C
|
|
||||||
RET
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; set sector address (1,..., 26) for subsequent read/write
|
|
||||||
; -------------------------------------------------------
|
|
||||||
set_sec:
|
|
||||||
LD HL, CPM_VARS.curr_sec
|
|
||||||
LD (HL), C
|
|
||||||
RET
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; set subsequent dma address (initially 80h)
|
|
||||||
; -------------------------------------------------------
|
|
||||||
set_dma:
|
|
||||||
LD L, C
|
|
||||||
LD H, B
|
|
||||||
LD (CPM_VARS.dma_addr), HL
|
|
||||||
RET
|
|
||||||
|
|
||||||
sec_tran:
|
|
||||||
LD L, C
|
|
||||||
LD H, B
|
|
||||||
RET
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; read track/sector to preset dma address
|
|
||||||
; -------------------------------------------------------
|
|
||||||
read:
|
|
||||||
LD A, (CPM_VARS.cur_disk)
|
|
||||||
OR A
|
|
||||||
JP NZ, read_phys ; for physical disk use special routine
|
|
||||||
CALL ram_disk_calc_addr
|
|
||||||
CALL MON.ram_disk_read
|
|
||||||
XOR A
|
|
||||||
RET
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; write track/sector from preset dma address
|
|
||||||
; -------------------------------------------------------
|
|
||||||
write:
|
|
||||||
LD A, (CPM_VARS.cur_disk)
|
|
||||||
OR A
|
|
||||||
JP NZ,write_phys
|
|
||||||
CALL ram_disk_calc_addr
|
|
||||||
CALL MON.ram_disk_write
|
|
||||||
XOR A
|
|
||||||
RET
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; Calculate address for current sector and track
|
|
||||||
; -------------------------------------------------------
|
|
||||||
ram_disk_calc_addr:
|
|
||||||
LD HL, CPM_VARS.curr_track
|
|
||||||
; HL = cur_track * 16
|
|
||||||
LD L, (HL)
|
|
||||||
LD H, 0x0
|
|
||||||
ADD HL, HL
|
|
||||||
ADD HL, HL
|
|
||||||
ADD HL, HL
|
|
||||||
ADD HL, HL
|
|
||||||
; DE = HL + cur_sec
|
|
||||||
EX DE, HL
|
|
||||||
LD HL, CPM_VARS.curr_sec
|
|
||||||
LD L, (HL)
|
|
||||||
LD H, 0x0
|
|
||||||
ADD HL, DE
|
|
||||||
EX DE, HL
|
|
||||||
; store address
|
|
||||||
LD HL, (CPM_VARS.dma_addr)
|
|
||||||
RET
|
|
||||||
|
|
||||||
read_phys:
|
|
||||||
CALL read_phys_op
|
|
||||||
RET
|
|
||||||
|
|
||||||
write_phys:
|
|
||||||
CALL write_phys_op
|
|
||||||
RET
|
|
||||||
|
|
||||||
read_phys_op:
|
|
||||||
XOR A
|
|
||||||
; reset counter
|
|
||||||
LD (CPM_VARS.slicer_uninited_count), A
|
|
||||||
LD A, 0x1
|
|
||||||
LD (CPM_VARS.tmp_slicer_operation), A ; 0 - write; 1 - read
|
|
||||||
LD (CPM_VARS.tmp_slicer_can_read), A ; enable read fron disk
|
|
||||||
LD A, 0x2
|
|
||||||
LD (CPM_VARS.tmp_slicer_flush), A ; disable flush data to disk
|
|
||||||
JP base_read_write
|
|
||||||
|
|
||||||
write_phys_op:
|
|
||||||
XOR A
|
|
||||||
LD (CPM_VARS.tmp_slicer_operation), A
|
|
||||||
LD A, C
|
|
||||||
LD (CPM_VARS.tmp_slicer_flush), A
|
|
||||||
CP 0x2
|
|
||||||
JP NZ, .mode_ne_2
|
|
||||||
LD A, 0x10 ; 2048/128
|
|
||||||
LD (CPM_VARS.slicer_uninited_count), A
|
|
||||||
LD A, (CPM_VARS.cur_disk)
|
|
||||||
LD (CPM_VARS.slicer_uninited_disk), A
|
|
||||||
LD A, (CPM_VARS.curr_track)
|
|
||||||
LD (CPM_VARS.slicer_uninited_track), A
|
|
||||||
LD A, (CPM_VARS.curr_sec)
|
|
||||||
LD (CPM_VARS.slicer_uninited_sector_128), A
|
|
||||||
.mode_ne_2:
|
|
||||||
LD A, (CPM_VARS.slicer_uninited_count)
|
|
||||||
OR A
|
|
||||||
JP Z, slicer_read_write
|
|
||||||
DEC A
|
|
||||||
LD (CPM_VARS.slicer_uninited_count), A
|
|
||||||
LD A, (CPM_VARS.cur_disk)
|
|
||||||
LD HL, CPM_VARS.slicer_uninited_disk
|
|
||||||
CP (HL)
|
|
||||||
JP NZ, slicer_read_write
|
|
||||||
LD A, (CPM_VARS.curr_track)
|
|
||||||
LD HL, CPM_VARS.slicer_uninited_track
|
|
||||||
CP (HL)
|
|
||||||
JP NZ, slicer_read_write
|
|
||||||
LD A, (CPM_VARS.curr_sec)
|
|
||||||
LD HL, CPM_VARS.slicer_uninited_sector_128
|
|
||||||
CP (HL)
|
|
||||||
JP NZ, slicer_read_write
|
|
||||||
INC (HL)
|
|
||||||
LD A, (HL)
|
|
||||||
CP 36 ; Sectors per track
|
|
||||||
JP C, .no_inc_track
|
|
||||||
LD (HL), 0x0
|
|
||||||
LD A, (CPM_VARS.slicer_uninited_track)
|
|
||||||
INC A
|
|
||||||
LD (CPM_VARS.slicer_uninited_track), A
|
|
||||||
|
|
||||||
.no_inc_track:
|
|
||||||
XOR A
|
|
||||||
LD (CPM_VARS.tmp_slicer_can_read), A
|
|
||||||
JP base_read_write
|
|
||||||
|
|
||||||
; --------------------------------------------------
|
|
||||||
slicer_read_write:
|
|
||||||
XOR A
|
|
||||||
LD (CPM_VARS.slicer_uninited_count), A
|
|
||||||
INC A
|
|
||||||
LD (CPM_VARS.tmp_slicer_can_read), A
|
|
||||||
|
|
||||||
; --------------------------------------------------
|
|
||||||
base_read_write:
|
|
||||||
XOR A
|
|
||||||
LD (CPM_VARS.tmp_slicer_result), A
|
|
||||||
LD A, (CPM_VARS.curr_sec)
|
|
||||||
OR A
|
|
||||||
RRA
|
|
||||||
OR A
|
|
||||||
RRA
|
|
||||||
LD (CPM_VARS.tmp_slicer_real_sector), A
|
|
||||||
LD HL, CPM_VARS.slicer_has_data
|
|
||||||
LD A, (HL)
|
|
||||||
LD (HL), 0x1
|
|
||||||
OR A
|
|
||||||
JP Z, .no_data
|
|
||||||
LD A, (CPM_VARS.cur_disk)
|
|
||||||
LD HL, CPM_VARS.slicer_disk
|
|
||||||
CP (HL)
|
|
||||||
JP NZ, .pos_diff
|
|
||||||
LD A, (CPM_VARS.curr_track)
|
|
||||||
LD HL, CPM_VARS.slicer_track
|
|
||||||
CP (HL)
|
|
||||||
JP NZ, .pos_diff
|
|
||||||
LD A, (CPM_VARS.tmp_slicer_real_sector)
|
|
||||||
LD HL, CPM_VARS.slicer_real_sector
|
|
||||||
CP (HL)
|
|
||||||
JP Z,calc_sec_addr_in_bfr
|
|
||||||
.pos_diff:
|
|
||||||
LD A, (CPM_VARS.slicer_need_save)
|
|
||||||
OR A
|
|
||||||
CALL NZ, slicer_save_buffer ; save buffer if needed
|
|
||||||
.no_data:
|
|
||||||
LD A, (CPM_VARS.cur_disk)
|
|
||||||
LD (CPM_VARS.slicer_disk), A
|
|
||||||
LD A, (CPM_VARS.curr_track)
|
|
||||||
LD (CPM_VARS.slicer_track), A
|
|
||||||
LD A, (CPM_VARS.tmp_slicer_real_sector)
|
|
||||||
LD (CPM_VARS.slicer_real_sector), A
|
|
||||||
LD A, (CPM_VARS.tmp_slicer_can_read)
|
|
||||||
OR A
|
|
||||||
CALL NZ,slicer_read_buffer
|
|
||||||
XOR A
|
|
||||||
LD (CPM_VARS.slicer_need_save), A
|
|
||||||
|
|
||||||
; --------------------------------------------------
|
|
||||||
calc_sec_addr_in_bfr:
|
|
||||||
LD A, (CPM_VARS.curr_sec)
|
|
||||||
AND 0x3
|
|
||||||
LD L, A
|
|
||||||
LD H, 0
|
|
||||||
ADD HL, HL
|
|
||||||
ADD HL, HL
|
|
||||||
ADD HL, HL
|
|
||||||
ADD HL, HL
|
|
||||||
ADD HL, HL
|
|
||||||
ADD HL, HL
|
|
||||||
ADD HL, HL ; *128
|
|
||||||
LD DE, CPM_VARS.slicer_buffer
|
|
||||||
ADD HL, DE
|
|
||||||
EX DE, HL
|
|
||||||
LD HL, (CPM_VARS.dma_addr)
|
|
||||||
LD C, 0x80
|
|
||||||
LD A, (CPM_VARS.tmp_slicer_operation)
|
|
||||||
OR A
|
|
||||||
JP NZ, .no_save
|
|
||||||
LD A, 0x1
|
|
||||||
LD (CPM_VARS.slicer_need_save), A
|
|
||||||
EX DE, HL
|
|
||||||
.no_save:
|
|
||||||
LD A, (DE)
|
|
||||||
INC DE
|
|
||||||
LD (HL), A
|
|
||||||
INC HL
|
|
||||||
DEC C
|
|
||||||
JP NZ, .no_save
|
|
||||||
LD A, (CPM_VARS.tmp_slicer_flush)
|
|
||||||
CP 0x1
|
|
||||||
LD A, (CPM_VARS.tmp_slicer_result)
|
|
||||||
RET NZ
|
|
||||||
OR A
|
|
||||||
RET NZ
|
|
||||||
XOR A
|
|
||||||
LD (CPM_VARS.slicer_need_save), A
|
|
||||||
CALL slicer_save_buffer
|
|
||||||
LD A, (CPM_VARS.tmp_slicer_result)
|
|
||||||
RET
|
|
||||||
|
|
||||||
; --------------------------------------------------
|
|
||||||
slicer_save_buffer:
|
|
||||||
CALL slicer_get_floppy_args
|
|
||||||
LD C, 0xA6 ; VG93 CMD
|
|
||||||
CALL MON.write_floppy
|
|
||||||
LD (CPM_VARS.tmp_slicer_result), A
|
|
||||||
RET
|
|
||||||
|
|
||||||
; --------------------------------------------------
|
|
||||||
slicer_read_buffer:
|
|
||||||
CALL slicer_get_floppy_args
|
|
||||||
LD C, 0x86 ; VG93 CMD
|
|
||||||
CALL MON.read_floppy
|
|
||||||
LD (CPM_VARS.tmp_slicer_result), A
|
|
||||||
RET
|
|
||||||
|
|
||||||
; --------------------------------------------------
|
|
||||||
slicer_get_floppy_args:
|
|
||||||
LD HL, CPM_VARS.tmp_buff9
|
|
||||||
LD A, (CPM_VARS.slicer_disk)
|
|
||||||
DEC A
|
|
||||||
JP Z, .non_interleave
|
|
||||||
LD HL, interleave_0
|
|
||||||
.non_interleave:
|
|
||||||
LD A, (CPM_VARS.slicer_real_sector)
|
|
||||||
ADD A, L
|
|
||||||
LD L, A
|
|
||||||
LD E, (HL)
|
|
||||||
LD A, (CPM_VARS.slicer_track)
|
|
||||||
LD D, A
|
|
||||||
LD HL, CPM_VARS.slicer_buffer
|
|
||||||
LD A, (CPM_VARS.slicer_disk)
|
|
||||||
RET
|
|
||||||
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; Print zerro ended string
|
|
||||||
; Inp: HL -> string
|
|
||||||
; -------------------------------------------------------
|
|
||||||
print_strz:
|
|
||||||
LD A, (HL)
|
|
||||||
OR A
|
|
||||||
RET Z
|
|
||||||
LD C, A
|
|
||||||
PUSH HL
|
|
||||||
CALL conout_f
|
|
||||||
POP HL
|
|
||||||
INC HL
|
|
||||||
JP print_strz
|
|
||||||
|
|
||||||
msg_hello:
|
|
||||||
DB ASCII_ESC, "6", "0" ; 40x25 cursor on
|
|
||||||
DB ASCII_ESC, "8", "3" ; set palette
|
|
||||||
DB ASCII_ESC, "5", 33, 37 ; set cursor r,c
|
|
||||||
DB ASCII_ESC, "4", "1" ; set color
|
|
||||||
DB ASCII_ESC, "1", 22, 226, 226, 252, 1 ; draw fill rect x1,y1,x2,y2,m
|
|
||||||
DB ASCII_ESC, "4", "0" ; set color
|
|
||||||
DB ASCII_ESC, "1", 30, 230, 219, 248, 1 ; draw fill rect x1,y1,x2,y2,m
|
|
||||||
DB ASCII_ESC, "4", "3" ; set color
|
|
||||||
DB "OKEAH-240 CP/M (V2.2) REL.8'\r\n\n"
|
|
||||||
DB ASCII_ESC, "4", "2", 0 ; set color
|
|
||||||
|
|
||||||
|
|
||||||
; --------------------------------------------------
|
|
||||||
; Disk parameters blocks in ROM (DPBs)
|
|
||||||
; Tables of memory that describe the characteristics
|
|
||||||
; of discs on our system. There is one DPB for each
|
|
||||||
; disc type
|
|
||||||
|
|
||||||
; ----------------------------------------
|
|
||||||
; Block size | No of sectors | BSH | BLM |
|
|
||||||
; ----------------------------------------
|
|
||||||
; 1K | 8 | 3 | 7 |
|
|
||||||
; 2K | 16 | 4 | 15 |
|
|
||||||
; 4K | 32 | 5 | 31 |
|
|
||||||
; 8K | 64 | 6 | 63 |
|
|
||||||
; 16K | 128 | 7 | 127 |
|
|
||||||
; ----------------------------------------
|
|
||||||
|
|
||||||
; -------------------------------------
|
|
||||||
; Block size| Extent mask (EXM) |
|
|
||||||
; | Small disk | Large disk |
|
|
||||||
; -------------------------------------
|
|
||||||
; 2K | 1 | 0 |
|
|
||||||
; 4K | 3 | 1 |
|
|
||||||
; 8K | 7 | 3 |
|
|
||||||
; 16K | 15 | 7 |
|
|
||||||
; -------------------------------------
|
|
||||||
; CKS - number of dir sectors to check before write, 0 for HDD
|
|
||||||
|
|
||||||
; For RAM-Disk 192k
|
|
||||||
dpb_ram:
|
|
||||||
DW 16 ; SPT Sector (128b) per track (16d)
|
|
||||||
DB 3 ; BSH 1k
|
|
||||||
DB 7 ; BLM 1k; Allocation block size = (BLM + 1) * 128 = 1k
|
|
||||||
DB 0 ; EXM extent mask
|
|
||||||
DW 191 ; DSM Disk size blocks - 1
|
|
||||||
DW 31 ; DRM Dir elements - 1
|
|
||||||
DB 10000000b ; AL0 Dir map byte 1
|
|
||||||
DB 00000000b ; AL1 Dir map byte 2
|
|
||||||
DW 0x0008 ; CKS checksum vector size (8 sectors=1k)
|
|
||||||
DW 0x0000 ; OFF (tracks reserved for system)
|
|
||||||
|
|
||||||
dpb_empty:
|
|
||||||
DS 15, 0xff
|
|
||||||
|
|
||||||
; For FLOPPY 720k
|
|
||||||
dpb_flop_720k:
|
|
||||||
DW 36 ; SPT Sector (128b) per track 36 * 128 = 18KB
|
|
||||||
DB 4 ; BSH 2k
|
|
||||||
DB 15 ; BLM 2k; Allocation block size = (BLM + 1) * 128 = 2k
|
|
||||||
DB 0 ; EXM extent mask
|
|
||||||
DW 359 ; DSM Disk size blocks - 1 (359d)
|
|
||||||
DW 127 ; DRM Directory entries - 1 (127d)
|
|
||||||
DB 11000000b ; AL0 Dir map byte 1 (2 dir blk)
|
|
||||||
DB 00000000b ; AL1 Dir map byte 2
|
|
||||||
DW 32 ; CKS checksum vector size (32 sectors = 2k)
|
|
||||||
DW 0x0000 ; OFF (No of tracks reserved for system)
|
|
||||||
|
|
||||||
; For FLOPPY 360k
|
|
||||||
dpb_flop_360k:
|
|
||||||
DW 36 ; SPT Sector (128b) per track 36 * 128 = 18KB
|
|
||||||
DB 4 ; BSH 2k
|
|
||||||
DB 15 ; BLM 2k; Allocation block size = (BLM + 1) * 128 = 2k
|
|
||||||
DB 1 ; EXM extent mask
|
|
||||||
DW 179 ; DSM Disk size blocks - 1 (179d)
|
|
||||||
DW 127 ; DRM Directory entries - 1 (127d)
|
|
||||||
DB 11000000b ; AL0 Dir map byte 1 (2 dir blk)
|
|
||||||
DB 00000000b ; AL1 Dir map byte 2
|
|
||||||
DW 32 ; CKS checksum vector size (32 sectors = 2k)
|
|
||||||
DW 0x0000 ; OFF (No of tracks reserved for system)
|
|
||||||
|
|
||||||
bios_ini_vals:
|
|
||||||
DB 0xaa, 0xaa, 0, 0xff, 1, 8, 6, 4, 2, 9, 7, 5, 3
|
|
||||||
|
|
||||||
; --------------------------------------------------
|
|
||||||
; Disk parameters headers in ROM
|
|
||||||
; --------------------------------------------------
|
|
||||||
; Disk A RAM
|
|
||||||
dph_disk_a:
|
|
||||||
DW 0 ; Sector translate table pointer
|
|
||||||
DW 0, 0, 0 ; Scratchpad area
|
|
||||||
DW CPM_VARS.dir_buffer ; Directory buffer pointer
|
|
||||||
DW CPM_VARS.DPB_A_RAM ; DPB Pointer
|
|
||||||
DW CPM_VARS.CHK_VEC_A ; Check Vector pointer
|
|
||||||
DW CPM_VARS.AL_MAP_A ; Allocation map pointer
|
|
||||||
|
|
||||||
; Disk B Floppy
|
|
||||||
dph_disk_b:
|
|
||||||
DW 0 ; Sector translate table pointer
|
|
||||||
DW 0, 0, 0 ; Scratchpad area
|
|
||||||
DW CPM_VARS.dir_buffer ; Directory buffer pointer
|
|
||||||
DW CPM_VARS.DPB_B_RAM ; DPB Pointer
|
|
||||||
DW CPM_VARS.CHK_VEC_B ; Check Vector pointer
|
|
||||||
DW CPM_VARS.AL_MAP_B ; Allocation map pointer
|
|
||||||
|
|
||||||
; Disk C Floppy
|
|
||||||
dph_disk_c:
|
|
||||||
DW 0 ; Sector translate table pointer
|
|
||||||
DW 0, 0, 0 ; Scratchpad area
|
|
||||||
DW CPM_VARS.dir_buffer ; Directory buffer pointer
|
|
||||||
DW CPM_VARS.DPB_C_RAM ; DPB Pointer
|
|
||||||
DW CPM_VARS.CHK_VEC_C ; Check Vector pointer
|
|
||||||
DW CPM_VARS.AL_MAP_C ; Allocation map pointer
|
|
||||||
|
|
||||||
res_data: ; offset 0xda28
|
|
||||||
DB 1, 8, 6, 4, 2, 9, 7, 5, 3
|
|
||||||
|
|
||||||
DPB_END EQU $
|
|
||||||
DB 0x0e, 3
|
|
||||||
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; Filler to align blocks in ROM
|
|
||||||
; -------------------------------------------------------
|
|
||||||
LAST EQU $
|
|
||||||
CODE_SIZE EQU LAST-0xD600
|
|
||||||
FILL_SIZE EQU 0x500-CODE_SIZE
|
|
||||||
|
|
||||||
DISPLAY "| BIOS\t| ",/H,boot_f," | ",/H,CODE_SIZE," | ",/H,FILL_SIZE," |"
|
|
||||||
|
|
||||||
IFDEF CHECK_INTEGRITY
|
|
||||||
; Check integrity
|
|
||||||
ASSERT bios_wboot = 0xd6a8
|
|
||||||
ASSERT sel_disk = 0xd781
|
|
||||||
ASSERT home = 0xd7a7
|
|
||||||
ASSERT ram_disk_calc_addr = 0xd7eb
|
|
||||||
ASSERT write_phys_op = 0xd81e
|
|
||||||
ASSERT base_read_write = 0xd88a
|
|
||||||
ASSERT calc_sec_addr_in_bfr = 0xd8e4
|
|
||||||
ASSERT slicer_save_buffer = 0xd928
|
|
||||||
ASSERT print_strz = 0xd95e
|
|
||||||
ASSERT msg_hello = 0xd96b
|
|
||||||
ASSERT dpb_ram = 0xd9af
|
|
||||||
ASSERT dph_disk_a = 0xd9f8
|
|
||||||
ASSERT res_data = 0xda28
|
|
||||||
ENDIF
|
|
||||||
|
|
||||||
FILLER:
|
|
||||||
DS FILL_SIZE, 0xff
|
|
||||||
|
|
||||||
ENDMODULE
|
|
||||||
|
|
||||||
IFNDEF BUILD_ROM
|
|
||||||
OUTEND
|
|
||||||
ENDIF
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,882 +0,0 @@
|
|||||||
; =======================================================
|
|
||||||
; Ocean-240.2
|
|
||||||
; CPM CPP, ROM PART
|
|
||||||
; AT 0xDB00
|
|
||||||
;
|
|
||||||
; Disassembled by Romych 2025-09-09
|
|
||||||
; =======================================================
|
|
||||||
|
|
||||||
INCLUDE "equates.inc"
|
|
||||||
INCLUDE "ram.inc"
|
|
||||||
|
|
||||||
IFNDEF BUILD_ROM
|
|
||||||
OUTPUT ccp_rom.bin
|
|
||||||
ENDIF
|
|
||||||
|
|
||||||
MODULE CCP_ROM
|
|
||||||
|
|
||||||
ORG 0xDB00
|
|
||||||
|
|
||||||
@ccp_entry:
|
|
||||||
LD HL, 0x0 ; prevent stack overflow
|
|
||||||
ADD HL, SP
|
|
||||||
LD (CPM_VARS.saved_stack_ptr), HL
|
|
||||||
LD SP, CPM_VARS.ccp_safe_stack
|
|
||||||
|
|
||||||
CALL get_cmd_index
|
|
||||||
LD HL, ccp_commands ;= DB6Ch
|
|
||||||
LD E, A
|
|
||||||
LD D, 0x0
|
|
||||||
ADD HL, DE
|
|
||||||
ADD HL, DE
|
|
||||||
LD A, (HL)
|
|
||||||
INC HL
|
|
||||||
LD H, (HL)
|
|
||||||
LD L, A
|
|
||||||
JP (HL) ; jump to command
|
|
||||||
|
|
||||||
ccp_commands_str:
|
|
||||||
DB "SDIR READ WRITE"
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; Search user command position in available commands list
|
|
||||||
; -------------------------------------------------------
|
|
||||||
get_cmd_index:
|
|
||||||
LD HL, ccp_commands_str ; -> 'DIR'
|
|
||||||
LD C, 0x0
|
|
||||||
.cmd_next:
|
|
||||||
LD A, C
|
|
||||||
CP CCP_COMMAND_COUNT
|
|
||||||
RET NC
|
|
||||||
LD DE, CCP_RAM.ccp_current_fcb_fn
|
|
||||||
LD B, CCP_COMMAND_SIZE
|
|
||||||
.cmp_nxt:
|
|
||||||
LD A, (DE)
|
|
||||||
CP (HL) ; -> 'DIR'
|
|
||||||
JP NZ, .no_eq
|
|
||||||
INC DE
|
|
||||||
INC HL
|
|
||||||
DEC B
|
|
||||||
JP NZ, .cmp_nxt
|
|
||||||
LD A, (DE)
|
|
||||||
CP ASCII_SP
|
|
||||||
JP NZ, .inc_next
|
|
||||||
LD A, C
|
|
||||||
RET
|
|
||||||
.no_eq:
|
|
||||||
INC HL
|
|
||||||
DEC B
|
|
||||||
JP NZ, .no_eq
|
|
||||||
.inc_next:
|
|
||||||
INC C
|
|
||||||
JP .cmd_next
|
|
||||||
|
|
||||||
; --------------------------------------------------
|
|
||||||
; Command handlers ref table
|
|
||||||
; --------------------------------------------------
|
|
||||||
ccp_commands:
|
|
||||||
DW ccp_dir
|
|
||||||
DW ccp_read
|
|
||||||
DW ccp_write
|
|
||||||
DW ccp_ret ; r8
|
|
||||||
; DW ccp_exit1 ; r8
|
|
||||||
|
|
||||||
ccp_ret:
|
|
||||||
LD HL, (CPM_VARS.saved_stack_ptr)
|
|
||||||
LD SP, HL
|
|
||||||
JP CCP_RAM.ccp_unk_cmd
|
|
||||||
|
|
||||||
;ccp_exit1:
|
|
||||||
; JP MON.mon_hexb
|
|
||||||
; --------------------------------------------------
|
|
||||||
; DIR [filemask] command handler
|
|
||||||
; --------------------------------------------------
|
|
||||||
ccp_dir:
|
|
||||||
CALL CCP_RAM.ccp_cv_first_to_fcb
|
|
||||||
CALL CCP_RAM.ccp_drive_sel
|
|
||||||
; chech some filemask specified in command line
|
|
||||||
LD HL, CCP_RAM.ccp_current_fcb_fn
|
|
||||||
LD A, (HL)
|
|
||||||
CP ' '
|
|
||||||
JP NZ, .has_par
|
|
||||||
|
|
||||||
; no filemask, fill with wildcard '?'
|
|
||||||
LD B, 11
|
|
||||||
.fill_wildcard:
|
|
||||||
LD (HL), '?'
|
|
||||||
INC HL
|
|
||||||
DEC B
|
|
||||||
JP NZ, .fill_wildcard
|
|
||||||
|
|
||||||
; find file by specified mask
|
|
||||||
.has_par:
|
|
||||||
CALL CCP_RAM.ccp_find_first
|
|
||||||
JP NZ, .f_found
|
|
||||||
; no files found, print and exit
|
|
||||||
CALL CCP_RAM.ccp_out_no_file
|
|
||||||
JP CCP_RAM.ccp_cmdline_back
|
|
||||||
|
|
||||||
.f_found:
|
|
||||||
CALL CCP_RAM.ccp_out_crlf
|
|
||||||
LD HL, 0x0
|
|
||||||
LD (CPM_VARS.tmp_dir_total), HL
|
|
||||||
LD E, 0
|
|
||||||
|
|
||||||
.do_next_direntry:
|
|
||||||
PUSH DE
|
|
||||||
CALL CCP_RAM.ccp_find_first
|
|
||||||
POP DE
|
|
||||||
PUSH DE
|
|
||||||
|
|
||||||
; Find file with e number
|
|
||||||
.find_file_e:
|
|
||||||
DEC E
|
|
||||||
JP M, .file_out_next
|
|
||||||
PUSH DE
|
|
||||||
CALL CCP_RAM.ccp_bdos_find_next
|
|
||||||
POP DE
|
|
||||||
JP Z, .file_e_found
|
|
||||||
JP .find_file_e
|
|
||||||
|
|
||||||
.file_out_next:
|
|
||||||
LD A, (CCP_RAM.ccp_bdos_result_code)
|
|
||||||
; calc address of DIR entry in DMA buffer
|
|
||||||
; A[6:5] = A[1:0] = 32*A
|
|
||||||
RRCA ; [C] -> [7:0] -> [C]
|
|
||||||
RRCA ;
|
|
||||||
RRCA ;
|
|
||||||
AND 01100000b ; mask
|
|
||||||
LD C, A
|
|
||||||
PUSH BC
|
|
||||||
CALL CCP_RAM.ccp_out_crlf ; start new line
|
|
||||||
CALL CCP_RAM.ccp_bdos_drv_get
|
|
||||||
INC A
|
|
||||||
LD (dma_buffer), A ; disk
|
|
||||||
POP BC
|
|
||||||
LD B, 0x0
|
|
||||||
LD HL, dma_buffer+FCB_FN ; filename
|
|
||||||
LD E, L
|
|
||||||
LD D, H
|
|
||||||
ADD HL, BC
|
|
||||||
|
|
||||||
; copy filename to tmp FCB and out to screen
|
|
||||||
LD B, 0x1
|
|
||||||
.copy_next:
|
|
||||||
LD A, (HL)
|
|
||||||
LD (DE), A
|
|
||||||
LD C, A
|
|
||||||
CALL BIOS.conout_f
|
|
||||||
INC HL
|
|
||||||
INC DE
|
|
||||||
INC B
|
|
||||||
LD A, B
|
|
||||||
CP FN_LEN ; >12 end of name
|
|
||||||
JP C, .copy_next
|
|
||||||
|
|
||||||
.zero_up_36:
|
|
||||||
XOR A
|
|
||||||
LD (DE), A ; zero at end
|
|
||||||
INC B
|
|
||||||
LD A, B
|
|
||||||
CP 36
|
|
||||||
JP C, .zero_up_36
|
|
||||||
|
|
||||||
; calc file size for current entry
|
|
||||||
LD DE, dma_buffer
|
|
||||||
CALL cpp_bdos_f_size
|
|
||||||
LD HL, (fcb_ra_record_num) ; file size in blocks
|
|
||||||
|
|
||||||
; get disk blk size
|
|
||||||
LD A, (CPM_VARS.bdos_curdsk)
|
|
||||||
OR A
|
|
||||||
JP NZ, .no_dsk_a0
|
|
||||||
LD B, 3 ; for A - blk=3
|
|
||||||
JP .dsk_a0
|
|
||||||
|
|
||||||
.no_dsk_a0:
|
|
||||||
LD B, 4 ; for other disks - blk=4
|
|
||||||
.dsk_a0:
|
|
||||||
LD C, L
|
|
||||||
|
|
||||||
; convert 128b OS block to disk blocks
|
|
||||||
.mul_to_dsk_blk:
|
|
||||||
XOR A
|
|
||||||
LD A, H
|
|
||||||
RRA
|
|
||||||
LD H, A
|
|
||||||
LD A, L
|
|
||||||
RRA
|
|
||||||
LD L, A
|
|
||||||
DEC B
|
|
||||||
JP NZ, .mul_to_dsk_blk
|
|
||||||
; round up
|
|
||||||
LD A, (CPM_VARS.bdos_curdsk)
|
|
||||||
OR A
|
|
||||||
JP NZ, .no_dsk_a1
|
|
||||||
LD A, 00000111b ; for A - ~(~0 << 3)
|
|
||||||
JP .ds_skip1
|
|
||||||
.no_dsk_a1:
|
|
||||||
LD A, 00001111b ; for other dsk - ~(~0 << 4)
|
|
||||||
.ds_skip1:
|
|
||||||
AND C
|
|
||||||
JP Z, .cvt_blk_kb
|
|
||||||
INC HL
|
|
||||||
|
|
||||||
; Convert blocks to kilobytes (A-1k B-2k)
|
|
||||||
.cvt_blk_kb:
|
|
||||||
LD A, (CPM_VARS.bdos_curdsk)
|
|
||||||
OR A
|
|
||||||
JP Z, .ds_skip2
|
|
||||||
ADD HL, HL ; 2k
|
|
||||||
|
|
||||||
; add file size to total dir size
|
|
||||||
.ds_skip2:
|
|
||||||
EX DE, HL
|
|
||||||
LD HL, (CPM_VARS.tmp_dir_total)
|
|
||||||
ADD HL, DE
|
|
||||||
LD (CPM_VARS.tmp_dir_total), HL
|
|
||||||
|
|
||||||
; display size in K
|
|
||||||
LD C, ' '
|
|
||||||
CALL BIOS.conout_f
|
|
||||||
CALL BIOS.conout_f
|
|
||||||
CALL ccp_cout_num
|
|
||||||
LD C, 'K'
|
|
||||||
CALL BIOS.conout_f
|
|
||||||
CALL CCP_RAM.ccp_getkey_no_wait
|
|
||||||
JP NZ, CCP_RAM.ccp_cmdline_back
|
|
||||||
POP DE
|
|
||||||
INC E
|
|
||||||
JP .do_next_direntry
|
|
||||||
|
|
||||||
.file_e_found:
|
|
||||||
POP DE
|
|
||||||
LD HL, msg_free_space ;= "\r\nFREE SPACE "
|
|
||||||
|
|
||||||
; Out: FREE SPACE
|
|
||||||
CALL ccp_out_str_z
|
|
||||||
LD A, (CPM_VARS.bdos_curdsk)
|
|
||||||
OR A
|
|
||||||
JP NZ, .no_ram_dsk
|
|
||||||
LD HL, (BIOS.disk_a_size)
|
|
||||||
JP .calc_remanis_ds
|
|
||||||
|
|
||||||
.no_ram_dsk:
|
|
||||||
DEC A
|
|
||||||
LD HL, (BIOS.disk_b_size)
|
|
||||||
JP Z, .calc_remanis_ds
|
|
||||||
|
|
||||||
LD HL, (BIOS.disk_c_size)
|
|
||||||
LD A, (disk_sw_trk)
|
|
||||||
OR A
|
|
||||||
JP NZ, .calc_remanis_ds
|
|
||||||
|
|
||||||
LD A, H
|
|
||||||
CP 1
|
|
||||||
JP Z, .d720
|
|
||||||
LD HL, 360
|
|
||||||
JP .calc_remanis_ds
|
|
||||||
.d720:
|
|
||||||
LD HL, 720
|
|
||||||
|
|
||||||
; Disk size - Dir size = Free
|
|
||||||
.calc_remanis_ds:
|
|
||||||
EX DE, HL
|
|
||||||
LD HL, (CPM_VARS.tmp_dir_total)
|
|
||||||
LD A, E
|
|
||||||
SUB L
|
|
||||||
LD E, A
|
|
||||||
LD A, D
|
|
||||||
SBC A, H
|
|
||||||
LD D, A
|
|
||||||
CALL ccp_cout_num
|
|
||||||
LD C, 'K'
|
|
||||||
CALL BIOS.conout_f
|
|
||||||
CALL CCP_RAM.ccp_out_crlf
|
|
||||||
JP CCP_RAM.ccp_cmdline_back
|
|
||||||
|
|
||||||
msg_free_space:
|
|
||||||
DB "\r\nFREE SPACE ",0
|
|
||||||
|
|
||||||
ccp_cout_num:
|
|
||||||
LD A, D
|
|
||||||
AND 11100000b
|
|
||||||
JP Z, .less_224
|
|
||||||
LD C, '*'
|
|
||||||
CALL BIOS.conout_f
|
|
||||||
CALL BIOS.conout_f
|
|
||||||
CALL BIOS.conout_f
|
|
||||||
CALL BIOS.conout_f
|
|
||||||
RET
|
|
||||||
|
|
||||||
.less_224:
|
|
||||||
LD HL, 0x0
|
|
||||||
; copy number to BC
|
|
||||||
LD B, D
|
|
||||||
LD C, E
|
|
||||||
LD DE, 0x1
|
|
||||||
LD A, 13
|
|
||||||
|
|
||||||
.bc_rra:
|
|
||||||
PUSH AF
|
|
||||||
PUSH HL
|
|
||||||
; BC >> 1
|
|
||||||
LD A, B
|
|
||||||
RRA
|
|
||||||
LD B, A
|
|
||||||
LD A, C
|
|
||||||
RRA
|
|
||||||
LD C, A
|
|
||||||
JP NC, .bc_rra_ex
|
|
||||||
POP HL
|
|
||||||
CALL cpp_daa16
|
|
||||||
PUSH HL
|
|
||||||
|
|
||||||
.bc_rra_ex:
|
|
||||||
LD L, E
|
|
||||||
LD H, D
|
|
||||||
CALL cpp_daa16
|
|
||||||
EX DE, HL
|
|
||||||
POP HL
|
|
||||||
POP AF
|
|
||||||
DEC A
|
|
||||||
JP NZ, .bc_rra
|
|
||||||
LD D, 4
|
|
||||||
LD B, 0
|
|
||||||
|
|
||||||
.next_d:
|
|
||||||
LD E, 4
|
|
||||||
|
|
||||||
.next_e:
|
|
||||||
LD A, L
|
|
||||||
RLA
|
|
||||||
LD L, A
|
|
||||||
LD A, H
|
|
||||||
RLA
|
|
||||||
LD H, A
|
|
||||||
LD A, C
|
|
||||||
RLA
|
|
||||||
LD C, A
|
|
||||||
DEC E
|
|
||||||
JP NZ, .next_e
|
|
||||||
LD A, C
|
|
||||||
AND 0xf
|
|
||||||
ADD A, '0'
|
|
||||||
CP '0'
|
|
||||||
JP NZ, .no_zero
|
|
||||||
DEC B
|
|
||||||
INC B
|
|
||||||
JP NZ, .b_no_one
|
|
||||||
LD A, D
|
|
||||||
DEC A
|
|
||||||
JP Z, .d_one
|
|
||||||
LD A, ' '
|
|
||||||
JP .b_no_one
|
|
||||||
|
|
||||||
.d_one:
|
|
||||||
LD A, '0'
|
|
||||||
.no_zero:
|
|
||||||
LD B, 0x1
|
|
||||||
.b_no_one:
|
|
||||||
LD C, A
|
|
||||||
CALL BIOS.conout_f
|
|
||||||
DEC D
|
|
||||||
JP NZ, .next_d
|
|
||||||
RET
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; ADD with correction HL=HL+DE
|
|
||||||
; -------------------------------------------------------
|
|
||||||
cpp_daa16:
|
|
||||||
LD A, L
|
|
||||||
ADD A, E
|
|
||||||
DAA
|
|
||||||
LD L, A
|
|
||||||
LD A, H
|
|
||||||
ADC A, D
|
|
||||||
DAA
|
|
||||||
LD H, A
|
|
||||||
RET
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; Call BDOS function 35 (F_SIZE) - Compute file size
|
|
||||||
; -------------------------------------------------------
|
|
||||||
cpp_bdos_f_size:
|
|
||||||
LD C, F_SIZE
|
|
||||||
JP jp_bdos_enter
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; Read Intel HEX data from serial port
|
|
||||||
; -------------------------------------------------------
|
|
||||||
ccp_read:
|
|
||||||
LD DE, msg_read_hex
|
|
||||||
CALL out_dollar_str
|
|
||||||
LD HL, 0x0
|
|
||||||
LD (CCP_RAM.hex_length), HL
|
|
||||||
|
|
||||||
; Wait for start of Intel HEX line
|
|
||||||
.wait_colon:
|
|
||||||
CALL MON.mon_serial_in
|
|
||||||
CP ':'
|
|
||||||
JP NZ, .wait_colon
|
|
||||||
|
|
||||||
; Init checksum
|
|
||||||
XOR A
|
|
||||||
LD D, A
|
|
||||||
|
|
||||||
CALL ser_read_hexb ; read byte_count
|
|
||||||
JP Z, .end_of_file
|
|
||||||
LD E, A
|
|
||||||
CALL ser_read_hexb ; read address hi
|
|
||||||
LD H, A
|
|
||||||
CALL ser_read_hexb ; read address lo
|
|
||||||
LD L, A ; HL - dst address
|
|
||||||
CALL ser_read_hexb ; read rec type
|
|
||||||
|
|
||||||
; calculate length += byte_count
|
|
||||||
PUSH HL
|
|
||||||
LD HL, (CCP_RAM.hex_length)
|
|
||||||
LD A, L
|
|
||||||
ADD A, E
|
|
||||||
LD L, A
|
|
||||||
LD A, H
|
|
||||||
ADC A, 0
|
|
||||||
LD H, A
|
|
||||||
LD (CCP_RAM.hex_length), HL
|
|
||||||
POP HL
|
|
||||||
|
|
||||||
LD C, E
|
|
||||||
|
|
||||||
; receive next E=byte_count bytes
|
|
||||||
.receive_rec:
|
|
||||||
CALL ser_read_hexb
|
|
||||||
LD (HL), A
|
|
||||||
INC HL
|
|
||||||
DEC E
|
|
||||||
JP NZ, .receive_rec
|
|
||||||
|
|
||||||
CALL ser_read_hexb ; receive checksum
|
|
||||||
JP NZ, .load_error ; jump if error
|
|
||||||
JP .wait_colon ; jump to wait next line
|
|
||||||
|
|
||||||
.end_of_file:
|
|
||||||
; read tail 4 bytes: 00 00 01 ff
|
|
||||||
CALL ser_read_hexb
|
|
||||||
CALL ser_read_hexb
|
|
||||||
CALL ser_read_hexb
|
|
||||||
CALL ser_read_hexb
|
|
||||||
JP Z, .load_complete
|
|
||||||
|
|
||||||
.load_error:
|
|
||||||
LD DE, .msg_error
|
|
||||||
JP out_dollar_str
|
|
||||||
|
|
||||||
.load_complete:
|
|
||||||
; Out message with length of received file
|
|
||||||
LD HL, (CCP_RAM.hex_length)
|
|
||||||
LD A, H
|
|
||||||
CALL MON.mon_hexb
|
|
||||||
LD A, L
|
|
||||||
CALL MON.mon_hexb
|
|
||||||
LD DE, .msg_bytes
|
|
||||||
CALL out_dollar_str
|
|
||||||
; Calculate number of pages
|
|
||||||
LD HL, (CCP_RAM.hex_length)
|
|
||||||
LD A, L
|
|
||||||
ADD A, 0xff
|
|
||||||
LD A, H
|
|
||||||
ADC A, 0x0
|
|
||||||
RLA
|
|
||||||
LD (CCP_RAM.hex_sectors), A
|
|
||||||
; Out message with number of pages
|
|
||||||
CALL MON.mon_hexb
|
|
||||||
LD DE, .msg_pages
|
|
||||||
CALL out_dollar_str
|
|
||||||
|
|
||||||
; Check for file name specified in cmd line
|
|
||||||
CALL CCP_RAM.ccp_cv_first_to_fcb
|
|
||||||
CALL CCP_RAM.ccp_drive_sel
|
|
||||||
LD A, (CCP_RAM.ccp_current_fcb_fn)
|
|
||||||
CP ASCII_SP
|
|
||||||
JP Z, .warm_boot
|
|
||||||
|
|
||||||
; Create file
|
|
||||||
LD DE, CCP_RAM.ccp_current_fcb
|
|
||||||
LD C, F_MAKE
|
|
||||||
CALL jp_bdos_enter
|
|
||||||
INC A
|
|
||||||
JP Z, .load_error
|
|
||||||
LD HL, tpa_start
|
|
||||||
LD (CCP_RAM.hex_buff), HL
|
|
||||||
|
|
||||||
.wr_sector:
|
|
||||||
; set source buffer address
|
|
||||||
LD HL, (CCP_RAM.hex_buff)
|
|
||||||
EX DE, HL
|
|
||||||
LD C, F_DMAOFF
|
|
||||||
CALL jp_bdos_enter
|
|
||||||
; write source buffer to disk
|
|
||||||
LD DE, CCP_RAM.ccp_current_fcb
|
|
||||||
LD C, F_WRITE
|
|
||||||
CALL jp_bdos_enter
|
|
||||||
; check errors
|
|
||||||
OR A
|
|
||||||
JP NZ, .load_error
|
|
||||||
|
|
||||||
; rewind forward to next sector
|
|
||||||
LD HL, (CCP_RAM.hex_buff)
|
|
||||||
LD DE, 128 ; sector size
|
|
||||||
ADD HL, DE
|
|
||||||
LD (CCP_RAM.hex_buff), HL
|
|
||||||
|
|
||||||
; decrement sector count
|
|
||||||
LD A, (CCP_RAM.hex_sectors)
|
|
||||||
DEC A
|
|
||||||
LD (CCP_RAM.hex_sectors), A
|
|
||||||
JP NZ, .wr_sector ; jump if remains sectors
|
|
||||||
|
|
||||||
; close file
|
|
||||||
LD DE, CCP_RAM.ccp_current_fcb
|
|
||||||
LD C, F_CLOSE
|
|
||||||
CALL jp_bdos_enter
|
|
||||||
; check errors
|
|
||||||
CP 0xff
|
|
||||||
JP Z, .load_error
|
|
||||||
|
|
||||||
.warm_boot:
|
|
||||||
LD C, P_TERMCPM
|
|
||||||
JP jp_bdos_enter
|
|
||||||
|
|
||||||
.msg_bytes:
|
|
||||||
DB "h bytes ($"
|
|
||||||
|
|
||||||
.msg_pages:
|
|
||||||
DB " pages)\n\r$"
|
|
||||||
|
|
||||||
.msg_error:
|
|
||||||
DB "error!\n\r$"
|
|
||||||
|
|
||||||
; ---------------------------------------------------
|
|
||||||
; Read next two symbols from serial and convert to
|
|
||||||
; byte
|
|
||||||
; Out: A - byte
|
|
||||||
; CF set if error
|
|
||||||
; ---------------------------------------------------
|
|
||||||
ser_read_hexb:
|
|
||||||
PUSH BC
|
|
||||||
CALL MON.mon_serial_in
|
|
||||||
CALL hex_to_nibble
|
|
||||||
RLCA
|
|
||||||
RLCA
|
|
||||||
RLCA
|
|
||||||
RLCA
|
|
||||||
LD C, A
|
|
||||||
CALL MON.mon_serial_in
|
|
||||||
CALL hex_to_nibble
|
|
||||||
OR C
|
|
||||||
LD C, A
|
|
||||||
ADD A, D
|
|
||||||
LD D, A
|
|
||||||
LD A, C
|
|
||||||
POP BC
|
|
||||||
RET
|
|
||||||
|
|
||||||
; ---------------------------------------------------
|
|
||||||
; Convert hex symbol to byte
|
|
||||||
; Inp: A - '0'..'F'
|
|
||||||
; Out: A - 0..15
|
|
||||||
; CF set if error
|
|
||||||
; ---------------------------------------------------
|
|
||||||
hex_to_nibble:
|
|
||||||
SUB '0' ; < '0' - error
|
|
||||||
RET C
|
|
||||||
ADD A, 233 ; F -> 255
|
|
||||||
RET C ; > F - error
|
|
||||||
ADD A, 6
|
|
||||||
JP P, .l1
|
|
||||||
ADD A, 7
|
|
||||||
RET C
|
|
||||||
.l1:
|
|
||||||
ADD A, 10
|
|
||||||
OR A
|
|
||||||
RET
|
|
||||||
|
|
||||||
; ---------------------------------------------------
|
|
||||||
; Out $ ended string
|
|
||||||
; Inp: DE -> string$
|
|
||||||
; ---------------------------------------------------
|
|
||||||
out_dollar_str:
|
|
||||||
LD C, C_WRITESTR
|
|
||||||
JP jp_bdos_enter
|
|
||||||
|
|
||||||
msg_read_hex:
|
|
||||||
DB "\n\rRead HEX from RS232... $"
|
|
||||||
|
|
||||||
filler1:
|
|
||||||
DS 62, 0
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; Out zerro ended string
|
|
||||||
; In: HL -> strZ
|
|
||||||
; -------------------------------------------------------
|
|
||||||
ccp_out_str_z:
|
|
||||||
LD A, (HL)
|
|
||||||
OR A
|
|
||||||
RET Z
|
|
||||||
LD C, A
|
|
||||||
CALL BIOS.conout_f
|
|
||||||
INC HL
|
|
||||||
JP ccp_out_str_z
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; Delete file and out No Space message
|
|
||||||
; -------------------------------------------------------
|
|
||||||
ccp_del_f_no_space:
|
|
||||||
LD DE, CCP_RAM.ccp_current_fcb
|
|
||||||
CALL CCP_RAM.ccp_bdos_era_file
|
|
||||||
JP CCP_RAM.ccp_no_space
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; Read current file next block
|
|
||||||
; Out: A=0 - Ok, 0xFF - HW Error;
|
|
||||||
; -------------------------------------------------------
|
|
||||||
cpp_read_f_blk:
|
|
||||||
LD DE, CPM_VARS.ccp_fcb ; FCB here
|
|
||||||
JP CCP_RAM.ccp_bdos_read_f
|
|
||||||
|
|
||||||
ccp_write:
|
|
||||||
CALL CCP_RAM.ccp_cv_first_to_fcb
|
|
||||||
CALL CCP_RAM.ccp_drive_sel
|
|
||||||
LD HL, CCP_RAM.ccp_current_fcb_fn
|
|
||||||
LD A, (HL)
|
|
||||||
CP ' '
|
|
||||||
JP NZ, .find_f
|
|
||||||
LD B, 11
|
|
||||||
|
|
||||||
.fill_with_wc:
|
|
||||||
LD (HL), '?'
|
|
||||||
INC HL
|
|
||||||
DEC B
|
|
||||||
JP NZ, .fill_with_wc
|
|
||||||
|
|
||||||
.find_f:
|
|
||||||
CALL CCP_RAM.ccp_find_first
|
|
||||||
JP NZ, .found_f
|
|
||||||
CALL CCP_RAM.ccp_out_no_file
|
|
||||||
JP CCP_RAM.ccp_cmdline_back
|
|
||||||
|
|
||||||
.found_f:
|
|
||||||
LD E, 0 ; file counter
|
|
||||||
|
|
||||||
.do_next_f1:
|
|
||||||
PUSH DE
|
|
||||||
CALL CCP_RAM.ccp_find_first
|
|
||||||
POP DE
|
|
||||||
PUSH DE
|
|
||||||
|
|
||||||
.do_next_f2:
|
|
||||||
DEC E
|
|
||||||
JP M, .do_file
|
|
||||||
PUSH DE
|
|
||||||
CALL CCP_RAM.ccp_bdos_find_next
|
|
||||||
POP DE
|
|
||||||
JP Z, .no_more_f
|
|
||||||
JP .do_next_f2
|
|
||||||
|
|
||||||
.do_file:
|
|
||||||
POP BC
|
|
||||||
PUSH BC
|
|
||||||
LD A, C
|
|
||||||
OR A
|
|
||||||
JP Z, .calc_addr
|
|
||||||
LD DE, 1200
|
|
||||||
|
|
||||||
; Delay with key interrupt check
|
|
||||||
.delay_1:
|
|
||||||
XOR A
|
|
||||||
.delay_2:
|
|
||||||
DEC A
|
|
||||||
JP NZ, .delay_2
|
|
||||||
PUSH DE
|
|
||||||
CALL CCP_RAM.ccp_getkey_no_wait
|
|
||||||
POP DE
|
|
||||||
JP NZ, CCP_RAM.ccp_cmdline_back
|
|
||||||
DEC DE
|
|
||||||
LD A, D
|
|
||||||
OR E
|
|
||||||
JP NZ, .delay_1
|
|
||||||
|
|
||||||
.calc_addr:
|
|
||||||
LD A, (CCP_RAM.ccp_bdos_result_code)
|
|
||||||
; A=0-3 - for Ok, calc address of DIR entry in DMA buffer
|
|
||||||
RRCA
|
|
||||||
RRCA
|
|
||||||
RRCA
|
|
||||||
AND 01100000b
|
|
||||||
LD C, A
|
|
||||||
PUSH BC
|
|
||||||
CALL CCP_RAM.ccp_out_crlf
|
|
||||||
CALL CCP_RAM.ccp_bdos_drv_get
|
|
||||||
INC A
|
|
||||||
LD (CPM_VARS.ccp_fcb_dr), A ; set drive number
|
|
||||||
POP BC
|
|
||||||
LD B, 0x0
|
|
||||||
LD HL, dma_buffer+1
|
|
||||||
ADD HL, BC
|
|
||||||
LD DE, CPM_VARS.ccp_fcb_fn
|
|
||||||
LD B, 0x1
|
|
||||||
|
|
||||||
.copy_fn:
|
|
||||||
LD A, (HL)
|
|
||||||
LD (DE), A
|
|
||||||
INC HL
|
|
||||||
INC DE
|
|
||||||
INC B
|
|
||||||
LD A, B
|
|
||||||
CP 12
|
|
||||||
JP C, .copy_fn
|
|
||||||
|
|
||||||
.fillz_fn:
|
|
||||||
XOR A
|
|
||||||
LD (DE), A
|
|
||||||
INC DE
|
|
||||||
INC B
|
|
||||||
LD A, B
|
|
||||||
CP 36
|
|
||||||
JP C, .fillz_fn
|
|
||||||
LD HL, CPM_VARS.ccp_fcb_fn
|
|
||||||
CALL ccp_out_str_z
|
|
||||||
LD HL, dma_buffer
|
|
||||||
|
|
||||||
; Empty first 128 bytes of DMA buffer
|
|
||||||
LD B, 128
|
|
||||||
.clear_buf:
|
|
||||||
LD (HL), 0x0
|
|
||||||
INC HL
|
|
||||||
DEC B
|
|
||||||
JP NZ, .clear_buf
|
|
||||||
|
|
||||||
; Copy file name at buffer start
|
|
||||||
LD HL, dma_buffer
|
|
||||||
LD DE, CPM_VARS.ccp_fcb_fn
|
|
||||||
LD B, 8
|
|
||||||
|
|
||||||
.find_sp:
|
|
||||||
LD A, (DE)
|
|
||||||
CP ' '
|
|
||||||
JP Z, .sp_rep_dot ; ' ' -> '.'
|
|
||||||
LD (HL), A
|
|
||||||
INC HL
|
|
||||||
INC DE
|
|
||||||
JP .find_sp
|
|
||||||
.sp_rep_dot:
|
|
||||||
LD (HL), '.'
|
|
||||||
INC HL
|
|
||||||
CALL CCP_RAM.ccp_find_no_space
|
|
||||||
|
|
||||||
.cont_copy_fn:
|
|
||||||
LD A, (DE)
|
|
||||||
LD (HL), A
|
|
||||||
OR A
|
|
||||||
JP Z, .end_copy
|
|
||||||
INC HL
|
|
||||||
INC DE
|
|
||||||
JP .cont_copy_fn
|
|
||||||
|
|
||||||
.end_copy:
|
|
||||||
LD DE, CPM_VARS.ccp_fcb
|
|
||||||
CALL CCP_RAM.ccp_bdos_open_f
|
|
||||||
LD DE, 0x8000 ; Block ID
|
|
||||||
LD HL, dma_buffer
|
|
||||||
CALL cpp_pause_tape_wr_blk
|
|
||||||
CALL cpp_pause_tape_wr_blk
|
|
||||||
LD DE, 0x1
|
|
||||||
|
|
||||||
; Read file block and write to Tape
|
|
||||||
.read_f_write_t:
|
|
||||||
PUSH DE
|
|
||||||
CALL cpp_read_f_blk
|
|
||||||
; a=0xff if error; a=1 - EOF
|
|
||||||
DEC A
|
|
||||||
JP Z, .eof
|
|
||||||
LD A, (CPM_VARS.ccp_fcb_cr)
|
|
||||||
AND 0x7f
|
|
||||||
CP 0x1
|
|
||||||
JP NZ, .write_once
|
|
||||||
; Write block to Tape with ID=0 twice
|
|
||||||
LD DE, 0x0 ; Block ID=0
|
|
||||||
LD HL, dma_buffer
|
|
||||||
CALL cpp_pause_tape_wr_blk
|
|
||||||
|
|
||||||
.write_once:
|
|
||||||
CALL CCP_RAM.ccp_getkey_no_wait
|
|
||||||
LD HL, dma_buffer
|
|
||||||
POP DE
|
|
||||||
JP NZ, CCP_RAM.ccp_cmdline_back
|
|
||||||
CALL cpp_pause_tape_wr_blk
|
|
||||||
; Inc Block ID and continue
|
|
||||||
INC DE
|
|
||||||
JP .read_f_write_t
|
|
||||||
|
|
||||||
.eof:
|
|
||||||
POP DE
|
|
||||||
EX DE, HL
|
|
||||||
LD (dma_buffer), HL
|
|
||||||
EX DE, HL
|
|
||||||
|
|
||||||
; Final block ID=0xFFFF
|
|
||||||
LD DE, 0xffff
|
|
||||||
; Write twice
|
|
||||||
CALL cpp_pause_tape_wr_blk
|
|
||||||
CALL cpp_pause_tape_wr_blk
|
|
||||||
POP DE
|
|
||||||
INC E
|
|
||||||
JP .do_next_f1
|
|
||||||
|
|
||||||
.no_more_f:
|
|
||||||
POP DE
|
|
||||||
CALL CCP_RAM.ccp_out_crlf
|
|
||||||
JP CCP_RAM.ccp_cmdline_back
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; Write block to tape after pause
|
|
||||||
; -------------------------------------------------------
|
|
||||||
cpp_pause_tape_wr_blk:
|
|
||||||
LD BC, 3036
|
|
||||||
.delay:
|
|
||||||
DEC BC
|
|
||||||
LD A, B
|
|
||||||
OR C
|
|
||||||
JP NZ, .delay
|
|
||||||
JP BIOS.tape_write_f
|
|
||||||
|
|
||||||
DB 0x4c
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; Filler to align blocks in ROM
|
|
||||||
; -------------------------------------------------------
|
|
||||||
LAST EQU $
|
|
||||||
CODE_SIZE EQU LAST-0xDB00
|
|
||||||
FILL_SIZE EQU 0x500-CODE_SIZE
|
|
||||||
|
|
||||||
DISPLAY "| CCP_ROM\t| ",/H,ccp_entry," | ",/H,CODE_SIZE," | ",/H,FILL_SIZE," |"
|
|
||||||
|
|
||||||
; Check integrity
|
|
||||||
ASSERT ccp_dir = 0xdb62
|
|
||||||
ASSERT ccp_dir.find_file_e = 0xdb97
|
|
||||||
ASSERT ccp_dir.file_out_next = 0xdba6
|
|
||||||
ASSERT ccp_dir.file_e_found = 0xdc3e
|
|
||||||
ASSERT ccp_dir.calc_remanis_ds = 0xdc72
|
|
||||||
ASSERT ccp_dir.no_ram_dsk = 0xdc52
|
|
||||||
ASSERT msg_free_space = 0xdc8a
|
|
||||||
ASSERT ccp_cout_num = 0xdc9a
|
|
||||||
|
|
||||||
FILLER
|
|
||||||
DS FILL_SIZE-1, 0xff
|
|
||||||
DB 0xaa
|
|
||||||
|
|
||||||
ENDMODULE
|
|
||||||
|
|
||||||
IFNDEF BUILD_ROM
|
|
||||||
OUTEND
|
|
||||||
ENDIF
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
; ======================================================
|
|
||||||
; Ocean-240.2
|
|
||||||
; CP/M Combine file. Includes all sources to build
|
|
||||||
; ROM 0xC000
|
|
||||||
;
|
|
||||||
; Disassembled by Romych 2025-02-12
|
|
||||||
; ======================================================
|
|
||||||
|
|
||||||
DEFINE BUILD_ROM
|
|
||||||
|
|
||||||
DEVICE NOSLOT64K
|
|
||||||
|
|
||||||
;
|
|
||||||
; |-----------|---------------|-----------|--------------------------------------|
|
|
||||||
; | OFFSET | SIZE | Module | Memory Address |
|
|
||||||
; |-----------|---------------|-----------|--------------------------------------|
|
|
||||||
; | 0x0000 | 2048 (0x800) | CCP_RAM | 0xC000..0xC7FF -> RAM 0xB200..0xB5FF |
|
|
||||||
; | 0x0800 | 3584 (0xE00) | BDOS | 0xC800.. |
|
|
||||||
; | 0x1600 | 1024 (0x400) | BIOS | 0xD600..D9FF |
|
|
||||||
; | 0x1B00 | 1280 (0x500) | CCP_ROM | 0xDB00..DFFF |
|
|
||||||
; |-----------|---------------|-----------|--------------------------------------|
|
|
||||||
;
|
|
||||||
|
|
||||||
DISPLAY "| Module | Offset | Size | Free |"
|
|
||||||
DISPLAY "|-------------|---------|--------|--------|"
|
|
||||||
|
|
||||||
|
|
||||||
OUTPUT cpm-C000.bin
|
|
||||||
;LABELSLIST "cpm.labels"
|
|
||||||
;CSPECTMAP "cpm.map"
|
|
||||||
INCLUDE "equates.inc"
|
|
||||||
INCLUDE "bdos.inc"
|
|
||||||
|
|
||||||
INCLUDE "ccp_ram.asm"
|
|
||||||
INCLUDE "bdos.asm"
|
|
||||||
INCLUDE "bios.asm"
|
|
||||||
INCLUDE "ccp_rom.asm"
|
|
||||||
|
|
||||||
OUTEND
|
|
||||||
|
|
||||||
OUTPUT variables.bin
|
|
||||||
INCLUDE "cpm_vars.inc"
|
|
||||||
OUTEND
|
|
||||||
|
|
||||||
|
|
||||||
END
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,43 +0,0 @@
|
|||||||
; ==================================================
|
|
||||||
; Simple CP/M program
|
|
||||||
; By Romych
|
|
||||||
; ==================================================
|
|
||||||
DEVICE NOSLOT64K
|
|
||||||
SLDOPT COMMENT WPMEM, ASSERTION, LOGPOINT
|
|
||||||
|
|
||||||
; INCLUDE "ok240/equates.inc"
|
|
||||||
INCLUDE "ok240/bdos.inc"
|
|
||||||
|
|
||||||
OUTPUT main.com
|
|
||||||
|
|
||||||
ORG 0x100
|
|
||||||
|
|
||||||
LD B, 10
|
|
||||||
LD SP, stack
|
|
||||||
AGAIN:
|
|
||||||
LD DE, message
|
|
||||||
; ASSERTION B < 11
|
|
||||||
LD A, 10
|
|
||||||
SUB B
|
|
||||||
OR 0x30
|
|
||||||
LD (DE), A
|
|
||||||
LD C, C_WRITESTR
|
|
||||||
PUSH BC
|
|
||||||
CALL BDOS_ENTER
|
|
||||||
POP BC
|
|
||||||
DEC B ; LOGPOINT
|
|
||||||
JP NZ, AGAIN
|
|
||||||
JP WARM_BOOT
|
|
||||||
|
|
||||||
message: ; WPMEM, 1, w
|
|
||||||
DB "n - Welcome to OK240.2!\r\n$"
|
|
||||||
|
|
||||||
OUTEND
|
|
||||||
|
|
||||||
;DS 1024
|
|
||||||
stack EQU 0xbfc0
|
|
||||||
|
|
||||||
;DISPLAY "message: EQU\t| ",/H,message
|
|
||||||
|
|
||||||
|
|
||||||
END
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,73 +0,0 @@
|
|||||||
; =======================================================
|
|
||||||
; Ocean-240.2
|
|
||||||
; CP/M v2.2. Equates to call BDOS functions
|
|
||||||
;
|
|
||||||
; Created by Romych 2026-02-16
|
|
||||||
; =======================================================
|
|
||||||
|
|
||||||
|
|
||||||
IFNDEF _BDOS_INC
|
|
||||||
DEFINE _BDOS_INC
|
|
||||||
|
|
||||||
; ------------------------------------------------------
|
|
||||||
; CP/M Entries and variables
|
|
||||||
; ------------------------------------------------------
|
|
||||||
WARM_BOOT EQU 0x0000 ; Jump warm_boot (Restart)
|
|
||||||
IO_BYTE EQU 0x0003 ; Input/Output mapping
|
|
||||||
CUR_USER_DRV EQU 0x0004 ; [7:4] - curent user, [3:0] - current drive
|
|
||||||
BDOS_ENTER EQU 0x0005 ; Jump bdos (CALL 5 to make CP/M requests)
|
|
||||||
FCB1 EQU 0x005c ; Default FCB, 16 bytes
|
|
||||||
FCB2 EQU 0x006c
|
|
||||||
|
|
||||||
DMA_BUFFER EQU 0x0080 ; Default "DMA" 128 bytes buffer
|
|
||||||
CMD_LINE_LEN EQU 0x0080 ; command line character count
|
|
||||||
CMD_LINE EQU 0x0081 ; command line buffer
|
|
||||||
TPA EQU 0x0100 ; start of program
|
|
||||||
|
|
||||||
|
|
||||||
; ------------------------------------------------------
|
|
||||||
; CP/M BDOS Functions
|
|
||||||
; ------------------------------------------------------
|
|
||||||
P_TERMCPM EQU 0 ; System Reset
|
|
||||||
C_READ EQU 1 ; Console input
|
|
||||||
C_WRITE EQU 2 ; Console output
|
|
||||||
A_READ EQU 3 ; Auxiliary (Reader) input
|
|
||||||
A_WRITE EQU 4 ; Auxiliary (Punch) output
|
|
||||||
L_WRITE EQU 5 ; Printer output
|
|
||||||
C_RAWIO EQU 6 ; Direct console I/O
|
|
||||||
A_STATIN EQU 7 ; Auxiliary Input status
|
|
||||||
A_STATOUT EQU 8 ; Auxiliary Output status
|
|
||||||
C_WRITESTR EQU 9 ; Output string
|
|
||||||
C_READSTR EQU 10 ; Buffered console input
|
|
||||||
C_STAT EQU 11 ; Console status
|
|
||||||
S_BDOSVER EQU 12 ; Return version number
|
|
||||||
DRV_ALLRESET EQU 13 ; Reset discs
|
|
||||||
DRV_SET EQU 14 ; Select disc
|
|
||||||
F_OPEN EQU 15 ; Open file
|
|
||||||
F_CLOSE EQU 16 ; Close file
|
|
||||||
F_SFIRST EQU 17 ; search for first
|
|
||||||
F_SNEXT EQU 18 ; search for next
|
|
||||||
F_DELETE EQU 19 ; delete file
|
|
||||||
F_READ EQU 20 ; read next record
|
|
||||||
F_WRITE EQU 21 ; write next record
|
|
||||||
F_MAKE EQU 22 ; create file
|
|
||||||
F_RENAME EQU 23 ; Rename file
|
|
||||||
DRV_LOGINVEC EQU 24 ; Return bitmap of logged-in drives
|
|
||||||
DRV_GET EQU 25 ; Return current drive
|
|
||||||
F_DMAOFF EQU 26 ; Set DMA address
|
|
||||||
DRV_ALLOCVEC EQU 27 ; Return address of allocation map
|
|
||||||
DRV_SETRO EQU 28 ; Software write-protect current disc
|
|
||||||
DRV_ROVEC EQU 29 ; Return bitmap of read-only drives
|
|
||||||
F_ATTRIB EQU 30 ; set file attributes
|
|
||||||
DRV_DPB EQU 31 ; get DPB address
|
|
||||||
F_USERNUM EQU 32 ; get/set user number
|
|
||||||
F_READRAND EQU 33 ; Random access read record
|
|
||||||
F_WRITERAND EQU 34 ; Random access write record
|
|
||||||
F_SIZE EQU 35 ; Compute file size
|
|
||||||
F_RANDREC EQU 36 ; Update random access pointer
|
|
||||||
DRV_RESET EQU 37 ; Selectively reset disc drives
|
|
||||||
DRV_ACCESS EQU 38 ; Access drives
|
|
||||||
DRV_FREE EQU 39 ; Free drive
|
|
||||||
F_WRITEZF EQU 40 ; Write random with zero fill
|
|
||||||
|
|
||||||
ENDIF
|
|
||||||
@ -1,104 +0,0 @@
|
|||||||
; ======================================================
|
|
||||||
; Ocean-240.2
|
|
||||||
; Equates for all assembly sources
|
|
||||||
;
|
|
||||||
; By Romych 2026-03-10
|
|
||||||
; ======================================================
|
|
||||||
|
|
||||||
IFNDEF _EQUATES
|
|
||||||
DEFINE _EQUATES
|
|
||||||
|
|
||||||
ASCII_BELL EQU 0x07 ; Make Beep
|
|
||||||
ASCII_BS EQU 0x08 ; Move cursor left (Back Space)
|
|
||||||
ASCII_TAB EQU 0x09 ; Move cursor right +8 pos
|
|
||||||
ASCII_LF EQU 0x0A ; Move cursor down (Line Feed)
|
|
||||||
ASCII_FF EQU 0x0C ; Move cursor to home (Form Feed)
|
|
||||||
ASCII_CR EQU 0x0D ; Move gursor to 1st pos (Cariage Return)
|
|
||||||
ASCII_CAN EQU 0x18 ; Move cursor right
|
|
||||||
ASCII_EM EQU 0x19 ; Move cursor up
|
|
||||||
ASCII_SUB EQU 0x1A ; CTRL-Z - end of text file marker
|
|
||||||
ASCII_ESC EQU 0x1B ;
|
|
||||||
ASCII_US EQU 0x1F ; Clear screen
|
|
||||||
ASCII_SP EQU 0x20
|
|
||||||
ASCII_DEL EQU 0x7F
|
|
||||||
|
|
||||||
; ------------------------------------------------------
|
|
||||||
BELL_PIN EQU 0x08 ; DD67 Pin PC3 - "BELL"
|
|
||||||
; ------------------------------------------------------
|
|
||||||
CTRL_A EQU 0x01
|
|
||||||
CTRL_B EQU 0x02
|
|
||||||
CTRL_C EQU 0x03 ; Warm boot
|
|
||||||
CTRL_D EQU 0x04
|
|
||||||
CTRL_E EQU 0x05 ; Move to beginning of new line (Physical EOL)
|
|
||||||
CTRL_F EQU 0x06
|
|
||||||
CTRL_G EQU 0x07
|
|
||||||
CTRL_H EQU 0x08 ; Backspace
|
|
||||||
CTRL_I EQU 0x09
|
|
||||||
CTRL_J EQU 0x0A ; LF - Line Feed
|
|
||||||
CTRL_K EQU 0x0B
|
|
||||||
CTRL_L EQU 0x0C
|
|
||||||
CTRL_M EQU 0x0D ; CR - Carriage Return
|
|
||||||
CTRL_N EQU 0x0E
|
|
||||||
CTRL_O EQU 0x0F
|
|
||||||
CTRL_P EQU 0x10 ; turn on/off printer
|
|
||||||
CTRL_Q EQU 0x11
|
|
||||||
CTRL_R EQU 0x12 ; Repeat current cmd line
|
|
||||||
CTRL_S EQU 0x13 ; Temporary stop display data to console (aka DC3)
|
|
||||||
CTRL_T EQU 0x14
|
|
||||||
CTRL_U EQU 0x15 ; Cancel (erase) current cmd line
|
|
||||||
CTRL_V EQU 0x16
|
|
||||||
CTRL_W EQU 0x17
|
|
||||||
CTRL_X EQU 0x18 ; Cancel (erase) current cmd line
|
|
||||||
CTRL_Y EQU 0x19
|
|
||||||
CTRL_Z EQU 0x1A ; CTRL-Z - end of text file marker
|
|
||||||
|
|
||||||
; ------------------------------------------------------
|
|
||||||
TRUE EQU 0xFF
|
|
||||||
FALSE EQU 0x00
|
|
||||||
|
|
||||||
; ---------------------------------------------------
|
|
||||||
; FCB Offsets
|
|
||||||
; ---------------------------------------------------
|
|
||||||
FCB_LEN EQU 32 ; length of FCB
|
|
||||||
FCB_SHF EQU 5
|
|
||||||
FN_LEN EQU 12 ; Length of filename in FCB
|
|
||||||
FCB_DR EQU 0 ; Drive. 0 for default, 1-16 for A-P
|
|
||||||
FCB_FN EQU 1 ; Fn - Filename, 7-bit ASCII. The top bits - attributes
|
|
||||||
FCB_FT EQU 9 ; Filetype, 7-bit ASCII.
|
|
||||||
; T1' to T3' have the following
|
|
||||||
; T1' - Read-Only
|
|
||||||
; T2' - System (hidden)
|
|
||||||
; T3' - Archive
|
|
||||||
FCB_EXT EQU 12 ; EX - Set this to 0 when opening a file and then leave it to
|
|
||||||
; CP/M. You can rewind a file by setting EX, RC, S2 and CR to 0.
|
|
||||||
FCB_S1 EQU 13 ; S1 - Reserved.
|
|
||||||
FCB_S2 EQU 14 ; S2 - Reserved. Bit 7 - wile write flag, [6:0] module number (Extent hi bits)
|
|
||||||
FCB_RC EQU 15 ; RC - Set this to 0 when opening a file and then leave it to CP/M.
|
|
||||||
FCB_AL EQU 16 ; AL - Image of the second half of the directory entry,
|
|
||||||
; containing the file's allocation (which disc blocks it owns).
|
|
||||||
FCB_CR EQU 32 ; CR - Current record within extent. It is usually best to set
|
|
||||||
; this to 0 immediately after a file has been opened and
|
|
||||||
; then ignore it.
|
|
||||||
FCB_RN EQU 33 ; Rn - Random access record number. A 16-bit (with R2 used for overflow)
|
|
||||||
|
|
||||||
|
|
||||||
; ------------------------------------------------------
|
|
||||||
IRQ_0 EQU 0x01
|
|
||||||
IRQ_1 EQU 0x02
|
|
||||||
IRQ_2 EQU 0x04
|
|
||||||
|
|
||||||
IRQ_KEYBOARD EQU 0x02
|
|
||||||
IRQ_PRINTER EQU 0x08
|
|
||||||
IRQ_TIMER EQU 0x10
|
|
||||||
|
|
||||||
KBD_ACK EQU 0x10
|
|
||||||
PRINTER_ACK EQU 0x10
|
|
||||||
|
|
||||||
TX_READY EQU 0x01
|
|
||||||
RX_READY EQU 0x02
|
|
||||||
|
|
||||||
TMR0_SQWAVE EQU 0x36
|
|
||||||
|
|
||||||
; ------------------------------------------------------
|
|
||||||
|
|
||||||
ENDIF
|
|
||||||
@ -1,132 +0,0 @@
|
|||||||
; =======================================================
|
|
||||||
; Ocean-240.2
|
|
||||||
; Computer with FDC variant.
|
|
||||||
; IO Ports definitions
|
|
||||||
;
|
|
||||||
; By Romych 2025-09-09
|
|
||||||
; =======================================================
|
|
||||||
|
|
||||||
IFNDEF _IO_PORTS
|
|
||||||
DEFINE _IO_PORTS
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; КР580ВВ55 DD79
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; Port A - User port A
|
|
||||||
USR_DD79PA EQU 0x00
|
|
||||||
|
|
||||||
; Port B - User port B
|
|
||||||
USR_DD79PB EQU 0x01
|
|
||||||
|
|
||||||
; Port C - User port C
|
|
||||||
USR_DD79PC EQU 0x02
|
|
||||||
|
|
||||||
; Config: [1][ma1,ma0][0-aO|1-aI],[0-chO,1-chI],[mb],[0-bO|1-bI],[0-clO,1-clI]
|
|
||||||
; Set bit: [0][xxx][bbb][0|1]
|
|
||||||
USR_DD79CTR EQU 0x03
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; КР1818ВГ93
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; CMD
|
|
||||||
FDC_CMD EQU 0x20
|
|
||||||
|
|
||||||
; TRACK
|
|
||||||
FDC_TRACK EQU 0x21
|
|
||||||
|
|
||||||
; SECTOR
|
|
||||||
FDC_SECT EQU 0x22
|
|
||||||
|
|
||||||
; DATA
|
|
||||||
FDC_DATA EQU 0x23
|
|
||||||
|
|
||||||
;
|
|
||||||
FDC_DRQ EQU 0x24
|
|
||||||
|
|
||||||
; Controller port
|
|
||||||
FLOPPY EQU 0x25
|
|
||||||
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; КР580ВВ55 DD78
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; Port A - Keyboard Data
|
|
||||||
KBD_DD78PA EQU 0x40
|
|
||||||
|
|
||||||
; Port B - JST3,SHFT,CTRL,ACK,TAPE5,TAPE4,GK,GC
|
|
||||||
KBD_DD78PB EQU 0x41
|
|
||||||
|
|
||||||
; Port C - [PC7..0]
|
|
||||||
KBD_DD78PC EQU 0x42
|
|
||||||
|
|
||||||
; Сonfig: [1][ma1,ma0][0-aO|1-aI],[0-chO,1-chI],[mb],[0-bO|1-bI],[0-clO,1-clI]
|
|
||||||
; Set bit: [0][xxx][bbb][0|1];
|
|
||||||
KBD_DD78CTR EQU 0x43
|
|
||||||
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; КР580ВИ53 DD70
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; Counter 1
|
|
||||||
TMR_DD70C1 EQU 0x60
|
|
||||||
|
|
||||||
; Counter 2
|
|
||||||
TMR_DD70C2 EQU 0x61
|
|
||||||
|
|
||||||
; Counter 3
|
|
||||||
TMR_DD70C3 EQU 0x62
|
|
||||||
|
|
||||||
; Config: [sc1,sc0][rl1,rl0][m2,m1,m0][bcd]
|
|
||||||
; sc - timer, rl=01-LSB, 10-MSB, 11-LSB+MSB
|
|
||||||
; mode 000 - int on fin,
|
|
||||||
; 001 - one shot,
|
|
||||||
; x10 - rate gen,
|
|
||||||
; x11-sq wave
|
|
||||||
TMR_DD70CTR EQU 0x63
|
|
||||||
|
|
||||||
; Programable Interrupt controller PIC KR580VV59
|
|
||||||
PIC_DD75A EQU 0x80
|
|
||||||
PIC_DD75B EQU 0x81
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; КР580ВВ51 DD72
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; Data
|
|
||||||
UART_DD72RD EQU 0xA0
|
|
||||||
|
|
||||||
; [RST,RQ_RX,RST_ERR,PAUSE,RX_EN,RX_RDY,TX_RDY]
|
|
||||||
UART_DD72RR EQU 0xA1
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; КР580ВВ55 DD17
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; Port A - VShift[8..1]
|
|
||||||
SYS_DD17PA EQU 0xC0
|
|
||||||
|
|
||||||
; Port B - [ROM14,13][REST][ENROM-][A18,17,16][32k]
|
|
||||||
SYS_DD17PB EQU 0xC1
|
|
||||||
|
|
||||||
; Port C - HShift[HS5..1,SB3..1]
|
|
||||||
SYS_DD17PC EQU 0xC2
|
|
||||||
|
|
||||||
; Сonfig: [1][ma1,ma0][0-aO|1-aI],[0-chO,1-chI],[mb],[0-bO|1-bI],[0-clO,1-clI]
|
|
||||||
; Set bit: [0][xxx][bbb][0|1];
|
|
||||||
SYS_DD17CTR EQU 0xC3
|
|
||||||
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; КР580ВВ55 DD67
|
|
||||||
; -------------------------------------------------------
|
|
||||||
; Port A - LPT Data
|
|
||||||
LPT_DD67PA EQU 0xE0
|
|
||||||
|
|
||||||
; Port B - [VSU,C/M,FL3..1,COL3..1]
|
|
||||||
VID_DD67PB EQU 0xE1
|
|
||||||
|
|
||||||
; Port C - [USER3..1,STB-LP,BELL,TAPE3..1]
|
|
||||||
DD67PC EQU 0xE2
|
|
||||||
|
|
||||||
; Сonfig: [1][ma1,ma0][0-aO|1-aI],[0-chO,1-chI],[mb],[0-bO|1-bI],[0-clO,1-clI]
|
|
||||||
; Set bit: [0][xxx][bbb][0|1];
|
|
||||||
DD67CTR EQU 0xE3
|
|
||||||
|
|
||||||
ENDIF
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
; =======================================================
|
|
||||||
; Ocean-240.2
|
|
||||||
; Equates to call Monitor v8 functions
|
|
||||||
;
|
|
||||||
; Created by Romych 2026-02-16
|
|
||||||
; =======================================================
|
|
||||||
|
|
||||||
IFNDEF _MON_INC
|
|
||||||
DEFINE _MON_INC
|
|
||||||
|
|
||||||
mon_start EQU 0xE000
|
|
||||||
mon_hexb EQU 0xE003
|
|
||||||
non_con_status EQU 0xE006
|
|
||||||
mon_con_in EQU 0xE009
|
|
||||||
mon_con_out EQU 0xE00C
|
|
||||||
mon_serial_in EQU 0xE00F
|
|
||||||
mon_serial_out EQU 0xE012
|
|
||||||
mon_char_print EQU 0xE015
|
|
||||||
mon_tape_read EQU 0xE018
|
|
||||||
mon_tape_write EQU 0xE01B
|
|
||||||
mon_ram_disk_read EQU 0xE01E
|
|
||||||
mon_ram_disk_write EQU 0xE021
|
|
||||||
mon_tape_read_ram EQU 0xE024
|
|
||||||
mon_tape_write_ram EQU 0xE027
|
|
||||||
mon_tape_wait EQU 0xE02A
|
|
||||||
mon_tape_detect EQU 0xE02D
|
|
||||||
mon_read_floppy EQU 0xE030
|
|
||||||
mon_write_floppy EQU 0xE033
|
|
||||||
mon_out_str_z EQU 0xE036
|
|
||||||
|
|
||||||
ENDIF
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
; =======================================================
|
|
||||||
; Ocean-240.2
|
|
||||||
;
|
|
||||||
; RAM area at address: 0x0000 - 0x0100, used by CP/M and
|
|
||||||
; HW-Monitor
|
|
||||||
; By Romych 2026-02-03
|
|
||||||
; =======================================================
|
|
||||||
|
|
||||||
IFNDEF _RAM
|
|
||||||
DEFINE _RAM
|
|
||||||
|
|
||||||
MODULE RAM
|
|
||||||
|
|
||||||
@warm_boot EQU 0x0000 ; Jump warm_boot (Restart)
|
|
||||||
@warm_boot_addr EQU 0x0001 ; address of warm boot entry point
|
|
||||||
@iobyte EQU 0x0003 ; Input/Output mapping
|
|
||||||
@cur_user_drv EQU 0x0004 ; [7:4] - curent user, [3:0] - current drive
|
|
||||||
@bdos_enter EQU 0x0005 ; Jump bdos (CALL 5 to make CP/M requests)
|
|
||||||
@bdos_ent_addr EQU 0x0006 ; addres of BDOS entry point
|
|
||||||
;@RST1 EQU 0x0008
|
|
||||||
;@RST1_handler_addr EQU 0x0009
|
|
||||||
;RST2 EQU 0x0010
|
|
||||||
;RST3 EQU 0x0018
|
|
||||||
;RST4 EQU 0x0020
|
|
||||||
;RST5 EQU 0x0028
|
|
||||||
;RST6 EQU 0x0030
|
|
||||||
;RST7 EQU 0x0038
|
|
||||||
@fcb1 EQU 0x005c ; Default FCB, 16 bytes
|
|
||||||
@fcb2 EQU 0x006c
|
|
||||||
;NMI_ISR EQU 0x0066
|
|
||||||
|
|
||||||
@dma_buffer EQU 0x0080 ; Default "DMA" 128 bytes buffer
|
|
||||||
@p_cmd_line_len EQU 0x0080 ; command line character count
|
|
||||||
@p_cmd_line EQU 0x0081 ; command line buffer
|
|
||||||
@bios_stack EQU 0x0100
|
|
||||||
@tpa_start EQU 0x0100 ; start of program
|
|
||||||
@video_ram EQU 0x4000
|
|
||||||
|
|
||||||
ENDMODULE
|
|
||||||
|
|
||||||
ENDIF
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
# Эмулятор персонального компьютера Океан-240.2
|
|
||||||
|
|
||||||
## Образы дискет для эмулятора.
|
|
||||||
|
|
||||||
В файле floppyB.okd собрано большинство софта для компьютера. По умолчанию, при запуске эмулятора, это дискета B:.
|
|
||||||
Софт отсортирован по типам для разных юзеров.
|
|
||||||
|
|
||||||
- USER 0 - утилиты ОС CP/M
|
|
||||||
- USER 1 - Ассемблеры, линкеры
|
|
||||||
- USER 2 - MSBASIC и программы *.BAS
|
|
||||||
- USER 3 - Игры
|
|
||||||
- USER 5 - ZEXDOC, ZEXALL - тесты процессора Z80
|
|
||||||
|
|
||||||
Файл empty.okd - это образ пустой, отформатированной дискеты.
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
10
go.mod
Normal file
10
go.mod
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
module z80em
|
||||||
|
|
||||||
|
go 1.25
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/sirupsen/logrus v1.9.4
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require golang.org/x/sys v0.13.0 // indirect
|
||||||
14
go.sum
Normal file
14
go.sum
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||||
|
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||||
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
BIN
img/Debug.jpg
BIN
img/Debug.jpg
Binary file not shown.
|
Before Width: | Height: | Size: 149 KiB |
BIN
img/IconBig.png
BIN
img/IconBig.png
Binary file not shown.
|
Before Width: | Height: | Size: 210 KiB |
@ -3,7 +3,6 @@ package logger
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"okemu/config"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -46,12 +45,15 @@ func InitLogging() {
|
|||||||
// FlushLogs Flush logs if logging to file
|
// FlushLogs Flush logs if logging to file
|
||||||
func FlushLogs() {
|
func FlushLogs() {
|
||||||
if logBuffer != nil {
|
if logBuffer != nil {
|
||||||
_ = logBuffer.Flush()
|
err := logBuffer.Flush()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReconfigureLogging Reconfigure logging by config values
|
// ReconfigureLogging Reconfigure logging by config values
|
||||||
func ReconfigureLogging(config *config.OkEmuConfig) {
|
func ReconfigureLogging(config *config.ServiceConfig) {
|
||||||
// redirect logging if log file specified
|
// redirect logging if log file specified
|
||||||
if len(config.LogFile) > 0 {
|
if len(config.LogFile) > 0 {
|
||||||
var err error
|
var err error
|
||||||
29
main.go
Normal file
29
main.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"z80em/config"
|
||||||
|
"z80em/logger"
|
||||||
|
"z80em/okean240"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Version = "v1.0.0"
|
||||||
|
var BuildTime = "2026-03-01"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// base log init
|
||||||
|
logger.InitLogging()
|
||||||
|
|
||||||
|
// load config yml file
|
||||||
|
config.LoadConfig()
|
||||||
|
|
||||||
|
conf := config.GetConfig()
|
||||||
|
|
||||||
|
// Reconfigure logging by config values
|
||||||
|
logger.ReconfigureLogging(conf)
|
||||||
|
|
||||||
|
println("Init computer")
|
||||||
|
computer := okean240.New(config)
|
||||||
|
println("Run computer")
|
||||||
|
computer.Run()
|
||||||
|
}
|
||||||
Binary file not shown.
BIN
okean240/MON_r6.BIN
Normal file
BIN
okean240/MON_r6.BIN
Normal file
Binary file not shown.
75
okean240/computer.go
Normal file
75
okean240/computer.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package okean240
|
||||||
|
|
||||||
|
import (
|
||||||
|
"z80em/config"
|
||||||
|
"z80em/z80em"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ComputerType struct {
|
||||||
|
cpu z80em.Z80Type
|
||||||
|
memory Memory
|
||||||
|
ioPorts [256]byte
|
||||||
|
cycles uint64
|
||||||
|
dd17EnableOut bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type ComputerInterface interface {
|
||||||
|
//Init(rom0 string, rom1 string)
|
||||||
|
Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ComputerType) M1MemRead(addr uint16) byte {
|
||||||
|
return c.memory.M1MemRead(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ComputerType) MemRead(addr uint16) byte {
|
||||||
|
return c.memory.MemRead(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ComputerType) MemWrite(addr uint16, val byte) {
|
||||||
|
c.memory.MemWrite(addr, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ComputerType) IORead(port uint16) byte {
|
||||||
|
return c.ioPorts[port]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ComputerType) IOWrite(port uint16, val byte) {
|
||||||
|
c.ioPorts[byte(port&0x00ff)] = val
|
||||||
|
switch byte(port & 0x00ff) {
|
||||||
|
case SYS_DD17PB:
|
||||||
|
|
||||||
|
if c.dd17EnableOut {
|
||||||
|
c.memory.Configure(val)
|
||||||
|
}
|
||||||
|
case SYS_DD17CTR:
|
||||||
|
c.dd17EnableOut = val == 0x80
|
||||||
|
case KBD_DD78CTR:
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New Builds new computer
|
||||||
|
func New(cfg config.OkEmuConfig) *ComputerType {
|
||||||
|
c := ComputerType{}
|
||||||
|
c.memory = Memory{}
|
||||||
|
c.memory.Init(cfg.MonitorFile, cfg.CPMFile)
|
||||||
|
|
||||||
|
c.cpu = *z80em.New(&c)
|
||||||
|
|
||||||
|
c.cycles = 0
|
||||||
|
c.dd17EnableOut = false
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ComputerType) Run() {
|
||||||
|
c.cpu.Reset()
|
||||||
|
for {
|
||||||
|
state := c.cpu.GetState()
|
||||||
|
log.Infof("%d - [%x]: %x\n", c.cycles, state.PC, c.MemRead(state.PC))
|
||||||
|
c.cycles += uint64(c.cpu.RunInstruction())
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
154
okean240/ioports.go
Normal file
154
okean240/ioports.go
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
package okean240
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ocean-240.2
|
||||||
|
* Computer with FDC variant.
|
||||||
|
* IO Ports definitions
|
||||||
|
*
|
||||||
|
* By Romych 2026-03-01
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* КР580ВВ55 DD79 USER PORT
|
||||||
|
*/
|
||||||
|
|
||||||
|
// USR_DD79PA User port A
|
||||||
|
const USR_DD79PA = 0x00
|
||||||
|
|
||||||
|
// USR_DD79PB User port B
|
||||||
|
const USR_DD79PB = 0x01
|
||||||
|
|
||||||
|
// USR_DD79PC User port C
|
||||||
|
const USR_DD79PC = 0x02
|
||||||
|
|
||||||
|
// USR_DD79CTR Config
|
||||||
|
const USR_DD79CTR = 0x03 // Config: [1][ma1,ma0][0-aO|1-aI],[0-chO,1-chI],[mb],[0-bO|1-bI],[0-clO,1-clI]
|
||||||
|
// Set bit: [0][xxx][bbb][0|1]
|
||||||
|
|
||||||
|
/*
|
||||||
|
* КР1818ВГ93 FDC Controller
|
||||||
|
*/
|
||||||
|
|
||||||
|
// FDC_CMD FDC Command
|
||||||
|
const FDC_CMD = 0x20
|
||||||
|
|
||||||
|
// FDC_TRACK FDC Track No
|
||||||
|
const FDC_TRACK = 0x21
|
||||||
|
|
||||||
|
// FDC_SECT FDC Sector
|
||||||
|
const FDC_SECT = 0x22
|
||||||
|
|
||||||
|
// FDC_DATA FDC Data
|
||||||
|
const FDC_DATA = 0x23
|
||||||
|
|
||||||
|
// FDC_WAIT FDC Wait
|
||||||
|
const FDC_WAIT = 0x24
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Floppy Controller port
|
||||||
|
*/
|
||||||
|
|
||||||
|
// FLOPPY Floppy Controller port
|
||||||
|
const FLOPPY = 0x25 // WR: 5-SSEN, 4-#DDEN, 3-INIT, 2-DRSEL, 1-MOT1, 0-MOT0
|
||||||
|
// RD: 7-MOTST, 6-SSEL, 5,4-x , 3-DRSEL, 2-MOT1, 1-MOT0, 0-INT
|
||||||
|
|
||||||
|
/*
|
||||||
|
* КР580ВВ55 DD78 Keyboard
|
||||||
|
*/
|
||||||
|
|
||||||
|
// KBD_DD78PA Port A - Keyboard Data
|
||||||
|
const KBD_DD78PA = 0x40
|
||||||
|
|
||||||
|
// KBD_DD78PB Port B - JST3,SHFT,CTRL,ACK,TAPE5,TAPE4,GK,GC
|
||||||
|
const KBD_DD78PB = 0x41
|
||||||
|
|
||||||
|
// KBD_DD78PC Port C - [PC7:5],[KBD_ACK],[PC3:0]
|
||||||
|
const KBD_DD78PC = 0x42
|
||||||
|
|
||||||
|
// KBD_DD78CTR Control port
|
||||||
|
const KBD_DD78CTR = 0x43 // Сonfig: [1][ma1,ma0][0-aO|1-aI],[0-chO,1-chI],[mb],[0-bO|1-bI],[0-clO,1-clI]
|
||||||
|
// Set bit: [0][xxx][bbb][0|1]
|
||||||
|
|
||||||
|
/*
|
||||||
|
* КР580ВИ53 DD70
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TMR_DD70C1 Timer counter 1
|
||||||
|
const TMR_DD70C1 = 0x60
|
||||||
|
|
||||||
|
// TMR_DD70C2 Timer counter 2
|
||||||
|
const TMR_DD70C2 = 0x61
|
||||||
|
|
||||||
|
// TMR_DD70C3 Timer counter 3
|
||||||
|
const TMR_DD70C3 = 0x62
|
||||||
|
|
||||||
|
/*
|
||||||
|
TMR_DD70CTR
|
||||||
|
Timer config: [sc1,sc0][rl1,rl0][m2,m1,m0][bcd]
|
||||||
|
sc - timer, rl=01-LSB, 10-MSB, 11-LSB+MSB
|
||||||
|
mode 000 - int on fin,
|
||||||
|
001 - one shot,
|
||||||
|
x10 - rate gen,
|
||||||
|
x11-sq wave
|
||||||
|
*/
|
||||||
|
const TMR_DD70CTR = 0x63
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Programmable Interrupt controller PIC KR580VV59
|
||||||
|
*/
|
||||||
|
|
||||||
|
// PIC_DD75RS RS Port
|
||||||
|
const PIC_DD75RS = 0x80
|
||||||
|
|
||||||
|
// PIC_DD75RM RM Port
|
||||||
|
const PIC_DD75RM = 0x81
|
||||||
|
|
||||||
|
/*
|
||||||
|
* КР580ВВ51 DD72
|
||||||
|
*/
|
||||||
|
|
||||||
|
// UART_DD72RD Serial data
|
||||||
|
const UART_DD72RD = 0xA0
|
||||||
|
|
||||||
|
// UART_DD72RR Serial status [RST,RQ_RX,RST_ERR,PAUSE,RX_EN,RX_RDY,TX_RDY]
|
||||||
|
const UART_DD72RR = 0xA1
|
||||||
|
|
||||||
|
/*
|
||||||
|
* КР580ВВ55 DD17 System port
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Port A - VShift[8..1] Vertical shift
|
||||||
|
const SYS_DD17PA = 0xC0
|
||||||
|
|
||||||
|
// Port B - Memory mapper [ROM14,13][REST][ENROM-][A18,17,16][32k]
|
||||||
|
const SYS_DD17PB = 0xC1
|
||||||
|
|
||||||
|
// Port C - HShift[HS5..1,SB3..1] Horisontal shift
|
||||||
|
const SYS_DD17PC = 0xC2
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SYS_DD17CTR
|
||||||
|
* Сonfig: [1][ma1,ma0][0-aO|1-aI],[0-chO,1-chI],[mb],[0-bO|1-bI],[0-clO,1-clI]
|
||||||
|
* Set bit: [0][xxx][bbb][0|1]
|
||||||
|
*/
|
||||||
|
const SYS_DD17CTR = 0xC3
|
||||||
|
|
||||||
|
/*
|
||||||
|
* КР580ВВ55 DD67
|
||||||
|
*/
|
||||||
|
|
||||||
|
// LPT_DD67PA Port A - Printer Data
|
||||||
|
const LPT_DD67PA = 0xE0
|
||||||
|
|
||||||
|
// VID_DD67PB Port B - Video control [VSU,C/M,FL3:1,COL3:1]
|
||||||
|
const VID_DD67PB = 0xE1
|
||||||
|
|
||||||
|
// DD67PC Port C - [USER3:1, STB-LP, BELL, TAPE3:1]
|
||||||
|
const DD67PC = 0xE2
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DD67CTR
|
||||||
|
* Сonfig: [1][ma1,ma0][0-aO|1-aI],[0-chO,1-chI],[mb],[0-bO|1-bI],[0-clO,1-clI]
|
||||||
|
* Set bit: [0][xxx][bbb][0|1]
|
||||||
|
*/
|
||||||
|
const DD67CTR = 0xE3
|
||||||
@ -24,21 +24,20 @@ const (
|
|||||||
WindowNo3
|
WindowNo3
|
||||||
)
|
)
|
||||||
|
|
||||||
type MemoryBlock struct {
|
type RamBlock struct {
|
||||||
id byte
|
|
||||||
memory [RamBlockSize]byte
|
memory [RamBlockSize]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type Memory struct {
|
type Memory struct {
|
||||||
allMemory [RamBlocks]*MemoryBlock
|
allMemory [RamBlocks]RamBlock
|
||||||
memoryWindow [RamWindows]*MemoryBlock
|
memoryWindow [RamWindows]RamBlock
|
||||||
rom0 MemoryBlock // monitor + monitor
|
rom0 RamBlock // monitor + monitor
|
||||||
rom1 MemoryBlock // cpm + monitor
|
rom1 RamBlock // cpm + monitor
|
||||||
config byte
|
config byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type MemoryInterface interface {
|
type MemoryInterface interface {
|
||||||
// Init - Initialize memory at "computer started"
|
// Init - Initialize memory at "computer start"
|
||||||
Init(rom0 string, rom1 string)
|
Init(rom0 string, rom1 string)
|
||||||
// Configure - Set memory configuration
|
// Configure - Set memory configuration
|
||||||
Configure(value byte)
|
Configure(value byte)
|
||||||
@ -53,18 +52,15 @@ type MemoryInterface interface {
|
|||||||
func (m *Memory) Init(monFile string, cmpFile string) {
|
func (m *Memory) Init(monFile string, cmpFile string) {
|
||||||
|
|
||||||
// empty RAM
|
// empty RAM
|
||||||
var id byte = 0
|
|
||||||
for block := range m.allMemory {
|
for block := range m.allMemory {
|
||||||
rb := MemoryBlock{}
|
rb := RamBlock{}
|
||||||
rb.id = id
|
|
||||||
id++
|
|
||||||
for addr := 0; addr < RamBlockSize; addr++ {
|
for addr := 0; addr < RamBlockSize; addr++ {
|
||||||
rb.memory[addr] = RamDefaultInitPattern
|
rb.memory[addr] = RamDefaultInitPattern
|
||||||
}
|
}
|
||||||
m.allMemory[block] = &rb
|
m.allMemory[block] = rb
|
||||||
}
|
}
|
||||||
|
|
||||||
// Command ROM files and init ROM0,1
|
// Load ROM files and init ROM0,1
|
||||||
// Read the entire file into a byte slice
|
// Read the entire file into a byte slice
|
||||||
rom0bin, err := os.ReadFile(monFile)
|
rom0bin, err := os.ReadFile(monFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -74,10 +70,8 @@ func (m *Memory) Init(monFile string, cmpFile string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
m.rom0 = MemoryBlock{}
|
m.rom0 = RamBlock{}
|
||||||
m.rom0.id = 0xF0
|
m.rom1 = RamBlock{}
|
||||||
m.rom1 = MemoryBlock{}
|
|
||||||
m.rom1.id = 0xF1
|
|
||||||
half := RamBlockSize / 2
|
half := RamBlockSize / 2
|
||||||
for i := 0; i < half; i++ {
|
for i := 0; i < half; i++ {
|
||||||
// mon+mon
|
// mon+mon
|
||||||
@ -98,7 +92,7 @@ func (m *Memory) Configure(value byte) {
|
|||||||
// RST bit set just after System RESET
|
// RST bit set just after System RESET
|
||||||
// All memoryWindow windows points to ROM0 (monitor)
|
// All memoryWindow windows points to ROM0 (monitor)
|
||||||
for i := 0; i < RamWindows; i++ {
|
for i := 0; i < RamWindows; i++ {
|
||||||
m.memoryWindow[i] = &m.rom0
|
m.memoryWindow[i] = m.rom0
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Map RAM blocks to windows
|
// Map RAM blocks to windows
|
||||||
@ -107,14 +101,14 @@ func (m *Memory) Configure(value byte) {
|
|||||||
m.memoryWindow[i] = m.allMemory[sp+i]
|
m.memoryWindow[i] = m.allMemory[sp+i]
|
||||||
}
|
}
|
||||||
// Map two hi windows to low windows in 32k flag set
|
// Map two hi windows to low windows in 32k flag set
|
||||||
if m.config&AccessHiBit == 1 {
|
if m.config&AccessHiBit == 0 {
|
||||||
m.memoryWindow[WindowNo0] = m.memoryWindow[WindowNo2]
|
m.memoryWindow[WindowNo0] = m.memoryWindow[WindowNo2]
|
||||||
m.memoryWindow[WindowNo1] = m.memoryWindow[WindowNo3]
|
m.memoryWindow[WindowNo1] = m.memoryWindow[WindowNo3]
|
||||||
}
|
}
|
||||||
// If ROM enabled, map ROM to last window
|
// If ROM enabled, map ROM to last window
|
||||||
if m.config&ROMDisBit == 0 {
|
if m.config&ROMDisBit == 0 {
|
||||||
// If ROM enabled, CP/M + Mon at window 3 [0xC000:0xFFFF]
|
// If ROM enabled, CP/M + Mon at window 3 [0xC000:0xFFFF]
|
||||||
m.memoryWindow[WindowNo3] = &m.rom1
|
m.memoryWindow[WindowNo3] = m.rom1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -128,21 +122,5 @@ func (m *Memory) MemRead(addr uint16) byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Memory) MemWrite(addr uint16, val byte) {
|
func (m *Memory) MemWrite(addr uint16, val byte) {
|
||||||
window := addr >> 14
|
m.memoryWindow[addr>>14].memory[addr&0x3fff] = val
|
||||||
offset := addr & 0x3fff
|
|
||||||
if m.memoryWindow[window].id < 0xF0 {
|
|
||||||
// write to RAM only
|
|
||||||
m.memoryWindow[window].memory[offset] = val
|
|
||||||
} else {
|
|
||||||
log.Debugf("Attempting to write 0x%02X=>ROM[0x%04X]=", val, addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MemoryWindows Return memory pages, mapped to memory windows
|
|
||||||
func (m *Memory) MemoryWindows() []byte {
|
|
||||||
var res []byte
|
|
||||||
for w := 0; w < RamWindows; w++ {
|
|
||||||
res = append(res, m.memoryWindow[w].id)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
19
okemu.yml
19
okemu.yml
@ -1,15 +1,4 @@
|
|||||||
logFile: okemu.log
|
logFile: "okemu.log"
|
||||||
logLevel: debug
|
logLevel: "info"
|
||||||
monitorFile: rom/MON_r8_9c6c6546.mon
|
monitorFile: "okean240/MON_r6.BIN"
|
||||||
cpmFile: rom/CPM_r8_bc0695e4.cpm
|
cpmFile: "okean240/CPM_r7.BIN"
|
||||||
fdc:
|
|
||||||
- autoLoad: true
|
|
||||||
autoSave: true
|
|
||||||
floppyFile: floppy/floppyB.okd
|
|
||||||
- autoLoad: false
|
|
||||||
autoSave: false
|
|
||||||
floppyFile: floppy/empty.okd
|
|
||||||
debugger:
|
|
||||||
enabled: true
|
|
||||||
host: localhost
|
|
||||||
port: 10001
|
|
||||||
|
|||||||
14
rebuild.sh
Executable file
14
rebuild.sh
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
echo "Build OkEmu"
|
||||||
|
export GOROOT=/home/roma/projects/go/1.25.7/
|
||||||
|
export PATH=/home/roma/projects/go/1.25.7/bin/:$PATH
|
||||||
|
|
||||||
|
version=$(git describe --tags HEAD)
|
||||||
|
#commit=$(git rev-parse HEAD)
|
||||||
|
timestamp=$(date +%Y-%m-%d' '%H:%M:%S)
|
||||||
|
|
||||||
|
go build -ldflags "-X 'main.Version=$version' -X 'main.BuildTime=$timestamp'" .
|
||||||
|
|
||||||
|
#echo "Copy to kubelogtst01"
|
||||||
|
#scp stash boykovra@kubelogtst01:
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,3 +0,0 @@
|
|||||||
# Эмулятор персонального компьютера Океан-240.2
|
|
||||||
|
|
||||||
Двоичные образы операционной системы и монитора, прошиваемые в ПЗУ компьютера.
|
|
||||||
BIN
src/Icon.png
BIN
src/Icon.png
Binary file not shown.
|
Before Width: | Height: | Size: 34 KiB |
@ -1,7 +0,0 @@
|
|||||||
# Эмулятор персонального компьютера Океан-240.2#
|
|
||||||
|
|
||||||
Исходники эмулятора на языке [GO](https://go.dev/).
|
|
||||||
|
|
||||||
Для графического интерфейса используется библиотека [Fyne](https://fyne.io/). Для сборки исходников, нужно её установить по инструкции к ней.
|
|
||||||
|
|
||||||
Для вычисления выражений, используется моя модификация библиотеки [gval](github.com/PaesslerAG/gval), исходные коды в папке [gval](gval/).
|
|
||||||
@ -1,131 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
const defaultMonitorFile = "rom/MON_v5.bin"
|
|
||||||
const defaultCPMFile = "rom/CPM_v5.bin"
|
|
||||||
const DefaultDebufPort = 10000
|
|
||||||
const confFile = "okemu.yml"
|
|
||||||
|
|
||||||
type OkEmuConfig struct {
|
|
||||||
LogFile string `yaml:"logFile"`
|
|
||||||
LogLevel string `yaml:"logLevel"`
|
|
||||||
MonitorFile string `yaml:"monitorFile"`
|
|
||||||
CPMFile string `yaml:"cpmFile"`
|
|
||||||
FDC []FDCConfig `yaml:"fdc"`
|
|
||||||
Debugger DebuggerConfig `yaml:"debugger"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FDCConfig struct {
|
|
||||||
AutoLoad bool `yaml:"autoLoad"`
|
|
||||||
AutoSave bool `yaml:"autoSave"`
|
|
||||||
FloppyFile string `yaml:"floppyFile"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DebuggerConfig struct {
|
|
||||||
Enabled bool `yaml:"enabled"`
|
|
||||||
Host string `yaml:"host"`
|
|
||||||
Port int `yaml:"port"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var config *OkEmuConfig
|
|
||||||
|
|
||||||
//func usage() {
|
|
||||||
// fmt.Printf("Use: %s --config <file_path>.yml\n", filepath.Base(os.Args[0]))
|
|
||||||
// os.Exit(2)
|
|
||||||
//}
|
|
||||||
|
|
||||||
func GetConfig() *OkEmuConfig {
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadConfig() {
|
|
||||||
//args := os.Args
|
|
||||||
//if len(args) != 3 {
|
|
||||||
// usage()
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//if args[1] != "--config" {
|
|
||||||
// usage()
|
|
||||||
//}
|
|
||||||
// confFile := args[2]
|
|
||||||
|
|
||||||
conf := OkEmuConfig{}
|
|
||||||
data, err := os.ReadFile(confFile)
|
|
||||||
if err == nil {
|
|
||||||
err := yaml.Unmarshal(data, &conf)
|
|
||||||
if err != nil {
|
|
||||||
log.Panicf("Config file error: %v", err)
|
|
||||||
}
|
|
||||||
setDefaultConf(&conf)
|
|
||||||
checkConfig(&conf)
|
|
||||||
} else {
|
|
||||||
log.Panicf("Can not open config file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config = &conf
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkConfig(conf *OkEmuConfig) {
|
|
||||||
if conf.Debugger.Host == "" {
|
|
||||||
conf.Debugger.Host = "localhost"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setDefaultConf(conf *OkEmuConfig) {
|
|
||||||
if conf.LogLevel == "" {
|
|
||||||
conf.LogLevel = "info"
|
|
||||||
}
|
|
||||||
if conf.LogFile == "" {
|
|
||||||
conf.LogFile = "okemu.log"
|
|
||||||
}
|
|
||||||
if conf.MonitorFile == "" {
|
|
||||||
conf.MonitorFile = defaultMonitorFile
|
|
||||||
}
|
|
||||||
if conf.CPMFile == "" {
|
|
||||||
conf.CPMFile = defaultCPMFile
|
|
||||||
}
|
|
||||||
if conf.Debugger.Port < 80 || conf.Debugger.Port > 65535 {
|
|
||||||
log.Infof("Port %d incorrect, using default: %d", conf.Debugger.Port, DefaultDebufPort)
|
|
||||||
conf.Debugger.Port = DefaultDebufPort
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *OkEmuConfig) Clone() *OkEmuConfig {
|
|
||||||
|
|
||||||
fds := make([]FDCConfig, 2)
|
|
||||||
for n, fd := range c.FDC {
|
|
||||||
fds[n].FloppyFile = fd.FloppyFile
|
|
||||||
fds[n].AutoLoad = fd.AutoLoad
|
|
||||||
fds[n].AutoSave = fd.AutoSave
|
|
||||||
}
|
|
||||||
|
|
||||||
return &OkEmuConfig{
|
|
||||||
LogFile: c.LogFile,
|
|
||||||
LogLevel: c.LogLevel,
|
|
||||||
MonitorFile: c.MonitorFile,
|
|
||||||
CPMFile: c.CPMFile,
|
|
||||||
FDC: fds,
|
|
||||||
Debugger: DebuggerConfig{
|
|
||||||
Enabled: c.Debugger.Enabled,
|
|
||||||
Host: c.Debugger.Host,
|
|
||||||
Port: c.Debugger.Port,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *OkEmuConfig) Save() {
|
|
||||||
data, err := yaml.Marshal(c)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("config error: %v", err)
|
|
||||||
}
|
|
||||||
err = os.WriteFile(confFile, data, 0600)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("save config file: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,369 +0,0 @@
|
|||||||
package breakpoint
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"okemu/gval"
|
|
||||||
//"okemu/okean240"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
const MaxBreakpoints = 16383
|
|
||||||
const BpTmp1 = MaxBreakpoints + 1
|
|
||||||
const BpTmp2 = MaxBreakpoints + 2
|
|
||||||
|
|
||||||
const (
|
|
||||||
BPTypeSimplePC = iota // Simple PC=nn breakpoint
|
|
||||||
BPTypeSimpleSP // Simple SP>=nn breakpoint
|
|
||||||
BPTypeExpression // Complex expression breakpoint
|
|
||||||
)
|
|
||||||
|
|
||||||
type Breakpoint struct {
|
|
||||||
addr uint16
|
|
||||||
cond string
|
|
||||||
eval gval.Evaluable
|
|
||||||
bpType int
|
|
||||||
pass uint16
|
|
||||||
passCount uint16
|
|
||||||
enabled bool
|
|
||||||
mBank uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
var andMatch = regexp.MustCompile(`\s+AND\s+`)
|
|
||||||
var orMatch = regexp.MustCompile(`\s+OR\s+`)
|
|
||||||
|
|
||||||
// var xorMatch = regexp.MustCompile(`\s+XOR\s+`)
|
|
||||||
var hexHMatch = regexp.MustCompile(`[[:xdigit:]]+H`)
|
|
||||||
var eqMatch = regexp.MustCompile(`[^=><!]=[^=]`)
|
|
||||||
|
|
||||||
func patchExpression(expr string) string {
|
|
||||||
ex := strings.ToUpper(expr)
|
|
||||||
ex = andMatch.ReplaceAllString(ex, " && ")
|
|
||||||
ex = orMatch.ReplaceAllString(ex, " || ")
|
|
||||||
// ex = xorMatch.ReplaceAllString(ex, " ^ ")
|
|
||||||
for {
|
|
||||||
pos := hexHMatch.FindStringIndex(ex)
|
|
||||||
if pos != nil && len(pos) == 2 {
|
|
||||||
hex := "0x" + ex[pos[0]:pos[1]-1]
|
|
||||||
ex = ex[:pos[0]] + hex + ex[pos[1]:]
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
pos := eqMatch.FindStringIndex(ex)
|
|
||||||
if pos != nil && len(pos) == 2 {
|
|
||||||
ex = ex[:pos[0]+1] + "==" + ex[pos[1]-1:]
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ex = strings.ReplaceAll(ex, "NOT", "!")
|
|
||||||
ex = strings.ReplaceAll(ex, "<>", "!=")
|
|
||||||
return ex
|
|
||||||
}
|
|
||||||
|
|
||||||
var pcMatch = regexp.MustCompile(`^PC=[[:xdigit:]]+h$`)
|
|
||||||
var spMatch = regexp.MustCompile(`^SP>=[[:xdigit:]]+$`)
|
|
||||||
|
|
||||||
func getSecondUint16(param string, sep string) (uint16, error) {
|
|
||||||
p := strings.Split(param, sep)
|
|
||||||
v := p[1]
|
|
||||||
base := 0
|
|
||||||
if strings.HasSuffix(v, "h") || strings.HasSuffix(v, "H") {
|
|
||||||
v = strings.TrimSuffix(v, "H")
|
|
||||||
v = strings.TrimSuffix(v, "h")
|
|
||||||
base = 16
|
|
||||||
}
|
|
||||||
a, e := strconv.ParseUint(v, base, 16)
|
|
||||||
if e != nil {
|
|
||||||
return 0, e
|
|
||||||
}
|
|
||||||
return uint16(a), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBreakpoint(expr string, mBank uint8) (*Breakpoint, error) {
|
|
||||||
bp := Breakpoint{
|
|
||||||
addr: 0,
|
|
||||||
enabled: false,
|
|
||||||
passCount: 0,
|
|
||||||
pass: 0,
|
|
||||||
bpType: BPTypeSimplePC,
|
|
||||||
mBank: mBank,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if BP is simple PC=addr
|
|
||||||
expr = strings.TrimSpace(expr)
|
|
||||||
bp.cond = expr
|
|
||||||
pcMatched := pcMatch.MatchString(expr)
|
|
||||||
spMatched := spMatch.MatchString(expr)
|
|
||||||
|
|
||||||
if pcMatched {
|
|
||||||
// PC=xxxxh
|
|
||||||
bp.bpType = BPTypeSimplePC
|
|
||||||
v, e := getSecondUint16(expr, "=")
|
|
||||||
if e != nil {
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
bp.addr = v
|
|
||||||
} else if spMatched {
|
|
||||||
// SP>=xxxx
|
|
||||||
bp.bpType = BPTypeSimpleSP
|
|
||||||
v, e := getSecondUint16(expr, "=")
|
|
||||||
if e != nil {
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
bp.addr = v
|
|
||||||
} else {
|
|
||||||
// complex expression
|
|
||||||
bp.bpType = BPTypeExpression
|
|
||||||
ex := patchExpression(expr)
|
|
||||||
log.Debugf("Original Expression: '%s'", expr)
|
|
||||||
log.Debugf(" Patched Expression: '%s'", ex)
|
|
||||||
err := bp.SetExpression(ex)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &bp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Breakpoint) Enabled() bool {
|
|
||||||
return b.enabled
|
|
||||||
}
|
|
||||||
func (b *Breakpoint) SetEnabled(enabled bool) {
|
|
||||||
b.enabled = enabled
|
|
||||||
}
|
|
||||||
func (b *Breakpoint) PassCount() uint16 {
|
|
||||||
return b.passCount
|
|
||||||
}
|
|
||||||
func (b *Breakpoint) SetPassCount(passCount uint16) {
|
|
||||||
b.passCount = passCount
|
|
||||||
}
|
|
||||||
func (b *Breakpoint) Pass() uint16 {
|
|
||||||
return b.pass
|
|
||||||
}
|
|
||||||
func (b *Breakpoint) SetPass(pass uint16) {
|
|
||||||
b.pass = pass
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Breakpoint) IncPass() {
|
|
||||||
b.pass++
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Breakpoint) Addr() uint16 {
|
|
||||||
return b.addr
|
|
||||||
}
|
|
||||||
func (b *Breakpoint) SetAddr(addr uint16) {
|
|
||||||
b.addr = addr
|
|
||||||
}
|
|
||||||
func (b *Breakpoint) Type() int {
|
|
||||||
return b.bpType
|
|
||||||
}
|
|
||||||
func (b *Breakpoint) SetType(bpType int) {
|
|
||||||
b.bpType = bpType
|
|
||||||
}
|
|
||||||
|
|
||||||
func cvtToUint16(v interface{}) (uint16, error) {
|
|
||||||
switch value := v.(type) {
|
|
||||||
case int:
|
|
||||||
return uint16(value), nil
|
|
||||||
case int8:
|
|
||||||
return uint16(value), nil
|
|
||||||
case int16:
|
|
||||||
return uint16(value), nil
|
|
||||||
case int32:
|
|
||||||
return uint16(value), nil
|
|
||||||
case int64:
|
|
||||||
return uint16(value), nil
|
|
||||||
case uint:
|
|
||||||
return uint16(value), nil
|
|
||||||
case uint8:
|
|
||||||
return uint16(value), nil
|
|
||||||
case uint16:
|
|
||||||
return value, nil
|
|
||||||
case uint32:
|
|
||||||
return uint16(value), nil
|
|
||||||
case uint64:
|
|
||||||
return uint16(value), nil
|
|
||||||
default:
|
|
||||||
// log.Errorf("Unknown type of %v", value)
|
|
||||||
return 0, fmt.Errorf("error: unsupported type of %v", value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getUint16(name string, ctx map[string]interface{}) uint16 {
|
|
||||||
if v, ok := ctx[name]; ok {
|
|
||||||
if v == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
// most frequent case
|
|
||||||
if v, ok := v.(uint16); ok {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
// for less frequent cases
|
|
||||||
rv, e := cvtToUint16(v)
|
|
||||||
if e != nil {
|
|
||||||
log.Errorf("error: variable %s have unsupported type: %v", name, v)
|
|
||||||
}
|
|
||||||
return rv
|
|
||||||
}
|
|
||||||
log.Errorf("Variable %s not found in context!", name)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Breakpoint) Hit(parameters map[string]interface{}) bool {
|
|
||||||
if !b.enabled {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if b.bpType == BPTypeSimplePC {
|
|
||||||
pc := getUint16("PC", parameters)
|
|
||||||
if pc == b.addr {
|
|
||||||
log.Debugf("Breakpoint Hit PC=0x%04X", b.addr)
|
|
||||||
}
|
|
||||||
return pc == b.addr
|
|
||||||
} else if b.bpType == BPTypeSimpleSP {
|
|
||||||
sp := getUint16("SP", parameters)
|
|
||||||
if sp >= b.addr {
|
|
||||||
log.Debugf("Breakpoint Hit SP>=0x%04X", b.addr)
|
|
||||||
}
|
|
||||||
return sp >= b.addr
|
|
||||||
}
|
|
||||||
bc := context.WithValue(context.Background(), "MEM", parameters["MEM"])
|
|
||||||
bc = context.WithValue(bc, "IO", parameters["IO"])
|
|
||||||
value, err := b.eval.EvalBool(bc, parameters)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
var language gval.Language
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
language = gval.NewLanguage(gval.Base(), gval.Arithmetic(), gval.Bitmask(), gval.PropositionalLogic(),
|
|
||||||
gval.Function("PEEKW", CfPeekW),
|
|
||||||
gval.Function("PEEK", CfPeek),
|
|
||||||
gval.Function("BYTE", CfByte),
|
|
||||||
gval.Function("WORD", CfWord),
|
|
||||||
gval.Function("ABS", CfAbs),
|
|
||||||
gval.Function("IN", CfIn),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Breakpoint) SetExpression(expression string) error {
|
|
||||||
var err error
|
|
||||||
b.eval, err = language.NewEvaluable(expression)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Illegal expression", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.bpType = BPTypeExpression
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Breakpoint) Expression() string {
|
|
||||||
return b.cond
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Breakpoint) MBank() uint8 {
|
|
||||||
return b.mBank
|
|
||||||
}
|
|
||||||
|
|
||||||
func CfPeekW(ctx context.Context, arguments ...interface{}) (uint16, error) {
|
|
||||||
if len(arguments) != 1 {
|
|
||||||
return 0, fmt.Errorf("expected 1 argument")
|
|
||||||
}
|
|
||||||
addr, e := cvtToUint16(arguments[0])
|
|
||||||
if e != nil {
|
|
||||||
return 0, fmt.Errorf("error, illegal address: %v", e)
|
|
||||||
}
|
|
||||||
memInt := ctx.Value("MEM")
|
|
||||||
memRead, ok := memInt.(func(uint16) uint8)
|
|
||||||
if !ok {
|
|
||||||
return 0, fmt.Errorf("MEM must be func(uint16) uint8")
|
|
||||||
}
|
|
||||||
|
|
||||||
r := uint16(memRead(addr+1))<<8 | uint16(memRead(addr))
|
|
||||||
log.Debugf("PeekW[0x%04X]=0x%04X", addr, r)
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CfPeek(ctx context.Context, arguments ...interface{}) (uint16, error) {
|
|
||||||
result, err := CfPeekW(ctx, arguments...)
|
|
||||||
return result & 0x00ff, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func CfIn(ctx context.Context, arguments ...interface{}) (uint16, error) {
|
|
||||||
if len(arguments) != 1 {
|
|
||||||
return 0, fmt.Errorf("expected 1 argument")
|
|
||||||
}
|
|
||||||
addr, e := cvtToUint16(arguments[0])
|
|
||||||
if e != nil {
|
|
||||||
return 0, fmt.Errorf("error, illegal address: %v", e)
|
|
||||||
}
|
|
||||||
ioInt := ctx.Value("IO")
|
|
||||||
ioRead, ok := ioInt.(func(uint16) uint8)
|
|
||||||
if !ok {
|
|
||||||
return 0, fmt.Errorf("IO must be func(uint16) uint8")
|
|
||||||
}
|
|
||||||
|
|
||||||
r := uint16(ioRead(addr))
|
|
||||||
log.Debugf("IO[0x%04X]=0x%02X", addr, r)
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CfByte(ctx context.Context, arguments ...interface{}) (uint8, error) {
|
|
||||||
if len(arguments) != 1 {
|
|
||||||
return 0, fmt.Errorf("expected 1 argument")
|
|
||||||
}
|
|
||||||
val, e := cvtToUint16(arguments[0])
|
|
||||||
if e != nil {
|
|
||||||
return 0, fmt.Errorf("error, illegal value: %v", e)
|
|
||||||
}
|
|
||||||
return uint8(val), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CfWord(ctx context.Context, arguments ...interface{}) (uint16, error) {
|
|
||||||
if len(arguments) != 1 {
|
|
||||||
return 0, fmt.Errorf("expected 1 argument")
|
|
||||||
}
|
|
||||||
val, e := cvtToUint16(arguments[0])
|
|
||||||
if e != nil {
|
|
||||||
return 0, fmt.Errorf("error, illegal value: %v", e)
|
|
||||||
}
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CfAbs(ctx context.Context, arguments ...interface{}) (uint16, error) {
|
|
||||||
if len(arguments) != 1 {
|
|
||||||
return 0, fmt.Errorf("expected 1 argument")
|
|
||||||
}
|
|
||||||
val, e := cvtToUint16(arguments[0])
|
|
||||||
if e != nil {
|
|
||||||
return 0, fmt.Errorf("error, illegal value: %v", e)
|
|
||||||
}
|
|
||||||
if val < 256 {
|
|
||||||
// byte
|
|
||||||
i8 := int8(val)
|
|
||||||
if i8 < 0 {
|
|
||||||
return uint16(-i8), nil
|
|
||||||
}
|
|
||||||
return uint16(i8), nil
|
|
||||||
}
|
|
||||||
i16 := int16(val)
|
|
||||||
if i16 < 0 {
|
|
||||||
return uint16(-i16), nil
|
|
||||||
}
|
|
||||||
return uint16(i16), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//"IN(e): returns the byte at port e\n" \
|
|
||||||
//"NOT(e): negates expression e: if it's 0, returns 1. Otherwhise, return 0\n" \
|
|
||||||
//"ABS(e): returns absolute value of expression e\n" \
|
|
||||||
//"BYTE(e): same as (e)&FFH\n" \
|
|
||||||
//"WORD(e): same as (e)&FFFFH\n" \
|
|
||||||
@ -1,87 +0,0 @@
|
|||||||
package breakpoint
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
const expr1 = "PC=00100h and (NOT(B > 10)) and a != 5"
|
|
||||||
const expr2 = "PC=00115h"
|
|
||||||
const expr3 = "SP>=1332"
|
|
||||||
|
|
||||||
var ctx = map[string]interface{}{
|
|
||||||
"PC": 0x100,
|
|
||||||
"A": 0x55,
|
|
||||||
"B": 5,
|
|
||||||
"SP": 0x200,
|
|
||||||
}
|
|
||||||
|
|
||||||
const exprRep = "PC=00115h and (B=5 or BC = 5)"
|
|
||||||
const exprDst = "PC==0x00115 && (B==5 || BC == 5)"
|
|
||||||
const exprFn = "PC=PEEKW(SP-2) AND SP>=100"
|
|
||||||
|
|
||||||
func Test_PatchExpression(t *testing.T) {
|
|
||||||
ex := patchExpression(exprRep)
|
|
||||||
if ex != exprDst {
|
|
||||||
t.Errorf("Patched expression does not match\n got: %s\nexpected: %s", ex, exprDst)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_ComplexExpr(t *testing.T) {
|
|
||||||
|
|
||||||
b, e := NewBreakpoint(expr1, 1)
|
|
||||||
//e := b.SetExpression(exp1)
|
|
||||||
if e != nil {
|
|
||||||
t.Error(e)
|
|
||||||
} else if b != nil {
|
|
||||||
b.enabled = true
|
|
||||||
if !b.Hit(ctx) {
|
|
||||||
t.Errorf("Breakpoint not hit")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const expSimplePC = "PC=00119h"
|
|
||||||
|
|
||||||
func Test_BPSetPC(t *testing.T) {
|
|
||||||
b, e := NewBreakpoint(expSimplePC, 1)
|
|
||||||
if e != nil {
|
|
||||||
t.Error(e)
|
|
||||||
} else if b != nil {
|
|
||||||
if b.bpType != BPTypeSimplePC {
|
|
||||||
t.Errorf("Breakpoint type does not match BPTypeSimplePC")
|
|
||||||
}
|
|
||||||
b.enabled = true
|
|
||||||
if b.Hit(ctx) {
|
|
||||||
t.Errorf("Breakpoint hit but will not!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_MatchSP(t *testing.T) {
|
|
||||||
|
|
||||||
pcMatch := regexp.MustCompile(`SP>=[[:xdigit:]]+$`)
|
|
||||||
matched := pcMatch.MatchString(expr3)
|
|
||||||
if !matched {
|
|
||||||
t.Errorf("SP>=XXXXh not matched")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_GetCtx(t *testing.T) {
|
|
||||||
pc := getUint16("PC", ctx)
|
|
||||||
if pc != 0x100 {
|
|
||||||
t.Errorf("PC value not found in context")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_PeekWFn(t *testing.T) {
|
|
||||||
b, e := NewBreakpoint(exprFn, 1)
|
|
||||||
if e != nil {
|
|
||||||
t.Error(e)
|
|
||||||
}
|
|
||||||
b.enabled = true
|
|
||||||
b.Hit(ctx)
|
|
||||||
}
|
|
||||||
@ -1,305 +0,0 @@
|
|||||||
package debug
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"okemu/debug/breakpoint"
|
|
||||||
|
|
||||||
"github.com/romychs/z80go"
|
|
||||||
"github.com/romychs/z80go/dis"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
const BPMemAccess = 65535
|
|
||||||
|
|
||||||
type Debugger struct {
|
|
||||||
stepMode bool
|
|
||||||
doStep bool
|
|
||||||
runMode bool
|
|
||||||
runInst uint64
|
|
||||||
breakpointsEnabled bool
|
|
||||||
breakpoints map[uint16]*breakpoint.Breakpoint
|
|
||||||
cpuFrequency uint32
|
|
||||||
disassembler *dis.Disassembler
|
|
||||||
cpuHistoryEnabled bool
|
|
||||||
cpuHistoryStarted bool
|
|
||||||
cpuHistoryMaxSize int
|
|
||||||
cpuHistory []*z80go.CPU
|
|
||||||
memBreakpoints [65536]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDebugger() *Debugger {
|
|
||||||
d := Debugger{
|
|
||||||
stepMode: false,
|
|
||||||
doStep: false,
|
|
||||||
runMode: false,
|
|
||||||
runInst: 0,
|
|
||||||
breakpointsEnabled: false,
|
|
||||||
breakpoints: map[uint16]*breakpoint.Breakpoint{},
|
|
||||||
cpuHistoryEnabled: false,
|
|
||||||
cpuHistoryStarted: false,
|
|
||||||
cpuHistoryMaxSize: 0,
|
|
||||||
cpuHistory: []*z80go.CPU{},
|
|
||||||
}
|
|
||||||
return &d
|
|
||||||
}
|
|
||||||
|
|
||||||
type DEZOG interface {
|
|
||||||
SetupTcpHandler()
|
|
||||||
BreakpointHit(number uint16, typ byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) SetStepMode(step bool) {
|
|
||||||
d.SetRunMode(false)
|
|
||||||
d.stepMode = step
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) SetRunMode(run bool) {
|
|
||||||
if run {
|
|
||||||
d.runInst = 0
|
|
||||||
}
|
|
||||||
d.runMode = run
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) RunMode() bool {
|
|
||||||
return d.runMode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) DoStep() bool {
|
|
||||||
if d.doStep {
|
|
||||||
d.doStep = false
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) SetCpuHistoryEnabled(enable bool) {
|
|
||||||
d.cpuHistoryEnabled = enable
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) SetCpuHistoryMaxSize(size int) {
|
|
||||||
if size < 0 || size > 1_000_000 {
|
|
||||||
log.Error("CPU history max size must be positive and up to 1M")
|
|
||||||
} else {
|
|
||||||
d.cpuHistoryMaxSize = size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) CpuHistoryClear() {
|
|
||||||
d.cpuHistory = make([]*z80go.CPU, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) CpuHistorySize() int {
|
|
||||||
return len(d.cpuHistory)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) CpuHistory(index int) *z80go.CPU {
|
|
||||||
if index >= 0 && index < len(d.cpuHistory) {
|
|
||||||
return d.cpuHistory[index]
|
|
||||||
}
|
|
||||||
if len(d.cpuHistory) > 0 {
|
|
||||||
log.Warnf("CPU history index %d out of range [0:%d]", index, len(d.cpuHistory)-1)
|
|
||||||
} else {
|
|
||||||
log.Warn("CPU history is empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) SetCpuHistoryStarted(started bool) {
|
|
||||||
d.cpuHistoryStarted = started
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) SaveHistory(state *z80go.CPU) {
|
|
||||||
if d.cpuHistoryEnabled && d.cpuHistoryMaxSize > 0 && d.cpuHistoryStarted {
|
|
||||||
d.cpuHistory = append([]*z80go.CPU{state}, d.cpuHistory...)
|
|
||||||
if len(d.cpuHistory) > d.cpuHistoryMaxSize {
|
|
||||||
d.cpuHistory = d.cpuHistory[0 : d.cpuHistoryMaxSize-1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) CheckBreakpoints(ctx map[string]interface{}) (bool, uint16) {
|
|
||||||
if d.breakpointsEnabled && d.runMode {
|
|
||||||
for n, bp := range d.breakpoints {
|
|
||||||
if bp != nil && bp.Hit(ctx) {
|
|
||||||
// breakpoint hit
|
|
||||||
if bp.Pass() >= bp.PassCount() {
|
|
||||||
bp.SetPass(0)
|
|
||||||
d.runMode = false
|
|
||||||
return true, n
|
|
||||||
}
|
|
||||||
// increment breakpoint pass count
|
|
||||||
bp.IncPass()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) SetBreakpointsEnabled(enabled bool) {
|
|
||||||
d.breakpointsEnabled = enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) BreakpointsEnabled() bool {
|
|
||||||
return d.breakpointsEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBreakpoint Create new breakpoint with specified number
|
|
||||||
func (d *Debugger) SetBreakpoint(number uint16, exp string, mBank uint8) error {
|
|
||||||
var err error
|
|
||||||
bp, err := breakpoint.NewBreakpoint(exp, mBank)
|
|
||||||
if err == nil && bp != nil {
|
|
||||||
d.breakpoints[number] = bp
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) AddBreakpoint(exp string, mBank uint8) (uint16, error) {
|
|
||||||
var err error
|
|
||||||
bpNo := d.GetBreakpointNum()
|
|
||||||
if bpNo < breakpoint.MaxBreakpoints {
|
|
||||||
bp, err := breakpoint.NewBreakpoint(exp, mBank)
|
|
||||||
if err == nil && bp != nil {
|
|
||||||
bp.SetEnabled(true)
|
|
||||||
d.breakpoints[bpNo] = bp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bpNo, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) GetBreakpointNum() uint16 {
|
|
||||||
num := uint16(1)
|
|
||||||
for no, bp := range d.breakpoints {
|
|
||||||
if bp != nil && no < breakpoint.MaxBreakpoints && num <= no {
|
|
||||||
num = no + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return num
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) SetBreakpointPassCount(number uint16, count uint16) {
|
|
||||||
bp, ok := d.breakpoints[number]
|
|
||||||
if ok && bp != nil {
|
|
||||||
bp.SetPass(0)
|
|
||||||
bp.SetPassCount(count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) SetBreakpointEnabled(number uint16, enabled bool) {
|
|
||||||
bp, ok := d.breakpoints[number]
|
|
||||||
if ok && bp != nil {
|
|
||||||
bp.SetEnabled(enabled)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) BreakpointEnabled(number uint16) bool {
|
|
||||||
bp, ok := d.breakpoints[number]
|
|
||||||
if ok && bp != nil {
|
|
||||||
return bp.Enabled()
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) BreakpointMBank(number uint16) uint8 {
|
|
||||||
bp, ok := d.breakpoints[number]
|
|
||||||
if ok && bp != nil {
|
|
||||||
return bp.MBank()
|
|
||||||
}
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) ClearMemBreakpoints() {
|
|
||||||
for c := 0; c < 65536; c++ {
|
|
||||||
d.memBreakpoints[c] = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) StepMode() bool {
|
|
||||||
return d.stepMode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) SetDoStep(on bool) {
|
|
||||||
d.doStep = on
|
|
||||||
}
|
|
||||||
|
|
||||||
// BPExpression Return requested breakpoint
|
|
||||||
func (d *Debugger) BPExpression(number uint16) string {
|
|
||||||
bp, ok := d.breakpoints[number]
|
|
||||||
if ok && bp != nil {
|
|
||||||
return bp.Expression()
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunInst return and increment count of instructions executed
|
|
||||||
func (d *Debugger) RunInst() uint64 {
|
|
||||||
v := d.runInst
|
|
||||||
d.runInst++
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) SetMemBreakpoint(address uint16, typ byte, size uint16) {
|
|
||||||
var offset uint16
|
|
||||||
for offset = address; offset < address+size; offset++ {
|
|
||||||
d.memBreakpoints[offset] = typ
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) RemoveBreakpoint(number uint16) {
|
|
||||||
delete(d.breakpoints, number)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) CheckMemBreakpoints(accessMap *map[uint16]byte) (bool, uint16, byte) {
|
|
||||||
if !d.breakpointsEnabled {
|
|
||||||
return false, 0, 0
|
|
||||||
}
|
|
||||||
for addr, typ := range *accessMap {
|
|
||||||
bp := d.memBreakpoints[addr]
|
|
||||||
if bp == 0 {
|
|
||||||
return false, addr, 0
|
|
||||||
}
|
|
||||||
if (bp == 3) || bp == typ {
|
|
||||||
d.SetRunMode(false)
|
|
||||||
return true, addr, typ
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, 0, 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) ClearBreakpoints() {
|
|
||||||
clear(d.breakpoints)
|
|
||||||
}
|
|
||||||
|
|
||||||
type MemBP struct {
|
|
||||||
addr uint16
|
|
||||||
size uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Debugger) GetMemBreakpoints() []MemBP {
|
|
||||||
var res []MemBP
|
|
||||||
a := uint16(0)
|
|
||||||
s := uint16(0)
|
|
||||||
isBp := false
|
|
||||||
for addr := 0; addr < 65536; addr++ {
|
|
||||||
if d.memBreakpoints[addr] > 0 {
|
|
||||||
if !isBp {
|
|
||||||
isBp = true
|
|
||||||
a = uint16(addr)
|
|
||||||
}
|
|
||||||
s++
|
|
||||||
if addr == 65535 {
|
|
||||||
res = append(res, MemBP{addr: a, size: s})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if isBp {
|
|
||||||
isBp = false
|
|
||||||
res = append(res, MemBP{addr: a, size: s})
|
|
||||||
s = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MemBP) String() string {
|
|
||||||
return fmt.Sprintf("%04XH : %d", m.addr, m.size)
|
|
||||||
}
|
|
||||||
@ -1,556 +0,0 @@
|
|||||||
package dzrp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"okemu/config"
|
|
||||||
"okemu/debug"
|
|
||||||
"okemu/debug/breakpoint"
|
|
||||||
"okemu/okean240"
|
|
||||||
"okemu/z80/dis"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DZRP struct {
|
|
||||||
port string
|
|
||||||
config *config.OkEmuConfig
|
|
||||||
debugger *debug.Debugger
|
|
||||||
disassembler *dis.Disassembler
|
|
||||||
computer *okean240.ComputerType
|
|
||||||
conn net.Conn
|
|
||||||
reader *bufio.Reader
|
|
||||||
writer *bufio.Writer
|
|
||||||
cmd *Command
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetupTcpHandler Setup TCP listener, handle connections
|
|
||||||
|
|
||||||
func NewDZRP(config *config.OkEmuConfig, debug *debug.Debugger, dissasm *dis.Disassembler, comp *okean240.ComputerType) *DZRP {
|
|
||||||
return &DZRP{
|
|
||||||
port: config.Debugger.Host + ":" + strconv.Itoa(config.Debugger.Port),
|
|
||||||
debugger: debug,
|
|
||||||
disassembler: dissasm,
|
|
||||||
computer: comp,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DZRP) SetupTcpHandler() {
|
|
||||||
|
|
||||||
var err error
|
|
||||||
var l net.Listener
|
|
||||||
|
|
||||||
l, err = net.Listen("tcp4", p.port)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func(l net.Listener) {
|
|
||||||
err := l.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Error closing listener connection %v", err)
|
|
||||||
}
|
|
||||||
}(l)
|
|
||||||
|
|
||||||
log.Infof("Ready for debugger connections on %s", p.port)
|
|
||||||
for {
|
|
||||||
p.conn, err = l.Accept()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Accept connection: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
go p.handleConnection()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Receive messages, split to strings and parse
|
|
||||||
func (p *DZRP) handleConnection() {
|
|
||||||
p.reader = bufio.NewReader(p.conn)
|
|
||||||
p.writer = bufio.NewWriter(p.conn)
|
|
||||||
var command Command
|
|
||||||
n := 0
|
|
||||||
for {
|
|
||||||
// receive command packet byte by byte
|
|
||||||
b, err := p.reader.ReadByte()
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
log.Errorf("TCP error: %v", err)
|
|
||||||
p.debugger.SetStepMode(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch n {
|
|
||||||
case 0:
|
|
||||||
command.Len = uint32(b)
|
|
||||||
case 1:
|
|
||||||
command.Len |= uint32(b) << 8
|
|
||||||
case 2:
|
|
||||||
command.Len |= uint32(b) << 16
|
|
||||||
case 3:
|
|
||||||
command.Len |= uint32(b) << 24
|
|
||||||
case 4:
|
|
||||||
command.Sn = b
|
|
||||||
case 5:
|
|
||||||
command.Id = b
|
|
||||||
default:
|
|
||||||
command.Payload = append(command.Payload, b)
|
|
||||||
}
|
|
||||||
if n >= 5 && int(command.Len) == len(command.Payload) {
|
|
||||||
p.cmd = &command
|
|
||||||
if p.handleCommand() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
command.Len = 0
|
|
||||||
command.Payload = []uint8{}
|
|
||||||
n = 0
|
|
||||||
} else {
|
|
||||||
n++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Debug("Closing connection")
|
|
||||||
|
|
||||||
p.debugger.SetStepMode(false)
|
|
||||||
_ = p.writer.Flush()
|
|
||||||
p.writer = nil
|
|
||||||
p.reader = nil
|
|
||||||
err := p.conn.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Can not close TCP socket: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeResponse Write response to command back to client
|
|
||||||
func (p *DZRP) writeResponse(response *Response) error {
|
|
||||||
// log.Infof("Ready for debugger connections on %s", port)
|
|
||||||
// Send Len
|
|
||||||
l := response.Len
|
|
||||||
for c := 0; c < 4; c++ {
|
|
||||||
e := p.writer.WriteByte(byte(l))
|
|
||||||
if e != nil {
|
|
||||||
log.Warnf("Error writing Len: %v", e)
|
|
||||||
}
|
|
||||||
l = l >> 8
|
|
||||||
}
|
|
||||||
// Send Sn
|
|
||||||
e := p.writer.WriteByte(response.Sn)
|
|
||||||
if e != nil {
|
|
||||||
log.Warnf("Error writing Sn: %v", e)
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
// Send Payload
|
|
||||||
for _, b := range response.Payload {
|
|
||||||
e := p.writer.WriteByte(b)
|
|
||||||
if e != nil {
|
|
||||||
log.Warnf("Error writing payload: %v", e)
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
e = p.writer.Flush()
|
|
||||||
if e != nil {
|
|
||||||
log.Warnf("Error flushing response: %v", e)
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BreakpointHit Send notification message to Dezog on BP Hit
|
|
||||||
func (p *DZRP) BreakpointHit(number uint16, typ byte) {
|
|
||||||
// turn off temporary breakpoints on any hit
|
|
||||||
p.debugger.SetBreakpointEnabled(breakpoint.BpTmp1, false)
|
|
||||||
p.debugger.SetBreakpointEnabled(breakpoint.BpTmp2, false)
|
|
||||||
if p.writer != nil {
|
|
||||||
err := p.writeResponse(p.buildBpHitResponse(number, typ))
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("NTF_PAUSE, write response err: %v", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Warn("NTF_PAUSE, writer is nil")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DZRP) buildBpHitResponse(number uint16, typ byte) *Response {
|
|
||||||
bpReason, bpAddr, mBank := p.getBpReasonAndAddr(number, typ)
|
|
||||||
rsn := getBpReason(bpReason, bpAddr)
|
|
||||||
// msg - ASCIIZ bytes
|
|
||||||
msg := []byte(rsn)
|
|
||||||
msg = append(msg, byte(0))
|
|
||||||
|
|
||||||
rep := []byte{
|
|
||||||
1, // NTF_PAUSE
|
|
||||||
bpReason,
|
|
||||||
byte(bpAddr), byte(bpAddr >> 8),
|
|
||||||
mBank, // bank
|
|
||||||
}
|
|
||||||
rep = append(rep, msg...)
|
|
||||||
|
|
||||||
log.Debugf("NTF_PAUSE %s resp: %v", rsn, PayloadToString(rep))
|
|
||||||
|
|
||||||
return &Response{
|
|
||||||
Len: uint32(len(rep) + 1),
|
|
||||||
Sn: 0,
|
|
||||||
Payload: rep,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleCommand Handle command received from client, return true if last command is CMD_CLOSE
|
|
||||||
func (p *DZRP) handleCommand() bool {
|
|
||||||
log.Debugf("Handling command: %s", p.cmd.toString())
|
|
||||||
var err error
|
|
||||||
var resp *Response
|
|
||||||
handler, ok := commandHandlers[int(p.cmd.Id)]
|
|
||||||
if ok {
|
|
||||||
resp, err = handler(p)
|
|
||||||
} else {
|
|
||||||
//resp = NewResponse(p.cmd, nil)
|
|
||||||
err = errors.New("unknown command Id: " + strconv.Itoa(int(p.cmd.Id)))
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
err = p.writeResponse(resp)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Error handling command: %v", err)
|
|
||||||
}
|
|
||||||
return p.cmd.Id == CMD_CLOSE
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandHandler func(*DZRP) (*Response, error)
|
|
||||||
|
|
||||||
var commandHandlers = map[int]CommandHandler{
|
|
||||||
CMD_INIT: (*DZRP).handleCmdInit, //1
|
|
||||||
CMD_CLOSE: (*DZRP).handleCmdClose, //2
|
|
||||||
CMD_GET_REGISTERS: (*DZRP).handleCmdGetRegisters, //3
|
|
||||||
CMD_SET_REGISTER: (*DZRP).handleCmdSetRegister, //4
|
|
||||||
CMD_CONTINUE: (*DZRP).handleCmdContinue, //6
|
|
||||||
CMD_PAUSE: (*DZRP).handleCmdPause, // 7
|
|
||||||
CMD_READ_MEM: (*DZRP).handleCmdReadMem, //8
|
|
||||||
CMD_WRITE_MEM: (*DZRP).handleCmdWriteMem, //9
|
|
||||||
CMD_LOOPBACK: (*DZRP).handleCmdLoopback, // 15
|
|
||||||
CMD_READ_PORT: (*DZRP).handleCmdReadPort, // 20
|
|
||||||
CMD_WRITE_PORT: (*DZRP).handleCmdWritePort, // 21
|
|
||||||
CMD_INTERRUPT_ON_OFF: (*DZRP).handleCmdInterruptOnOff, // 23
|
|
||||||
CMD_ADD_BREAKPOINT: (*DZRP).handleCmdAddBreakpoint, //40
|
|
||||||
CMD_REMOVE_BREAKPOINT: (*DZRP).handleCmdRemoveBreakpoint, //41
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DZRP) handleCmdInit() (*Response, error) {
|
|
||||||
if len(p.cmd.Payload) < 4 {
|
|
||||||
return nil, errors.New("too short payload")
|
|
||||||
}
|
|
||||||
p.debugger.SetStepMode(true)
|
|
||||||
p.debugger.SetBreakpointsEnabled(true)
|
|
||||||
p.debugger.ClearBreakpoints()
|
|
||||||
p.debugger.ClearMemBreakpoints()
|
|
||||||
|
|
||||||
app := string(p.cmd.Payload[3 : len(p.cmd.Payload)-1])
|
|
||||||
log.Debugf("CMD_INIT: client version %d.%d.%d App: %s", p.cmd.Payload[0], p.cmd.Payload[1], p.cmd.Payload[2], app)
|
|
||||||
|
|
||||||
payload := []byte{0, VersionMajor, VersionMinor, VersionPatch, MachineZX128K}
|
|
||||||
payload = append(payload, []byte(AppName)...)
|
|
||||||
payload = append(payload, 0)
|
|
||||||
return NewResponse(p.cmd, payload), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DZRP) handleCmdClose() (*Response, error) {
|
|
||||||
log.Debug("CMD_CLOSE")
|
|
||||||
return NewResponse(p.cmd, nil), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DZRP) handleCmdPause() (*Response, error) {
|
|
||||||
log.Debug("CMD_PAUSE")
|
|
||||||
p.debugger.SetStepMode(true)
|
|
||||||
//return NewResponse(p.cmd, []byte{p.cmd.Sn}), nil
|
|
||||||
return NewResponse(p.cmd, nil), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DZRP) handleCmdReadMem() (*Response, error) {
|
|
||||||
if len(p.cmd.Payload) < 5 {
|
|
||||||
return nil, errors.New("too short payload")
|
|
||||||
}
|
|
||||||
addr := uint16(p.cmd.Payload[2])<<8 + uint16(p.cmd.Payload[1])
|
|
||||||
size := uint16(p.cmd.Payload[4])<<8 + uint16(p.cmd.Payload[3])
|
|
||||||
|
|
||||||
log.Debugf("CMD_READ_MEM[0x%04X] len: 0x%04X", addr, size)
|
|
||||||
mem := make([]byte, size)
|
|
||||||
//mem[0] = cmd.Sn
|
|
||||||
for i := 0; i < int(size); i++ {
|
|
||||||
mem[i] = p.computer.MemRead(addr)
|
|
||||||
addr++
|
|
||||||
}
|
|
||||||
return NewResponse(p.cmd, mem), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DZRP) handleCmdWriteMem() (*Response, error) {
|
|
||||||
if len(p.cmd.Payload) < 4 {
|
|
||||||
return nil, errors.New("too short payload")
|
|
||||||
}
|
|
||||||
addr := uint16(p.cmd.Payload[1]) + uint16(p.cmd.Payload[2])<<8
|
|
||||||
log.Debugf("CMD_WRITE_MEM[0x%04X] len: 0x%04X", addr, len(p.cmd.Payload)-3)
|
|
||||||
for i := 3; i < len(p.cmd.Payload); i++ {
|
|
||||||
p.computer.MemWrite(addr, p.cmd.Payload[i])
|
|
||||||
addr++
|
|
||||||
}
|
|
||||||
return NewResponse(p.cmd, []byte{p.cmd.Sn}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DZRP) handleCmdSetRegister() (*Response, error) {
|
|
||||||
lo := p.cmd.Payload[1]
|
|
||||||
hi := p.cmd.Payload[2]
|
|
||||||
word := uint16(hi)<<8 | uint16(lo)
|
|
||||||
s := p.computer.CPUState()
|
|
||||||
|
|
||||||
switch p.cmd.Payload[0] {
|
|
||||||
case RegPC:
|
|
||||||
s.PC = word
|
|
||||||
log.Debugf("Set PC=0x%04X", s.PC)
|
|
||||||
case RegSP:
|
|
||||||
s.SP = word
|
|
||||||
log.Debugf("Set SP=0x%04X", s.PC)
|
|
||||||
case RegAF:
|
|
||||||
s.A = hi
|
|
||||||
s.Flags.SetFlags(lo)
|
|
||||||
case RegBC:
|
|
||||||
s.B = hi
|
|
||||||
s.C = lo
|
|
||||||
case RegDE:
|
|
||||||
s.D = hi
|
|
||||||
s.E = lo
|
|
||||||
case RegHL:
|
|
||||||
s.H = hi
|
|
||||||
s.L = lo
|
|
||||||
case RegIX:
|
|
||||||
s.IX = word
|
|
||||||
case RegIY:
|
|
||||||
s.IY = word
|
|
||||||
case RegAF_:
|
|
||||||
s.AAlt = hi
|
|
||||||
s.FlagsAlt.SetFlags(lo)
|
|
||||||
case RegBC_:
|
|
||||||
s.BAlt = hi
|
|
||||||
s.CAlt = lo
|
|
||||||
case RegDE_:
|
|
||||||
s.DAlt = hi
|
|
||||||
s.EAlt = lo
|
|
||||||
case RegHL_:
|
|
||||||
s.HAlt = hi
|
|
||||||
s.LAlt = lo
|
|
||||||
case RegIM:
|
|
||||||
s.IMode = lo
|
|
||||||
case RegF:
|
|
||||||
s.Flags.SetFlags(lo)
|
|
||||||
case RegA:
|
|
||||||
s.A = lo
|
|
||||||
case RegC:
|
|
||||||
s.C = lo
|
|
||||||
case RegB:
|
|
||||||
s.B = lo
|
|
||||||
case RegE:
|
|
||||||
s.E = lo
|
|
||||||
case RegD:
|
|
||||||
s.D = lo
|
|
||||||
case RegL:
|
|
||||||
s.L = lo
|
|
||||||
case RegH:
|
|
||||||
s.H = lo
|
|
||||||
case RegIXL:
|
|
||||||
s.IX = s.IX&0xff00 | uint16(lo)
|
|
||||||
case RegIXH:
|
|
||||||
s.IX = (uint16(lo) << 8) | (s.IX & 0x00ff)
|
|
||||||
case RegIYL:
|
|
||||||
s.IY = s.IY&0xff00 | uint16(lo)
|
|
||||||
case RegIYH:
|
|
||||||
s.IY = (uint16(lo) << 8) | (s.IY & 0x00ff)
|
|
||||||
case RegF_:
|
|
||||||
s.FlagsAlt.SetFlags(lo)
|
|
||||||
case RegA_:
|
|
||||||
s.AAlt = lo
|
|
||||||
case RegC_:
|
|
||||||
s.CAlt = lo
|
|
||||||
case RegB_:
|
|
||||||
s.BAlt = lo
|
|
||||||
case RegE_:
|
|
||||||
s.EAlt = lo
|
|
||||||
case RegD_:
|
|
||||||
s.DAlt = lo
|
|
||||||
case RegL_:
|
|
||||||
s.LAlt = lo
|
|
||||||
case RegH_:
|
|
||||||
s.HAlt = lo
|
|
||||||
case RegR:
|
|
||||||
s.R = lo
|
|
||||||
case RegI:
|
|
||||||
s.I = lo
|
|
||||||
default:
|
|
||||||
return nil, errors.New("unknown register no: " + strconv.Itoa(int(p.cmd.Payload[0])))
|
|
||||||
}
|
|
||||||
p.computer.SetCPUState(s)
|
|
||||||
//return NewResponse(p.cmd, []byte{p.cmd.Sn}), nil
|
|
||||||
return NewResponse(p.cmd, nil), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DZRP) handleCmdGetRegisters() (*Response, error) {
|
|
||||||
s := p.computer.CPUState()
|
|
||||||
resp := []byte{
|
|
||||||
//p.cmd.Sn,
|
|
||||||
byte(s.PC), byte(s.PC >> 8),
|
|
||||||
byte(s.SP), byte(s.SP >> 8),
|
|
||||||
s.Flags.GetFlags(), s.A,
|
|
||||||
s.C, s.B,
|
|
||||||
s.E, s.D,
|
|
||||||
s.L, s.H,
|
|
||||||
byte(s.IX), byte(s.IX >> 8),
|
|
||||||
byte(s.IY), byte(s.IY >> 8),
|
|
||||||
s.FlagsAlt.GetFlags(), s.AAlt,
|
|
||||||
s.CAlt, s.BAlt,
|
|
||||||
s.EAlt, s.DAlt,
|
|
||||||
s.LAlt, s.HAlt,
|
|
||||||
s.R,
|
|
||||||
s.I,
|
|
||||||
s.IMode,
|
|
||||||
0, // reserved
|
|
||||||
0, // Nslots. The number of slots that will follow.
|
|
||||||
}
|
|
||||||
log.Debugf("CMD_GET_REGISTERS resp: %v", resp)
|
|
||||||
return NewResponse(p.cmd, resp), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DZRP) handleCmdContinue() (*Response, error) {
|
|
||||||
|
|
||||||
eb1 := p.cmd.Payload[0] != 0
|
|
||||||
ab1 := (uint16(p.cmd.Payload[2]) << 8) | uint16(p.cmd.Payload[1])
|
|
||||||
p.setTempBreakpoint(eb1, breakpoint.BpTmp1, ab1)
|
|
||||||
eb2 := p.cmd.Payload[3] != 0
|
|
||||||
ab2 := (uint16(p.cmd.Payload[5]) << 8) | uint16(p.cmd.Payload[4])
|
|
||||||
p.setTempBreakpoint(eb2, breakpoint.BpTmp2, ab2)
|
|
||||||
|
|
||||||
log.Debugf("CMD_CONTINUE BP1 en: %v, addr: 0x%04X; BP2 en: %v, addr: 0x%04X; AC: 0x%02X",
|
|
||||||
eb1, ab1, eb2, ab2, p.cmd.Payload[6])
|
|
||||||
|
|
||||||
p.debugger.SetRunMode(true)
|
|
||||||
|
|
||||||
//return NewResponse(p.cmd, []byte{p.cmd.Sn}), nil
|
|
||||||
return NewResponse(p.cmd, nil), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DZRP) setTempBreakpoint(ena bool, no uint16, addr uint16) {
|
|
||||||
if ena {
|
|
||||||
e := p.debugger.SetBreakpoint(no, fmt.Sprintf("PC=%04Xh", addr), 0)
|
|
||||||
if e != nil {
|
|
||||||
log.Debugf("setTmpBreakpoint err: %v", e)
|
|
||||||
}
|
|
||||||
p.debugger.SetBreakpointEnabled(no, true)
|
|
||||||
} else {
|
|
||||||
p.debugger.SetBreakpointEnabled(no, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DZRP) getBpReasonAndAddr(number uint16, typ byte) (reason byte, addr uint16, mBank uint8) {
|
|
||||||
reason = BprStepOver // default StepOver
|
|
||||||
addr = number
|
|
||||||
mBank = uint8(1)
|
|
||||||
if typ >= 1 && typ <= 2 { // 1-rd, 2-wr
|
|
||||||
reason = typ + 2
|
|
||||||
} else {
|
|
||||||
if number != breakpoint.BpTmp1 && number != breakpoint.BpTmp2 {
|
|
||||||
// bp hit
|
|
||||||
mBank = p.debugger.BreakpointMBank(addr)
|
|
||||||
reason = BprHit
|
|
||||||
}
|
|
||||||
addr = p.computer.CPUState().PC
|
|
||||||
}
|
|
||||||
return reason, addr, mBank
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DZRP) handleCmdAddBreakpoint() (*Response, error) {
|
|
||||||
if len(p.cmd.Payload) < 4 {
|
|
||||||
return nil, errors.New("too short payload")
|
|
||||||
}
|
|
||||||
addr := (uint16(p.cmd.Payload[1]) << 8) | uint16(p.cmd.Payload[0])
|
|
||||||
mBank := p.cmd.Payload[2]
|
|
||||||
isCond := p.cmd.Payload[3] != 0
|
|
||||||
var expr string
|
|
||||||
if isCond {
|
|
||||||
expr = string(p.cmd.Payload[3 : len(p.cmd.Payload)-3])
|
|
||||||
} else {
|
|
||||||
expr = fmt.Sprintf("PC=%04Xh", addr)
|
|
||||||
}
|
|
||||||
log.Debugf("CMD_ADD_BREAKPOINT addr: 0x%04X, bank: %d, cond: '%s'", addr, mBank, expr)
|
|
||||||
bpNum, err := p.debugger.AddBreakpoint(expr, mBank)
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("SetBreakpoint err: %v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if bpNum == breakpoint.MaxBreakpoints {
|
|
||||||
bpNum = 0
|
|
||||||
}
|
|
||||||
//bpNum := p.debugger.GetBreakpointNum()
|
|
||||||
//if bpNum < breakpoint.MaxBreakpoints {
|
|
||||||
// err := p.debugger.SetBreakpoint(bpNum, cond, mBank)
|
|
||||||
// p.debugger.SetBreakpointEnabled(bpNum, true)
|
|
||||||
// if err != nil {
|
|
||||||
// log.Debugf("SetBreakpoint err: %v", err)
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
//} else {
|
|
||||||
// bpNum = 0
|
|
||||||
//}
|
|
||||||
return NewResponse(p.cmd, []byte{byte(bpNum), byte(bpNum >> 8)}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getBpReason(reason byte, addr uint16) string {
|
|
||||||
rsn, ok := BprReasons[int(reason)]
|
|
||||||
if !ok {
|
|
||||||
rsn = strconv.Itoa(int(reason))
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("BP: '%s', at: 0x%04X", rsn, addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DZRP) handleCmdRemoveBreakpoint() (*Response, error) {
|
|
||||||
if len(p.cmd.Payload) < 2 {
|
|
||||||
return nil, errors.New("too short payload")
|
|
||||||
}
|
|
||||||
bpNum := (uint16(p.cmd.Payload[1]) << 8) | uint16(p.cmd.Payload[0])
|
|
||||||
p.debugger.RemoveBreakpoint(bpNum)
|
|
||||||
log.Debugf("CMD_REMOVE_BREAKPOINT no: %d", bpNum)
|
|
||||||
return NewResponse(p.cmd, nil), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleCmdLoopback send back received data
|
|
||||||
func (p *DZRP) handleCmdLoopback() (*Response, error) {
|
|
||||||
return NewResponse(p.cmd, p.cmd.Payload), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DZRP) handleCmdReadPort() (*Response, error) {
|
|
||||||
if len(p.cmd.Payload) < 2 {
|
|
||||||
return nil, errors.New("too short payload")
|
|
||||||
}
|
|
||||||
addr := (uint16(p.cmd.Payload[1]) << 8) | uint16(p.cmd.Payload[0])
|
|
||||||
return NewResponse(p.cmd, []byte{p.computer.IORead(addr)}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DZRP) handleCmdWritePort() (*Response, error) {
|
|
||||||
if len(p.cmd.Payload) < 3 {
|
|
||||||
return nil, errors.New("too short payload")
|
|
||||||
}
|
|
||||||
addr := (uint16(p.cmd.Payload[1]) << 8) | uint16(p.cmd.Payload[0])
|
|
||||||
p.computer.IOWrite(addr, p.cmd.Payload[2])
|
|
||||||
return NewResponse(p.cmd, nil), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DZRP) handleCmdInterruptOnOff() (*Response, error) {
|
|
||||||
if len(p.cmd.Payload) == 0 {
|
|
||||||
return nil, errors.New("too short payload")
|
|
||||||
}
|
|
||||||
on := p.cmd.Payload[0] != 0
|
|
||||||
p.debugger.SetBreakpointsEnabled(on)
|
|
||||||
log.Debugf("CMD_INTERRUPT_ONOFF on: %t", on)
|
|
||||||
return NewResponse(p.cmd, nil), nil
|
|
||||||
}
|
|
||||||
@ -1,178 +0,0 @@
|
|||||||
package dzrp
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
const (
|
|
||||||
VersionMajor = 2
|
|
||||||
VersionMinor = 1
|
|
||||||
VersionPatch = 0
|
|
||||||
AppName = "okemu v1.0.0"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Command struct {
|
|
||||||
Len uint32 // Length of the payload data. (little endian)
|
|
||||||
Sn uint8 // Sequence number, 1-255. Increased with each command
|
|
||||||
Id uint8 // Command ID
|
|
||||||
Payload []uint8 // Payload: Data[0]..Data[n-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
type Notification struct {
|
|
||||||
Len uint32 // Length of the following data beginning with the sequence number. (little endian)
|
|
||||||
Sn uint8 // Sequence number = 0
|
|
||||||
Payload []uint8 // Payload: Data[0]..Data[n-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
type Response struct {
|
|
||||||
Len uint32 // Length of the following data beginning with the sequence number. (little endian)
|
|
||||||
Sn uint8 // Sequence number, same as command.
|
|
||||||
Payload []uint8 // Payload: Data[0]..Data[n-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// The DRZP commands and responses.
|
|
||||||
// The response contains the command with the bit 7 set.
|
|
||||||
const (
|
|
||||||
CMD_INIT = 1
|
|
||||||
CMD_CLOSE = 2
|
|
||||||
CMD_GET_REGISTERS = 3
|
|
||||||
CMD_SET_REGISTER = 4
|
|
||||||
CMD_WRITE_BANK = 5
|
|
||||||
CMD_CONTINUE = 6
|
|
||||||
CMD_PAUSE = 7
|
|
||||||
CMD_READ_MEM = 8
|
|
||||||
CMD_WRITE_MEM = 9
|
|
||||||
CMD_SET_SLOT = 10
|
|
||||||
CMD_GET_TBBLUE_REG = 11
|
|
||||||
CMD_SET_BORDER = 12
|
|
||||||
CMD_SET_BREAKPOINTS = 13
|
|
||||||
CMD_RESTORE_MEM = 14
|
|
||||||
CMD_LOOPBACK = 15
|
|
||||||
CMD_GET_SPRITES_PALETTE = 16
|
|
||||||
CMD_GET_SPRITES_CLIP_WINDOW_AND_CONTROL = 17
|
|
||||||
|
|
||||||
// Sprites
|
|
||||||
CMD_GET_SPRITES = 18
|
|
||||||
CMD_GET_SPRITE_PATTERNS = 19
|
|
||||||
|
|
||||||
CMD_READ_PORT = 20
|
|
||||||
CMD_WRITE_PORT = 21
|
|
||||||
CMD_EXEC_ASM = 22
|
|
||||||
CMD_INTERRUPT_ON_OFF = 23
|
|
||||||
|
|
||||||
// Breakpoint
|
|
||||||
CMD_ADD_BREAKPOINT = 40
|
|
||||||
CMD_REMOVE_BREAKPOINT = 41
|
|
||||||
|
|
||||||
CMD_ADD_WATCHPOINT = 42
|
|
||||||
CMD_REMOVE_WATCHPOINT = 43
|
|
||||||
|
|
||||||
// State
|
|
||||||
CMD_READ_STATE = 50
|
|
||||||
CMD_WRITE_STATE = 51
|
|
||||||
)
|
|
||||||
|
|
||||||
// DZRP notifications.
|
|
||||||
const NTF_PAUSE = 1
|
|
||||||
|
|
||||||
// Machine type that is returned in CMD_INIT.
|
|
||||||
// It is required to determine the memory model
|
|
||||||
const (
|
|
||||||
MachineUnknown = 0
|
|
||||||
MachineZX16K = 1
|
|
||||||
MachineZX48K = 2
|
|
||||||
MachineZX128K = 3
|
|
||||||
MachineZXNEXT = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
RegPC = iota
|
|
||||||
RegSP
|
|
||||||
RegAF
|
|
||||||
RegBC
|
|
||||||
RegDE
|
|
||||||
RegHL
|
|
||||||
RegIX
|
|
||||||
RegIY
|
|
||||||
RegAF_
|
|
||||||
RegBC_
|
|
||||||
RegDE_
|
|
||||||
RegHL_
|
|
||||||
RegUnk
|
|
||||||
RegIM
|
|
||||||
RegF
|
|
||||||
RegA
|
|
||||||
RegC
|
|
||||||
RegB
|
|
||||||
RegE
|
|
||||||
RegD
|
|
||||||
RegL
|
|
||||||
RegH
|
|
||||||
RegIXL
|
|
||||||
RegIXH
|
|
||||||
RegIYL
|
|
||||||
RegIYH
|
|
||||||
RegF_
|
|
||||||
RegA_
|
|
||||||
RegC_
|
|
||||||
RegB_
|
|
||||||
RegE_
|
|
||||||
RegD_
|
|
||||||
RegL_
|
|
||||||
RegH_
|
|
||||||
RegR
|
|
||||||
RegI
|
|
||||||
)
|
|
||||||
|
|
||||||
type CmdInitCommand struct {
|
|
||||||
Major uint8 // Version (of the command sender): 3 bytes, big endian: Major.Minor.Patch
|
|
||||||
Minor uint8
|
|
||||||
Patch uint8
|
|
||||||
AppName string // 0-terminated string The program name + version as a string. E.g. "DeZog v1.4.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
type CmdInitResponse struct {
|
|
||||||
Sn uint8 // Same seq no
|
|
||||||
Error uint8 // Error: 0=no error, 1=general (unknown) error.
|
|
||||||
Major uint8 // Version (of the response sender) : 3 bytes, big endian: Major.Minor.Patch
|
|
||||||
Minor uint8
|
|
||||||
Patch uint8
|
|
||||||
Machine uint8 // Machine type (memory model): 0 = UNKNOWN, 1 = ZX16K, 2 = ZX48K, 3 = ZX128K, 4 = ZXNEXT.
|
|
||||||
AppName string // 0-terminated string The responding program name + version as a string. E.g. "dbg_uart_if v2.0.0"}
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
BprStepOver = 0
|
|
||||||
BprManual = 1
|
|
||||||
BprHit = 2
|
|
||||||
BprMemRead = 3
|
|
||||||
BprMemWrite = 4
|
|
||||||
BprOther = 255
|
|
||||||
)
|
|
||||||
|
|
||||||
var BprReasons = map[int]string{
|
|
||||||
BprStepOver: "Step-over",
|
|
||||||
BprManual: "Manual break",
|
|
||||||
BprHit: "Hit",
|
|
||||||
BprMemRead: "WP read",
|
|
||||||
BprMemWrite: "WP Write",
|
|
||||||
BprOther: "Other",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Command) toString() string {
|
|
||||||
return fmt.Sprintf("Len: %d, Sn: %d, Id: %d, Payload: %s", c.Len, c.Sn, c.Id, PayloadToString(c.Payload))
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewResponse(cmd *Command, payload []byte) *Response {
|
|
||||||
return &Response{
|
|
||||||
Len: uint32(len(payload) + 1),
|
|
||||||
Sn: cmd.Sn,
|
|
||||||
Payload: payload,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func PayloadToString(payload []byte) string {
|
|
||||||
res := "["
|
|
||||||
for _, b := range payload {
|
|
||||||
res += fmt.Sprintf("%02X ", b)
|
|
||||||
}
|
|
||||||
return res + "]"
|
|
||||||
}
|
|
||||||
@ -1,149 +0,0 @@
|
|||||||
package debug
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Operators with their priority
|
|
||||||
var operators = map[string]int{
|
|
||||||
"(": 12, ")": 12,
|
|
||||||
"*": 11, "/": 11, "%": 11,
|
|
||||||
"+": 10, "-": 10,
|
|
||||||
"<<": 9, ">>": 9,
|
|
||||||
"<": 8, "<=": 8, ">": 8, ">=": 8,
|
|
||||||
"=": 7, "!=": 7,
|
|
||||||
"&": 6,
|
|
||||||
"^": 5,
|
|
||||||
"|": 4,
|
|
||||||
"&&": 3,
|
|
||||||
"||": 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
var variables = map[string]bool{
|
|
||||||
"A": true, "B": true, "C": true, "D": true, "E": true, "F": true, "H": true, "L": true, "I": true,
|
|
||||||
"R": true, "SF": true, "NF": true, "PF": true, "VF": true, "XF": true, "YF": true, "ZF": true,
|
|
||||||
"A'": true, "B'": true, "C'": true, "D'": true, "E'": true, "F'": true, "H'": true, "L'": true,
|
|
||||||
"AF": true, "BC": true, "DE": true, "HL": true, "IX": true, "IY": true, "PC": true, "SP": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
OTUnknown = iota
|
|
||||||
OTValue
|
|
||||||
OTVariable
|
|
||||||
OTOperation
|
|
||||||
)
|
|
||||||
|
|
||||||
type Token struct {
|
|
||||||
name string
|
|
||||||
val uint16
|
|
||||||
ot int
|
|
||||||
}
|
|
||||||
|
|
||||||
type Expression struct {
|
|
||||||
infixExp string
|
|
||||||
inStack []Token
|
|
||||||
outStack []Token
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewExpression(infixExp string) *Expression {
|
|
||||||
return &Expression{infixExp, make([]Token, 0), make([]Token, 0)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Expression) Parse() error {
|
|
||||||
e.infixExp = strings.ToUpper(strings.TrimSpace(e.infixExp))
|
|
||||||
if e.infixExp == "" {
|
|
||||||
return errors.New("no Expression")
|
|
||||||
}
|
|
||||||
ptr := 0
|
|
||||||
for ptr < len(e.infixExp) {
|
|
||||||
token, err := getNextToken(e.infixExp[ptr:])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = validate(token)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = e.parseToken(token)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ptr += len(token.name)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Expression) parseToken(token Token) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validate(token Token) error {
|
|
||||||
switch token.ot {
|
|
||||||
case OTValue:
|
|
||||||
v, err := strconv.ParseUint(token.name, 0, 16)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
token.val = uint16(v)
|
|
||||||
case OTVariable:
|
|
||||||
if !variables[token.name] {
|
|
||||||
return errors.New("unknown variable")
|
|
||||||
}
|
|
||||||
case OTOperation:
|
|
||||||
v, ok := operators[token.name]
|
|
||||||
if !ok {
|
|
||||||
return errors.New("unknown operation")
|
|
||||||
}
|
|
||||||
token.val = uint16(v)
|
|
||||||
default:
|
|
||||||
return errors.New("unknown token")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const operations = "*/%+_<=>!&^|"
|
|
||||||
|
|
||||||
func getNextToken(str string) (Token, error) {
|
|
||||||
ptr := 0
|
|
||||||
exp := ""
|
|
||||||
ot := OTUnknown
|
|
||||||
for ptr < len(str) {
|
|
||||||
ch := str[ptr]
|
|
||||||
if ch == ' ' {
|
|
||||||
if ot == OTUnknown {
|
|
||||||
ptr++
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
// end of token
|
|
||||||
return Token{name: exp, ot: ot}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ch == 'X' || ch == 'O' || ch == 'B' || ch == 'H') && ot != OTValue {
|
|
||||||
exp += string(ch)
|
|
||||||
ptr++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ch >= '0' && ch <= '9' {
|
|
||||||
if len(exp) == 0 {
|
|
||||||
ot = OTValue
|
|
||||||
}
|
|
||||||
exp += string(ch)
|
|
||||||
ptr++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.Contains(operations, string(ch)) {
|
|
||||||
if len(exp) == 0 {
|
|
||||||
ot = OTOperation
|
|
||||||
}
|
|
||||||
exp += string(ch)
|
|
||||||
ptr++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return Token{name: exp, ot: ot}, errors.New("invalid token")
|
|
||||||
}
|
|
||||||
return Token{name: exp, ot: ot}, nil
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
package zrcp
|
|
||||||
|
|
||||||
const welcomeMessage = "Welcome to Ocean-240.2 remote command protocol (ZRCP partial implementation)\nWrite help for available commands\n"
|
|
||||||
const emptyResponse = "\ncommand> "
|
|
||||||
const aboutResponse = "ZEsarUX remote command protocol"
|
|
||||||
const getVersionResponse = "12.1"
|
|
||||||
const getRegistersResponse = "PC=%04x SP=%04x AF=%04x BC=%04x HL=%04x DE=%04x IX=%04x IY=%04x AF'=%04x BC'=%04x HL'=%04x DE'=%04x I=%02x R=%02x F=%s F'=%s MEMPTR=%04x IM0 IFF%s VPS: 0 MMU=%s"
|
|
||||||
const getStateResponse = "PC=%04x SP=%04x AF=%04x BC=%04x HL=%04x DE=%04x IX=%04x IY=%04x AF'=%04x BC'=%04x HL'=%04x DE'=%04x I=%02x R=%02x IM0 IFF%s (PC)=%s (SP)=%s MMU=%s"
|
|
||||||
|
|
||||||
const inCpuStepResponse = "\ncommand@cpu-step> "
|
|
||||||
const getMachineResponse = "Ocean-240.2 64K\n"
|
|
||||||
const respErrorLoading = "ERROR loading file"
|
|
||||||
const quitResponse = "Sayonara baby\n"
|
|
||||||
const runUntilBPMessage = "Running until a breakpoint, key press or data sent, menu opening or other event\n"
|
|
||||||
|
|
||||||
var PushValueTypeName = []string{
|
|
||||||
"default",
|
|
||||||
"call",
|
|
||||||
"rst",
|
|
||||||
"push",
|
|
||||||
"maskable_interrupt",
|
|
||||||
"non_maskable_interrupt",
|
|
||||||
}
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
package zrcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func parseUint64(s string) (uint64, error) {
|
|
||||||
v := strings.TrimSpace(strings.ToUpper(s))
|
|
||||||
base := 0
|
|
||||||
if strings.HasSuffix(v, "H") {
|
|
||||||
v = strings.TrimSuffix(v, "H")
|
|
||||||
base = 16
|
|
||||||
}
|
|
||||||
a, e := strconv.ParseUint(v, base, 64)
|
|
||||||
return a, e
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseUint16(s string) (uint16, error) {
|
|
||||||
v, e := parseUint64(s)
|
|
||||||
if v > 65535 {
|
|
||||||
return uint16(v), fmt.Errorf("too big uint16: %d", v)
|
|
||||||
}
|
|
||||||
return uint16(v), e
|
|
||||||
}
|
|
||||||
|
|
||||||
func toW(hi, lo byte) uint16 {
|
|
||||||
return uint16(lo) | (uint16(hi) << 8)
|
|
||||||
}
|
|
||||||
|
|
||||||
func iifStr(iif1, iif2 bool) string {
|
|
||||||
flags := []byte{'-', '-'}
|
|
||||||
if iif1 {
|
|
||||||
flags[0] = '1'
|
|
||||||
}
|
|
||||||
if iif2 {
|
|
||||||
flags[1] = '2'
|
|
||||||
}
|
|
||||||
return string(flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
func typToString(typ uint8) string {
|
|
||||||
switch typ {
|
|
||||||
case 0:
|
|
||||||
return "D"
|
|
||||||
case 1:
|
|
||||||
return "R"
|
|
||||||
case 2:
|
|
||||||
return "W"
|
|
||||||
case 3:
|
|
||||||
return "R/W"
|
|
||||||
default:
|
|
||||||
return "x"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,17 +0,0 @@
|
|||||||
package zrcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
const exp1 = "CS=0x0100 & SP>=256"
|
|
||||||
const memBpSet = "11ch 3 1"
|
|
||||||
|
|
||||||
func Test_SetMemPB(t *testing.T) {
|
|
||||||
//p := New
|
|
||||||
//resp := p.SetMemBreakpoint(memBpSet)
|
|
||||||
//if resp != "" {
|
|
||||||
// t.Errorf("SetMemBreakpoint() returned %s", resp)
|
|
||||||
//}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,191 +0,0 @@
|
|||||||
package forms
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"image/color"
|
|
||||||
"okemu/config"
|
|
||||||
"okemu/okean240"
|
|
||||||
"okemu/okean240/fdc"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
|
||||||
"fyne.io/fyne/v2/app"
|
|
||||||
"fyne.io/fyne/v2/canvas"
|
|
||||||
"fyne.io/fyne/v2/container"
|
|
||||||
"fyne.io/fyne/v2/dialog"
|
|
||||||
"fyne.io/fyne/v2/driver/desktop"
|
|
||||||
"fyne.io/fyne/v2/layout"
|
|
||||||
"fyne.io/fyne/v2/storage"
|
|
||||||
"fyne.io/fyne/v2/theme"
|
|
||||||
"fyne.io/fyne/v2/widget"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewMainWindow(computer *okean240.ComputerType, config *config.OkEmuConfig, title string) (*fyne.Window, *canvas.Raster, *widget.Label) {
|
|
||||||
emulatorApp := app.NewWithID("oceanemu-0001-0000-0001-8f33aba1fc0e")
|
|
||||||
w := emulatorApp.NewWindow(title)
|
|
||||||
|
|
||||||
// Handle all keys typed in main window canvas
|
|
||||||
w.Canvas().SetOnTypedKey(
|
|
||||||
func(key *fyne.KeyEvent) {
|
|
||||||
computer.PutKey(key)
|
|
||||||
})
|
|
||||||
|
|
||||||
w.Canvas().SetOnTypedRune(
|
|
||||||
func(key rune) {
|
|
||||||
computer.PutRune(key)
|
|
||||||
})
|
|
||||||
|
|
||||||
addShortcuts(w.Canvas(), computer)
|
|
||||||
|
|
||||||
// ---
|
|
||||||
label := widget.NewLabel(fmt.Sprintf("Screen size: %dx%d", computer.ScreenWidth(), computer.ScreenHeight()))
|
|
||||||
|
|
||||||
raster := canvas.NewRasterWithPixels(
|
|
||||||
func(x, y, w, h int) color.Color {
|
|
||||||
var xx uint16
|
|
||||||
if computer.ScreenWidth() == 512 {
|
|
||||||
xx = uint16(x)
|
|
||||||
} else {
|
|
||||||
xx = uint16(x) / 2
|
|
||||||
}
|
|
||||||
return computer.GetPixel(xx, uint16(y/2))
|
|
||||||
})
|
|
||||||
raster.Resize(fyne.NewSize(512, 512))
|
|
||||||
raster.SetMinSize(fyne.NewSize(512, 512))
|
|
||||||
|
|
||||||
centerRaster := container.NewCenter(raster)
|
|
||||||
w.Resize(fyne.NewSize(600, 600))
|
|
||||||
|
|
||||||
//vBox := container.NewVBox(
|
|
||||||
// newToolbar(computer, w, emulatorApp, config),
|
|
||||||
// centerRaster,
|
|
||||||
// label,
|
|
||||||
//)
|
|
||||||
vBox := container.NewBorder(
|
|
||||||
newToolbar(computer, w, emulatorApp, config),
|
|
||||||
label,
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
centerRaster,
|
|
||||||
)
|
|
||||||
w.SetContent(vBox)
|
|
||||||
|
|
||||||
return &w, raster, label
|
|
||||||
}
|
|
||||||
|
|
||||||
var floppyDriveExt = []string{".okd", ".dat", ".bin", ".raw"}
|
|
||||||
|
|
||||||
func newOkdOpenDialog(drive byte, c *okean240.ComputerType, w fyne.Window, config *config.OkEmuConfig) *dialog.FileDialog {
|
|
||||||
fod := dialog.NewFileOpen(func(reader fyne.URIReadCloser, err error) {
|
|
||||||
// Read file list error occurred
|
|
||||||
if err != nil {
|
|
||||||
dialog.ShowError(err, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// User Cancelled
|
|
||||||
if reader == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// User select Open button, load data
|
|
||||||
err = c.LoadFloppyData(drive, reader)
|
|
||||||
if err != nil {
|
|
||||||
dialog.ShowError(err, w)
|
|
||||||
}
|
|
||||||
_ = reader.Close()
|
|
||||||
}, w)
|
|
||||||
fod.SetFileName(config.FDC[drive].FloppyFile)
|
|
||||||
cwd, e := os.Getwd()
|
|
||||||
if e == nil {
|
|
||||||
uri, e := storage.ListerForURI(storage.NewFileURI(cwd))
|
|
||||||
if e == nil {
|
|
||||||
fod.SetLocation(uri)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fod.SetTitleText(fmt.Sprintf("Load floppy %s: image", string(rune(int(drive+66)))))
|
|
||||||
fod.SetFilter(storage.NewExtensionFileFilter(floppyDriveExt))
|
|
||||||
fod.Resize(fyne.NewSize(580, 500))
|
|
||||||
return fod
|
|
||||||
}
|
|
||||||
|
|
||||||
func newToolbar(c *okean240.ComputerType, w fyne.Window, a fyne.App, config *config.OkEmuConfig) *fyne.Container {
|
|
||||||
hBox := container.NewHBox()
|
|
||||||
|
|
||||||
for d := 0; d < fdc.TotalDrives; d++ {
|
|
||||||
hBox.Add(widget.NewLabel(string(rune(66+d)) + ":"))
|
|
||||||
hBox.Add(widget.NewToolbar(
|
|
||||||
// Button Open
|
|
||||||
widget.NewToolbarAction(theme.FolderOpenIcon(), func() {
|
|
||||||
newOkdOpenDialog(byte(d), c, w, config).Show()
|
|
||||||
}),
|
|
||||||
// Button Save
|
|
||||||
widget.NewToolbarAction(theme.DocumentSaveIcon(), func() {
|
|
||||||
err := c.SaveFloppy(byte(d))
|
|
||||||
if err != nil {
|
|
||||||
dialog.ShowError(err, w)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
))
|
|
||||||
hBox.Add(widget.NewSeparator())
|
|
||||||
}
|
|
||||||
|
|
||||||
//hBox.Add(widget.NewButtonWithIcon("1", theme.DownloadIcon(), func() {
|
|
||||||
// c.SetRamBytes(ramBytes)
|
|
||||||
//}))
|
|
||||||
//
|
|
||||||
//hBox.Add(widget.NewSeparator())
|
|
||||||
hBox.Add(widget.NewButtonWithIcon("^C", theme.MediaStopIcon(), func() {
|
|
||||||
c.PutCtrlKey(0x03)
|
|
||||||
}))
|
|
||||||
hBox.Add(widget.NewSeparator())
|
|
||||||
bNorm := widget.NewButtonWithIcon("", theme.MediaPlayIcon(), nil)
|
|
||||||
bFast := widget.NewButtonWithIcon("", theme.MediaFastForwardIcon(), nil)
|
|
||||||
|
|
||||||
bFast.OnTapped = func() {
|
|
||||||
c.SetFullSpeed(true)
|
|
||||||
c.SetCPUFrequency(50_000_000)
|
|
||||||
bNorm.Enable()
|
|
||||||
bFast.Disable()
|
|
||||||
}
|
|
||||||
|
|
||||||
bNorm.OnTapped = func() {
|
|
||||||
c.SetFullSpeed(false)
|
|
||||||
c.SetCPUFrequency(2_500_000)
|
|
||||||
bNorm.Disable()
|
|
||||||
bFast.Enable()
|
|
||||||
}
|
|
||||||
|
|
||||||
bNorm.Disable()
|
|
||||||
|
|
||||||
//hBox.Add(cbFreq)
|
|
||||||
hBox.Add(bNorm)
|
|
||||||
hBox.Add(bFast)
|
|
||||||
|
|
||||||
hBox.Add(widget.NewSeparator())
|
|
||||||
hBox.Add(widget.NewButtonWithIcon("", theme.SettingsIcon(), func() {
|
|
||||||
cfg := config.Clone()
|
|
||||||
d := dialog.NewCustomConfirm("OK-Emu settings", "Save", "Cancel", settingsDialog(cfg), func(b bool) {
|
|
||||||
if b {
|
|
||||||
cfg.Save()
|
|
||||||
}
|
|
||||||
}, w)
|
|
||||||
d.Resize(fyne.NewSize(450, 360))
|
|
||||||
//w.SetContent(settings.NewSettings().LoadAppearanceScreen(w))
|
|
||||||
d.Show()
|
|
||||||
}))
|
|
||||||
hBox.Add(layout.NewSpacer())
|
|
||||||
hBox.Add(widget.NewButtonWithIcon("Reset", theme.MediaReplayIcon(), func() {
|
|
||||||
c.SetPendingHardReset(true)
|
|
||||||
//computer.Reset(conf)
|
|
||||||
}))
|
|
||||||
return hBox
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add shortcuts for all Ctrl+<Letter>
|
|
||||||
func addShortcuts(c fyne.Canvas, computer *okean240.ComputerType) {
|
|
||||||
// Add shortcuts for Ctrl+A to Ctrl+Z
|
|
||||||
for kName := 'A'; kName <= 'Z'; kName++ {
|
|
||||||
kk := fyne.KeyName(kName)
|
|
||||||
sc := &desktop.CustomShortcut{KeyName: kk, Modifier: fyne.KeyModifierControl}
|
|
||||||
c.AddShortcut(sc, func(shortcut fyne.Shortcut) { computer.PutCtrlKey(byte(kName&0xff) - 0x40) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,102 +0,0 @@
|
|||||||
package forms
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"okemu/config"
|
|
||||||
"okemu/okean240/fdc"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
|
||||||
"fyne.io/fyne/v2/container"
|
|
||||||
"fyne.io/fyne/v2/data/binding"
|
|
||||||
"fyne.io/fyne/v2/data/validation"
|
|
||||||
"fyne.io/fyne/v2/theme"
|
|
||||||
"fyne.io/fyne/v2/widget"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func settingsDialog(config *config.OkEmuConfig) fyne.CanvasObject {
|
|
||||||
|
|
||||||
debugger := widget.NewForm(
|
|
||||||
widget.NewFormItem("Remote debug", debugCheckBox(config)),
|
|
||||||
widget.NewFormItem("Host", hostEntry(config)),
|
|
||||||
widget.NewFormItem("Port", portEntry(config)))
|
|
||||||
|
|
||||||
cont := container.NewAppTabs(
|
|
||||||
container.NewTabItemWithIcon("Debug", theme.MediaPlayIcon(), widget.NewCard("", "", debugger)),
|
|
||||||
)
|
|
||||||
for drv := byte(0); drv < fdc.TotalDrives; drv++ {
|
|
||||||
cont.Append(
|
|
||||||
container.NewTabItemWithIcon("Drive "+string(rune(66+drv))+":", theme.DocumentSaveIcon(), widget.NewCard("Floppy 720k", "", diskForm(config, drv))),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return cont
|
|
||||||
}
|
|
||||||
|
|
||||||
func portEntry(cfg *config.OkEmuConfig) *widget.Entry {
|
|
||||||
dbgPort := widget.NewEntry()
|
|
||||||
dbgPort.SetText(strconv.Itoa(cfg.Debugger.Port))
|
|
||||||
dbgPort.Validator = validation.NewRegexp(`^[0-9]+$`, "port can only contain numbers")
|
|
||||||
dbgPort.OnSubmitted = func(s string) {
|
|
||||||
p, e := strconv.Atoi(s)
|
|
||||||
if e != nil {
|
|
||||||
log.Warn("Illegal port number: " + s)
|
|
||||||
} else {
|
|
||||||
cfg.Debugger.Port = p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dbgPort
|
|
||||||
}
|
|
||||||
|
|
||||||
func hostEntry(cfg *config.OkEmuConfig) *widget.Entry {
|
|
||||||
entry := widget.NewEntry()
|
|
||||||
entry.SetText(cfg.Debugger.Host)
|
|
||||||
entry.Validator = validation.NewRegexp(`^[A-Za-z0-9_-]+$`, "hostname can only contain letters, numbers, '_', and '-'")
|
|
||||||
entry.OnSubmitted = func(s string) {
|
|
||||||
cfg.Debugger.Host = s
|
|
||||||
}
|
|
||||||
return entry
|
|
||||||
}
|
|
||||||
|
|
||||||
func debugCheckBox(cfg *config.OkEmuConfig) *widget.Check {
|
|
||||||
// Debug Enabled
|
|
||||||
check := widget.NewCheck("Enable", func(checked bool) {
|
|
||||||
cfg.Debugger.Enabled = checked
|
|
||||||
})
|
|
||||||
check.Checked = cfg.Debugger.Enabled
|
|
||||||
return check
|
|
||||||
}
|
|
||||||
|
|
||||||
func diskForm(cfg *config.OkEmuConfig, drv byte) *widget.Form {
|
|
||||||
dskAutoLoad := widget.NewCheck("AutoLoad", func(checked bool) {
|
|
||||||
cfg.FDC[drv].AutoLoad = checked
|
|
||||||
})
|
|
||||||
dskAutoLoad.Checked = cfg.FDC[drv].AutoLoad
|
|
||||||
|
|
||||||
dskAutoSave := widget.NewCheck("AutoSave", func(checked bool) {
|
|
||||||
cfg.FDC[drv].AutoSave = checked
|
|
||||||
})
|
|
||||||
|
|
||||||
dskAutoSave.Checked = cfg.FDC[drv].AutoSave
|
|
||||||
|
|
||||||
dskFileName := widget.NewEntryWithData(binding.BindString(&cfg.FDC[drv].FloppyFile))
|
|
||||||
dskFileName.SetPlaceHolder("Enter file name...")
|
|
||||||
dskFileName.Validator = FileExists("File not exists")
|
|
||||||
|
|
||||||
return widget.NewForm(
|
|
||||||
widget.NewFormItem("AutoLoad", dskAutoLoad),
|
|
||||||
widget.NewFormItem("AutoSave", dskAutoSave),
|
|
||||||
widget.NewFormItem("File", dskFileName),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FileExists(reason string) fyne.StringValidator {
|
|
||||||
return func(text string) error {
|
|
||||||
_, e := os.Stat(text)
|
|
||||||
if errors.Is(e, os.ErrNotExist) {
|
|
||||||
return errors.New(reason)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
51
src/go.mod
51
src/go.mod
@ -1,51 +0,0 @@
|
|||||||
module okemu
|
|
||||||
|
|
||||||
go 1.25
|
|
||||||
|
|
||||||
require (
|
|
||||||
fyne.io/fyne/v2 v2.7.3
|
|
||||||
github.com/PaesslerAG/gval v1.2.4
|
|
||||||
github.com/PaesslerAG/jsonpath v0.1.0
|
|
||||||
github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6
|
|
||||||
github.com/loov/hrtime v1.0.4
|
|
||||||
github.com/shopspring/decimal v1.3.1
|
|
||||||
github.com/sirupsen/logrus v1.9.4
|
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
fyne.io/systray v1.12.0 // indirect
|
|
||||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/fredbi/uri v1.1.1 // indirect
|
|
||||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
|
||||||
github.com/fyne-io/gl-js v0.2.0 // indirect
|
|
||||||
github.com/fyne-io/glfw-js v0.3.0 // indirect
|
|
||||||
github.com/fyne-io/image v0.1.1 // indirect
|
|
||||||
github.com/fyne-io/oksvg v0.2.0 // indirect
|
|
||||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect
|
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect
|
|
||||||
github.com/go-text/render v0.2.0 // indirect
|
|
||||||
github.com/go-text/typesetting v0.3.3 // indirect
|
|
||||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
|
||||||
github.com/hack-pad/go-indexeddb v0.3.2 // indirect
|
|
||||||
github.com/hack-pad/safejs v0.1.0 // indirect
|
|
||||||
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade // indirect
|
|
||||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
|
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.5.1 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
|
||||||
github.com/romychs/z80go v1.0.1 // indirect
|
|
||||||
github.com/rymdport/portal v0.4.2 // indirect
|
|
||||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
|
|
||||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
|
|
||||||
github.com/stretchr/testify v1.11.1 // indirect
|
|
||||||
github.com/yuin/goldmark v1.7.8 // indirect
|
|
||||||
golang.org/x/image v0.24.0 // indirect
|
|
||||||
golang.org/x/net v0.35.0 // indirect
|
|
||||||
golang.org/x/sys v0.30.0 // indirect
|
|
||||||
golang.org/x/text v0.22.0 // indirect
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
|
||||||
)
|
|
||||||
103
src/go.sum
103
src/go.sum
@ -1,103 +0,0 @@
|
|||||||
fyne.io/fyne/v2 v2.7.3 h1:xBT/iYbdnNHONWO38fZMBrVBiJG8rV/Jypmy4tVfRWE=
|
|
||||||
fyne.io/fyne/v2 v2.7.3/go.mod h1:gu+dlIcZWSzKZmnrY8Fbnj2Hirabv2ek+AKsfQ2bBlw=
|
|
||||||
fyne.io/systray v1.12.0 h1:CA1Kk0e2zwFlxtc02L3QFSiIbxJ/P0n582YrZHT7aTM=
|
|
||||||
fyne.io/systray v1.12.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
|
|
||||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
|
||||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
|
||||||
github.com/PaesslerAG/gval v1.2.4 h1:rhX7MpjJlcxYwL2eTTYIOBUyEKZ+A96T9vQySWkVUiU=
|
|
||||||
github.com/PaesslerAG/gval v1.2.4/go.mod h1:XRFLwvmkTEdYziLdaCeCa5ImcGVrfQbeNUbVR+C6xac=
|
|
||||||
github.com/PaesslerAG/jsonpath v0.1.0 h1:gADYeifvlqK3R3i2cR5B4DGgxLXIPb3TRTH1mGi0jPI=
|
|
||||||
github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8=
|
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
|
||||||
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
|
||||||
github.com/fredbi/uri v1.1.1 h1:xZHJC08GZNIUhbP5ImTHnt5Ya0T8FI2VAwI/37kh2Ko=
|
|
||||||
github.com/fredbi/uri v1.1.1/go.mod h1:4+DZQ5zBjEwQCDmXW5JdIjz0PUA+yJbvtBv+u+adr5o=
|
|
||||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
|
||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
|
||||||
github.com/fyne-io/gl-js v0.2.0 h1:+EXMLVEa18EfkXBVKhifYB6OGs3HwKO3lUElA0LlAjs=
|
|
||||||
github.com/fyne-io/gl-js v0.2.0/go.mod h1:ZcepK8vmOYLu96JoxbCKJy2ybr+g1pTnaBDdl7c3ajI=
|
|
||||||
github.com/fyne-io/glfw-js v0.3.0 h1:d8k2+Y7l+zy2pc7wlGRyPfTgZoqDf3AI4G+2zOWhWUk=
|
|
||||||
github.com/fyne-io/glfw-js v0.3.0/go.mod h1:Ri6te7rdZtBgBpxLW19uBpp3Dl6K9K/bRaYdJ22G8Jk=
|
|
||||||
github.com/fyne-io/image v0.1.1 h1:WH0z4H7qfvNUw5l4p3bC1q70sa5+YWVt6HCj7y4VNyA=
|
|
||||||
github.com/fyne-io/image v0.1.1/go.mod h1:xrfYBh6yspc+KjkgdZU/ifUC9sPA5Iv7WYUBzQKK7JM=
|
|
||||||
github.com/fyne-io/oksvg v0.2.0 h1:mxcGU2dx6nwjJsSA9PCYZDuoAcsZ/OuJlvg/Q9Njfo8=
|
|
||||||
github.com/fyne-io/oksvg v0.2.0/go.mod h1:dJ9oEkPiWhnTFNCmRgEze+YNprJF7YRbpjgpWS4kzoI=
|
|
||||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA=
|
|
||||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
|
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0=
|
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
|
||||||
github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc=
|
|
||||||
github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU=
|
|
||||||
github.com/go-text/typesetting v0.3.3 h1:ihGNJU9KzdK2QRDy1Bm7FT5RFQoYb+3n3EIhI/4eaQc=
|
|
||||||
github.com/go-text/typesetting v0.3.3/go.mod h1:vIRUT25mLQaSh4C8H/lIsKppQz/Gdb8Pu/tNwpi52ts=
|
|
||||||
github.com/go-text/typesetting-utils v0.0.0-20250618110550-c820a94c77b8 h1:4KCscI9qYWMGTuz6BpJtbUSRzcBrUSSE0ENMJbNSrFs=
|
|
||||||
github.com/go-text/typesetting-utils v0.0.0-20250618110550-c820a94c77b8/go.mod h1:3/62I4La/HBRX9TcTpBj4eipLiwzf+vhI+7whTc9V7o=
|
|
||||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
|
||||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
|
||||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
|
|
||||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
|
||||||
github.com/hack-pad/go-indexeddb v0.3.2 h1:DTqeJJYc1usa45Q5r52t01KhvlSN02+Oq+tQbSBI91A=
|
|
||||||
github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0=
|
|
||||||
github.com/hack-pad/safejs v0.1.0 h1:qPS6vjreAqh2amUqj4WNG1zIw7qlRQJ9K10eDKMCnE8=
|
|
||||||
github.com/hack-pad/safejs v0.1.0/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio=
|
|
||||||
github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6 h1:IIVxLyDUYErC950b8kecjoqDet8P5S4lcVRUOM6rdkU=
|
|
||||||
github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6/go.mod h1:JslaLRrzGsOKJgFEPBP65Whn+rdwDQSk0I0MCRFe2Zw=
|
|
||||||
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade h1:FmusiCI1wHw+XQbvL9M+1r/C3SPqKrmBaIOYwVfQoDE=
|
|
||||||
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
|
|
||||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
|
|
||||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/loov/hrtime v1.0.4 h1:K0wPQBsd9mWer2Sx8zIfpyAlF4ckZovtkEMUR/l9wpU=
|
|
||||||
github.com/loov/hrtime v1.0.4/go.mod h1:VbIwDNS2gYTRoo0RjQFdqdDlBjJLXrkDIOgoA7Jvupk=
|
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
|
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ=
|
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
|
||||||
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
|
|
||||||
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
|
||||||
github.com/romychs/z80go v1.0.0 h1:umlM6U/JPt/qSAUhxu51wSK5a98gJZwhTu34Xj+5Ml8=
|
|
||||||
github.com/romychs/z80go v1.0.0/go.mod h1:bah4U8xv8qWfRJsrioP643bri+LDzKyC03E718YS9wI=
|
|
||||||
github.com/romychs/z80go v1.0.1 h1:rBYxvvdWHGsitZMXTheZGk6lB9VIepUUVWMxPVWimPY=
|
|
||||||
github.com/romychs/z80go v1.0.1/go.mod h1:bah4U8xv8qWfRJsrioP643bri+LDzKyC03E718YS9wI=
|
|
||||||
github.com/rymdport/portal v0.4.2 h1:7jKRSemwlTyVHHrTGgQg7gmNPJs88xkbKcIL3NlcmSU=
|
|
||||||
github.com/rymdport/portal v0.4.2/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
|
|
||||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
|
||||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
|
||||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
|
||||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
|
||||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
|
|
||||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
|
|
||||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
|
|
||||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
|
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
|
||||||
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
|
||||||
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
|
||||||
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
|
|
||||||
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
|
|
||||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
|
||||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
|
||||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
|
||||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
|
||||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
Copyright (c) 2017, Paessler AG <support@paessler.com>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
@ -1,150 +0,0 @@
|
|||||||
package gval
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func BenchmarkGval(bench *testing.B) {
|
|
||||||
benchmarks := []evaluationTest{
|
|
||||||
{
|
|
||||||
// Serves as a "water test" to give an idea of the general overhead
|
|
||||||
name: "const",
|
|
||||||
expression: "1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "single parameter",
|
|
||||||
expression: "requests_made",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"requests_made": 99.0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "parameter",
|
|
||||||
expression: "requests_made > requests_succeeded",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"requests_made": 99.0,
|
|
||||||
"requests_succeeded": 90.0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// The most common use case, a single variable, modified slightly, compared to a constant.
|
|
||||||
// This is the "expected" use case.
|
|
||||||
name: "common",
|
|
||||||
expression: "(requests_made * requests_succeeded / 100) >= 90",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"requests_made": 99.0,
|
|
||||||
"requests_succeeded": 90.0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// All major possibilities in one expression.
|
|
||||||
name: "complex",
|
|
||||||
expression: `2 > 1 &&
|
|
||||||
"something" != "nothing" ||
|
|
||||||
date("2014-01-20") < date("Wed Jul 8 23:07:35 MDT 2015") &&
|
|
||||||
object["Variable name with spaces"] <= array[0] &&
|
|
||||||
modifierTest + 1000 / 2 > (80 * 100 % 2)`,
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"object": map[string]interface{}{"Variable name with spaces": 10.},
|
|
||||||
"array": []interface{}{0.},
|
|
||||||
"modifierTest": 7.3,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// no variables, no modifiers
|
|
||||||
name: "literal",
|
|
||||||
expression: "(2) > (1)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "modifier",
|
|
||||||
expression: "(2) + (2) == (4)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Benchmarks uncompiled parameter regex operators, which are the most expensive of the lot.
|
|
||||||
// Note that regex compilation times are unpredictable and wily things. The regex engine has a lot of edge cases
|
|
||||||
// and possible performance pitfalls. This test doesn't aim to be comprehensive against all possible regex scenarios,
|
|
||||||
// it is primarily concerned with tracking how much longer it takes to compile a regex at evaluation-time than during parse-time.
|
|
||||||
name: "regex",
|
|
||||||
expression: "(foo !~ bar) && (foo + bar =~ oba)",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": "foo",
|
|
||||||
"bar": "bar",
|
|
||||||
"baz": "baz",
|
|
||||||
"oba": ".*oba.*",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Benchmarks pre-compilable regex patterns. Meant to serve as a sanity check that constant strings used as regex patterns
|
|
||||||
// are actually being precompiled.
|
|
||||||
// Also demonstrates that (generally) compiling a regex at evaluation-time takes an order of magnitude more time than pre-compiling.
|
|
||||||
name: "constant regex",
|
|
||||||
expression: `(foo !~ "[bB]az") && (bar =~ "[bB]ar")`,
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": "foo",
|
|
||||||
"bar": "bar",
|
|
||||||
"baz": "baz",
|
|
||||||
"oba": ".*oba.*",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "accessors",
|
|
||||||
expression: "foo.Int",
|
|
||||||
parameter: fooFailureParameters,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "accessors method",
|
|
||||||
expression: "foo.Func()",
|
|
||||||
parameter: fooFailureParameters,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "accessors method parameter",
|
|
||||||
expression: `foo.FuncArgStr("bonk")`,
|
|
||||||
parameter: fooFailureParameters,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "nested accessors",
|
|
||||||
expression: `foo.Nested.Funk`,
|
|
||||||
parameter: fooFailureParameters,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "decimal arithmetic",
|
|
||||||
expression: "(requests_made * requests_succeeded / 100)",
|
|
||||||
extension: decimalArithmetic,
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"requests_made": 99.0,
|
|
||||||
"requests_succeeded": 90.0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "decimal logic",
|
|
||||||
expression: "(requests_made * requests_succeeded / 100) >= 90",
|
|
||||||
extension: decimalArithmetic,
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"requests_made": 99.0,
|
|
||||||
"requests_succeeded": 90.0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, benchmark := range benchmarks {
|
|
||||||
eval, err := Full().NewEvaluable(benchmark.expression)
|
|
||||||
if err != nil {
|
|
||||||
bench.Fatal(err)
|
|
||||||
}
|
|
||||||
_, err = eval(context.Background(), benchmark.parameter)
|
|
||||||
if err != nil {
|
|
||||||
bench.Fatal(err)
|
|
||||||
}
|
|
||||||
bench.Run(benchmark.name+"_evaluation", func(bench *testing.B) {
|
|
||||||
for i := 0; i < bench.N; i++ {
|
|
||||||
eval(context.Background(), benchmark.parameter)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
bench.Run(benchmark.name+"_parsing", func(bench *testing.B) {
|
|
||||||
for i := 0; i < bench.N; i++ {
|
|
||||||
Full().NewEvaluable(benchmark.expression)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,366 +0,0 @@
|
|||||||
package gval
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Selector allows for custom variable selection from structs
|
|
||||||
//
|
|
||||||
// Return value is again handled with variable() until end of the given path
|
|
||||||
type Selector interface {
|
|
||||||
SelectGVal(c context.Context, key string) (interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluable evaluates given parameter
|
|
||||||
type Evaluable func(c context.Context, parameter interface{}) (interface{}, error)
|
|
||||||
|
|
||||||
// EvalInt evaluates given parameter to an int
|
|
||||||
func (e Evaluable) EvalInt(c context.Context, parameter interface{}) (int, error) {
|
|
||||||
v, err := e(c, parameter)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
f, ok := convertToUint(v)
|
|
||||||
if !ok {
|
|
||||||
return 0, fmt.Errorf("expected number but got %v (%T)", v, v)
|
|
||||||
}
|
|
||||||
return int(f), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EvalUint evaluates given parameter to a float64
|
|
||||||
func (e Evaluable) EvalUint(c context.Context, parameter interface{}) (uint, error) {
|
|
||||||
v, err := e(c, parameter)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
f, ok := convertToUint(v)
|
|
||||||
if !ok {
|
|
||||||
return 0, fmt.Errorf("expected number but got %v (%T)", v, v)
|
|
||||||
}
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EvalBool evaluates given parameter to a bool
|
|
||||||
func (e Evaluable) EvalBool(c context.Context, parameter interface{}) (bool, error) {
|
|
||||||
v, err := e(c, parameter)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
b, ok := convertToBool(v)
|
|
||||||
if !ok {
|
|
||||||
return false, fmt.Errorf("expected bool but got %v (%T)", v, v)
|
|
||||||
}
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EvalString evaluates given parameter to a string
|
|
||||||
func (e Evaluable) EvalString(c context.Context, parameter interface{}) (string, error) {
|
|
||||||
o, err := e(c, parameter)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if s, ok := o.(string); ok {
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%v", o), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Const Evaluable represents given constant
|
|
||||||
func (*Parser) Const(value interface{}) Evaluable {
|
|
||||||
return constant(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:noinline
|
|
||||||
func constant(value interface{}) Evaluable {
|
|
||||||
return func(c context.Context, v interface{}) (interface{}, error) {
|
|
||||||
return value, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Var Evaluable represents value at given path.
|
|
||||||
// It supports with default language VariableSelector:
|
|
||||||
//
|
|
||||||
// map[interface{}]interface{},
|
|
||||||
// map[string]interface{} and
|
|
||||||
// []interface{} and via reflect
|
|
||||||
// struct fields,
|
|
||||||
// struct methods,
|
|
||||||
// slices and
|
|
||||||
// map with int or string key.
|
|
||||||
func (p *Parser) Var(path ...Evaluable) Evaluable {
|
|
||||||
if p.selector == nil {
|
|
||||||
return variable(path)
|
|
||||||
}
|
|
||||||
return p.selector(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluables is a slice of Evaluable.
|
|
||||||
type Evaluables []Evaluable
|
|
||||||
|
|
||||||
// EvalStrings evaluates given parameter to a string slice
|
|
||||||
func (evs Evaluables) EvalStrings(c context.Context, parameter interface{}) ([]string, error) {
|
|
||||||
strs := make([]string, len(evs))
|
|
||||||
for i, p := range evs {
|
|
||||||
k, err := p.EvalString(c, parameter)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
strs[i] = k
|
|
||||||
}
|
|
||||||
return strs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func variable(path Evaluables) Evaluable {
|
|
||||||
return func(c context.Context, v interface{}) (interface{}, error) {
|
|
||||||
v2 := v
|
|
||||||
for _, p := range path {
|
|
||||||
k, err := p.EvalString(c, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
switch o := v2.(type) {
|
|
||||||
case Selector:
|
|
||||||
v2, err = o.SelectGVal(c, k)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to select '%s' on %T: %w", k, o, err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
case map[interface{}]interface{}:
|
|
||||||
v2 = o[k]
|
|
||||||
continue
|
|
||||||
case map[string]interface{}:
|
|
||||||
v2 = o[k]
|
|
||||||
continue
|
|
||||||
case []interface{}:
|
|
||||||
if i, err := strconv.Atoi(k); err == nil && i >= 0 && len(o) > i {
|
|
||||||
v2 = o[i]
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
var ok bool
|
|
||||||
v2, ok = reflectSelect(k, o)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unknown parameter '%s' on %T", k, o)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v2, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func reflectSelect(key string, value interface{}) (selection interface{}, ok bool) {
|
|
||||||
vv := reflect.ValueOf(value)
|
|
||||||
vvElem := resolvePotentialPointer(vv)
|
|
||||||
|
|
||||||
switch vvElem.Kind() {
|
|
||||||
case reflect.Map:
|
|
||||||
mapKey, ok := reflectConvertTo(vv.Type().Key().Kind(), key)
|
|
||||||
if !ok {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
vvElem = vv.MapIndex(reflect.ValueOf(mapKey))
|
|
||||||
vvElem = resolvePotentialPointer(vvElem)
|
|
||||||
|
|
||||||
if vvElem.IsValid() {
|
|
||||||
return vvElem.Interface(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
// key didn't exist. Check if there is a bound method
|
|
||||||
method := vv.MethodByName(key)
|
|
||||||
if method.IsValid() {
|
|
||||||
return method.Interface(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
case reflect.Slice:
|
|
||||||
if i, err := strconv.Atoi(key); err == nil && i >= 0 && vv.Len() > i {
|
|
||||||
vvElem = resolvePotentialPointer(vv.Index(i))
|
|
||||||
return vvElem.Interface(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
// key not an int. Check if there is a bound method
|
|
||||||
method := vv.MethodByName(key)
|
|
||||||
if method.IsValid() {
|
|
||||||
return method.Interface(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
case reflect.Struct:
|
|
||||||
field := vvElem.FieldByName(key)
|
|
||||||
if field.IsValid() {
|
|
||||||
return field.Interface(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
method := vv.MethodByName(key)
|
|
||||||
if method.IsValid() {
|
|
||||||
return method.Interface(), true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolvePotentialPointer(value reflect.Value) reflect.Value {
|
|
||||||
if value.Kind() == reflect.Ptr {
|
|
||||||
return value.Elem()
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
func reflectConvertTo(k reflect.Kind, value string) (interface{}, bool) {
|
|
||||||
switch k {
|
|
||||||
case reflect.String:
|
|
||||||
return value, true
|
|
||||||
case reflect.Int:
|
|
||||||
if i, err := strconv.Atoi(value); err == nil {
|
|
||||||
return i, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Parser) callFunc(fun function, args ...Evaluable) Evaluable {
|
|
||||||
return func(c context.Context, v interface{}) (ret interface{}, err error) {
|
|
||||||
a := make([]interface{}, len(args))
|
|
||||||
for i, arg := range args {
|
|
||||||
ai, err := arg(c, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
a[i] = ai
|
|
||||||
}
|
|
||||||
return fun(c, a...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Parser) callEvaluable(fullname string, fun Evaluable, args ...Evaluable) Evaluable {
|
|
||||||
return func(c context.Context, v interface{}) (ret interface{}, err error) {
|
|
||||||
f, err := fun(c, v)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not call function: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
err = fmt.Errorf("failed to execute function '%s': %s", fullname, r)
|
|
||||||
ret = nil
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
ff := reflect.ValueOf(f)
|
|
||||||
|
|
||||||
if ff.Kind() != reflect.Func {
|
|
||||||
return nil, fmt.Errorf("could not call '%s' type %T", fullname, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
a := make([]reflect.Value, len(args))
|
|
||||||
for i := range args {
|
|
||||||
arg, err := args[i](c, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
a[i] = reflect.ValueOf(arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
rr := ff.Call(a)
|
|
||||||
|
|
||||||
r := make([]interface{}, len(rr))
|
|
||||||
for i, e := range rr {
|
|
||||||
r[i] = e.Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
errorInterface := reflect.TypeOf((*error)(nil)).Elem()
|
|
||||||
if len(r) > 0 && ff.Type().Out(len(r)-1).Implements(errorInterface) {
|
|
||||||
if r[len(r)-1] != nil {
|
|
||||||
err = r[len(r)-1].(error)
|
|
||||||
}
|
|
||||||
r = r[0 : len(r)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
switch len(r) {
|
|
||||||
case 0:
|
|
||||||
return err, nil
|
|
||||||
case 1:
|
|
||||||
return r[0], err
|
|
||||||
default:
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsConst returns if the Evaluable is a Parser.Const() value
|
|
||||||
func (e Evaluable) IsConst() bool {
|
|
||||||
pc := reflect.ValueOf(constant(nil)).Pointer()
|
|
||||||
pe := reflect.ValueOf(e).Pointer()
|
|
||||||
return pc == pe
|
|
||||||
}
|
|
||||||
|
|
||||||
func regEx(a, b Evaluable) (Evaluable, error) {
|
|
||||||
if !b.IsConst() {
|
|
||||||
return func(c context.Context, o interface{}) (interface{}, error) {
|
|
||||||
a, err := a.EvalString(c, o)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b, err := b.EvalString(c, o)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
matched, err := regexp.MatchString(b, a)
|
|
||||||
return matched, err
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
s, err := b.EvalString(context.TODO(), nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
regex, err := regexp.Compile(s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return func(c context.Context, v interface{}) (interface{}, error) {
|
|
||||||
s, err := a.EvalString(c, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return regex.MatchString(s), nil
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func notRegEx(a, b Evaluable) (Evaluable, error) {
|
|
||||||
if !b.IsConst() {
|
|
||||||
return func(c context.Context, o interface{}) (interface{}, error) {
|
|
||||||
a, err := a.EvalString(c, o)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b, err := b.EvalString(c, o)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
matched, err := regexp.MatchString(b, a)
|
|
||||||
return !matched, err
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
s, err := b.EvalString(context.TODO(), nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
regex, err := regexp.Compile(s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return func(c context.Context, v interface{}) (interface{}, error) {
|
|
||||||
s, err := a.EvalString(c, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return !regex.MatchString(s), nil
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
@ -1,250 +0,0 @@
|
|||||||
package gval
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEvaluable_IsConst(t *testing.T) {
|
|
||||||
p := Parser{}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
e Evaluable
|
|
||||||
want bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"const",
|
|
||||||
p.Const(80.5),
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"var",
|
|
||||||
p.Var(),
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if got := tt.e.IsConst(); got != tt.want {
|
|
||||||
t.Errorf("Evaluable.IsConst() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvaluable_EvalInt(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
e Evaluable
|
|
||||||
want int
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"point",
|
|
||||||
constant("5.3"),
|
|
||||||
5,
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"number",
|
|
||||||
constant(255.),
|
|
||||||
255,
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"error",
|
|
||||||
constant("5.3 cm"),
|
|
||||||
0,
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := tt.e.EvalInt(context.Background(), nil)
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("Evaluable.EvalInt() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if got != tt.want {
|
|
||||||
t.Errorf("Evaluable.EvalInt() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvaluable_EvalFloat64(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
e Evaluable
|
|
||||||
want float64
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"point",
|
|
||||||
constant("5.3"),
|
|
||||||
5.3,
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"number",
|
|
||||||
constant(255.),
|
|
||||||
255,
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"error",
|
|
||||||
constant("5.3 cm"),
|
|
||||||
0,
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := tt.e.EvalUint(context.Background(), nil)
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("Evaluable.EvalUint() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if got != tt.want {
|
|
||||||
t.Errorf("Evaluable.EvalUint() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type testSelector struct {
|
|
||||||
str string
|
|
||||||
Map map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s testSelector) SelectGVal(ctx context.Context, k string) (interface{}, error) {
|
|
||||||
if k == "str" {
|
|
||||||
return s.str, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if k == "map" {
|
|
||||||
return s.Map, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(k, "deep") {
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("unknown-key")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvaluable_CustomSelector(t *testing.T) {
|
|
||||||
var (
|
|
||||||
lang = Base()
|
|
||||||
tests = []struct {
|
|
||||||
name string
|
|
||||||
expr string
|
|
||||||
params interface{}
|
|
||||||
want interface{}
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"unknown",
|
|
||||||
"s.Foo",
|
|
||||||
map[string]interface{}{"s": &testSelector{}},
|
|
||||||
nil,
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"field directly",
|
|
||||||
"s.Str",
|
|
||||||
map[string]interface{}{"s": &testSelector{str: "test-value"}},
|
|
||||||
nil,
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"field via selector",
|
|
||||||
"s.str",
|
|
||||||
map[string]interface{}{"s": &testSelector{str: "test-value"}},
|
|
||||||
"test-value",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"flat",
|
|
||||||
"str",
|
|
||||||
&testSelector{str: "test-value"},
|
|
||||||
"test-value",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"map field",
|
|
||||||
"s.map.foo",
|
|
||||||
map[string]interface{}{"s": &testSelector{Map: map[string]interface{}{"foo": "bar"}}},
|
|
||||||
"bar",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"crawl to val",
|
|
||||||
"deep.deeper.deepest.str",
|
|
||||||
&testSelector{str: "foo"},
|
|
||||||
"foo",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"crawl to struct",
|
|
||||||
"deep.deeper.deepest",
|
|
||||||
&testSelector{},
|
|
||||||
testSelector{},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
booltests = []struct {
|
|
||||||
name string
|
|
||||||
expr string
|
|
||||||
params interface{}
|
|
||||||
want interface{}
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"test method",
|
|
||||||
"s.IsZero",
|
|
||||||
map[string]interface{}{"s": time.Now()},
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := lang.Evaluate(tt.expr, tt.params)
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("Evaluable.Evaluate() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("Evaluable.Evaluate() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range booltests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := lang.Evaluate(tt.expr, tt.params)
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("Evaluable.Evaluate() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
got, ok := convertToBool(got)
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("Evaluable.Evaluate() error = nok, wantErr %v", tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("Evaluable.Evaluate() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,450 +0,0 @@
|
|||||||
package gval_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/PaesslerAG/gval"
|
|
||||||
"github.com/PaesslerAG/jsonpath"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Example() {
|
|
||||||
|
|
||||||
vars := map[string]interface{}{"name": "World"}
|
|
||||||
|
|
||||||
value, err := gval.Evaluate(`"Hello " + name + "!"`, vars)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Print(value)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// Hello World!
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleEvaluate() {
|
|
||||||
|
|
||||||
value, err := gval.Evaluate("foo > 0", map[string]interface{}{
|
|
||||||
"foo": -1.,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Print(value)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// false
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleEvaluate_nestedParameter() {
|
|
||||||
|
|
||||||
value, err := gval.Evaluate("foo.bar > 0", map[string]interface{}{
|
|
||||||
"foo": map[string]interface{}{"bar": -1.},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Print(value)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// false
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleEvaluate_array() {
|
|
||||||
|
|
||||||
value, err := gval.Evaluate("foo[0]", map[string]interface{}{
|
|
||||||
"foo": []interface{}{-1.},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Print(value)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// -1
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleEvaluate_complexAccessor() {
|
|
||||||
|
|
||||||
value, err := gval.Evaluate(`foo["b" + "a" + "r"]`, map[string]interface{}{
|
|
||||||
"foo": map[string]interface{}{"bar": -1.},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Print(value)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// -1
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleEvaluate_arithmetic() {
|
|
||||||
|
|
||||||
value, err := gval.Evaluate("(requests_made * requests_succeeded / 100) >= 90",
|
|
||||||
map[string]interface{}{
|
|
||||||
"requests_made": 100,
|
|
||||||
"requests_succeeded": 80,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Print(value)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// false
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleEvaluate_string() {
|
|
||||||
|
|
||||||
value, err := gval.Evaluate(`http_response_body == "service is ok"`,
|
|
||||||
map[string]interface{}{
|
|
||||||
"http_response_body": "service is ok",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Print(value)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// true
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleEvaluate_float64() {
|
|
||||||
|
|
||||||
value, err := gval.Evaluate("(mem_used / total_mem) * 100",
|
|
||||||
map[string]interface{}{
|
|
||||||
"total_mem": 1024,
|
|
||||||
"mem_used": 512,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Print(value)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// 50
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleEvaluate_dateComparison() {
|
|
||||||
|
|
||||||
value, err := gval.Evaluate("date(`2014-01-02`) > date(`2014-01-01 23:59:59`)",
|
|
||||||
nil,
|
|
||||||
// define Date comparison because it is not part expression language gval
|
|
||||||
gval.InfixOperator(">", func(a, b interface{}) (interface{}, error) {
|
|
||||||
date1, ok1 := a.(time.Time)
|
|
||||||
date2, ok2 := b.(time.Time)
|
|
||||||
|
|
||||||
if ok1 && ok2 {
|
|
||||||
return date1.After(date2), nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("unexpected operands types (%T) > (%T)", a, b)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Print(value)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// true
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleEvaluable() {
|
|
||||||
eval, err := gval.Full(gval.Constant("maximum_time", 52)).
|
|
||||||
NewEvaluable("response_time <= maximum_time")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 50; i < 55; i++ {
|
|
||||||
value, err := eval(context.Background(), map[string]interface{}{
|
|
||||||
"response_time": i,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// true
|
|
||||||
// true
|
|
||||||
// true
|
|
||||||
// false
|
|
||||||
// false
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleEvaluate_strlen() {
|
|
||||||
|
|
||||||
value, err := gval.Evaluate(`strlen("someReallyLongInputString") <= 16`,
|
|
||||||
nil,
|
|
||||||
gval.Function("strlen", func(args ...interface{}) (interface{}, error) {
|
|
||||||
length := len(args[0].(string))
|
|
||||||
return (float64)(length), nil
|
|
||||||
}))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Print(value)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// false
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleEvaluate_encoding() {
|
|
||||||
|
|
||||||
value, err := gval.Evaluate(`(7 < "47" == true ? "hello world!\n\u263a" : "good bye\n")`+" + ` more text`",
|
|
||||||
nil,
|
|
||||||
gval.Function("strlen", func(args ...interface{}) (interface{}, error) {
|
|
||||||
length := len(args[0].(string))
|
|
||||||
return (float64)(length), nil
|
|
||||||
}))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Print(value)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// hello world!
|
|
||||||
// ☺ more text
|
|
||||||
}
|
|
||||||
|
|
||||||
type exampleType struct {
|
|
||||||
Hello string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e exampleType) World() string {
|
|
||||||
return "world"
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleEvaluate_accessor() {
|
|
||||||
|
|
||||||
value, err := gval.Evaluate(`foo.Hello + foo.World()`,
|
|
||||||
map[string]interface{}{
|
|
||||||
"foo": exampleType{Hello: "hello "},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Print(value)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// hello world
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleEvaluate_flatAccessor() {
|
|
||||||
|
|
||||||
value, err := gval.Evaluate(`Hello + World()`,
|
|
||||||
exampleType{Hello: "hello "},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Print(value)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// hello world
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleEvaluate_nestedAccessor() {
|
|
||||||
|
|
||||||
value, err := gval.Evaluate(`foo.Bar.Hello + foo.Bar.World()`,
|
|
||||||
map[string]interface{}{
|
|
||||||
"foo": struct{ Bar exampleType }{
|
|
||||||
Bar: exampleType{Hello: "hello "},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Print(value)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// hello world
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleVariableSelector() {
|
|
||||||
value, err := gval.Evaluate(`hello.world`,
|
|
||||||
"!",
|
|
||||||
gval.VariableSelector(func(path gval.Evaluables) gval.Evaluable {
|
|
||||||
return func(c context.Context, v interface{}) (interface{}, error) {
|
|
||||||
keys, err := path.EvalStrings(c, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s%s", strings.Join(keys, " "), v), nil
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Print(value)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// hello world!
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleEvaluable_EvalInt() {
|
|
||||||
eval, err := gval.Full().NewEvaluable("1 + x")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err := eval.EvalInt(context.Background(), map[string]interface{}{"x": 5})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Print(value)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// 6
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleEvaluable_EvalBool() {
|
|
||||||
eval, err := gval.Full().NewEvaluable("1 == x")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err := eval.EvalBool(context.Background(), map[string]interface{}{"x": 1})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if value {
|
|
||||||
fmt.Print("yeah")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// yeah
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleEvaluate_jsonpath() {
|
|
||||||
|
|
||||||
value, err := gval.Evaluate(`$["response-time"]`,
|
|
||||||
map[string]interface{}{
|
|
||||||
"response-time": 100,
|
|
||||||
},
|
|
||||||
jsonpath.Language(),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Print(value)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// 100
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleLanguage() {
|
|
||||||
lang := gval.NewLanguage(gval.JSON(), gval.Arithmetic(),
|
|
||||||
//pipe operator
|
|
||||||
gval.PostfixOperator("|", func(c context.Context, p *gval.Parser, pre gval.Evaluable) (gval.Evaluable, error) {
|
|
||||||
post, err := p.ParseExpression(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return func(c context.Context, v interface{}) (interface{}, error) {
|
|
||||||
v, err := pre(c, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return post(c, v)
|
|
||||||
}, nil
|
|
||||||
}))
|
|
||||||
|
|
||||||
eval, err := lang.NewEvaluable(`{"foobar": 50} | foobar + 100`)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err := eval(context.Background(), nil)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(value)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// 150
|
|
||||||
}
|
|
||||||
|
|
||||||
type exampleCustomSelector struct{ hidden string }
|
|
||||||
|
|
||||||
var _ gval.Selector = &exampleCustomSelector{}
|
|
||||||
|
|
||||||
func (s *exampleCustomSelector) SelectGVal(ctx context.Context, k string) (interface{}, error) {
|
|
||||||
if k == "hidden" {
|
|
||||||
return s.hidden, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleSelector() {
|
|
||||||
lang := gval.Base()
|
|
||||||
value, err := lang.Evaluate(
|
|
||||||
"myStruct.hidden",
|
|
||||||
map[string]interface{}{"myStruct": &exampleCustomSelector{hidden: "hello world"}},
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(value)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// hello world
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSub(ctx context.Context, p *gval.Parser) (gval.Evaluable, error) {
|
|
||||||
return p.ParseSublanguage(ctx, subLang)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
superLang = gval.NewLanguage(
|
|
||||||
gval.PrefixExtension('$', parseSub),
|
|
||||||
)
|
|
||||||
subLang = gval.NewLanguage(
|
|
||||||
gval.Init(func(ctx context.Context, p *gval.Parser) (gval.Evaluable, error) { return p.Const("hello world"), nil }),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExampleParser_ParseSublanguage() {
|
|
||||||
value, err := superLang.Evaluate("$", nil)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(value)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// hello world
|
|
||||||
}
|
|
||||||
@ -1,128 +0,0 @@
|
|||||||
package gval
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
type function func(ctx context.Context, arguments ...interface{}) (interface{}, error)
|
|
||||||
|
|
||||||
func toFunc(f interface{}) function {
|
|
||||||
if f, ok := f.(func(arguments ...interface{}) (interface{}, error)); ok {
|
|
||||||
return function(func(ctx context.Context, arguments ...interface{}) (interface{}, error) {
|
|
||||||
var v interface{}
|
|
||||||
errCh := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
if recovered := recover(); recovered != nil {
|
|
||||||
errCh <- fmt.Errorf("%v", recovered)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
result, err := f(arguments...)
|
|
||||||
v = result
|
|
||||||
errCh <- err
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
case err := <-errCh:
|
|
||||||
close(errCh)
|
|
||||||
return v, err
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if f, ok := f.(func(ctx context.Context, arguments ...interface{}) (interface{}, error)); ok {
|
|
||||||
return function(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun := reflect.ValueOf(f)
|
|
||||||
t := fun.Type()
|
|
||||||
return func(ctx context.Context, args ...interface{}) (interface{}, error) {
|
|
||||||
var v interface{}
|
|
||||||
errCh := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
if recovered := recover(); recovered != nil {
|
|
||||||
errCh <- fmt.Errorf("%v", recovered)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
in, err := createCallArguments(ctx, t, args)
|
|
||||||
if err != nil {
|
|
||||||
errCh <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
out := fun.Call(in)
|
|
||||||
|
|
||||||
r := make([]interface{}, len(out))
|
|
||||||
for i, e := range out {
|
|
||||||
r[i] = e.Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
err = nil
|
|
||||||
errorInterface := reflect.TypeOf((*error)(nil)).Elem()
|
|
||||||
if len(r) > 0 && t.Out(len(r)-1).Implements(errorInterface) {
|
|
||||||
if r[len(r)-1] != nil {
|
|
||||||
err = r[len(r)-1].(error)
|
|
||||||
}
|
|
||||||
r = r[0 : len(r)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
switch len(r) {
|
|
||||||
case 0:
|
|
||||||
v = nil
|
|
||||||
case 1:
|
|
||||||
v = r[0]
|
|
||||||
default:
|
|
||||||
v = r
|
|
||||||
}
|
|
||||||
errCh <- err
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
case err := <-errCh:
|
|
||||||
close(errCh)
|
|
||||||
return v, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createCallArguments(ctx context.Context, t reflect.Type, args []interface{}) ([]reflect.Value, error) {
|
|
||||||
variadic := t.IsVariadic()
|
|
||||||
numIn := t.NumIn()
|
|
||||||
|
|
||||||
// if first argument is a context, use the given execution context
|
|
||||||
if numIn > 0 {
|
|
||||||
thisFun := reflect.ValueOf(createCallArguments)
|
|
||||||
thisT := thisFun.Type()
|
|
||||||
if t.In(0) == thisT.In(0) {
|
|
||||||
args = append([]interface{}{ctx}, args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!variadic && len(args) != numIn) || (variadic && len(args) < numIn-1) {
|
|
||||||
return nil, fmt.Errorf("invalid number of parameters")
|
|
||||||
}
|
|
||||||
|
|
||||||
in := make([]reflect.Value, len(args))
|
|
||||||
var inType reflect.Type
|
|
||||||
for i, arg := range args {
|
|
||||||
if !variadic || i < numIn-1 {
|
|
||||||
inType = t.In(i)
|
|
||||||
} else if i == numIn-1 {
|
|
||||||
inType = t.In(numIn - 1).Elem()
|
|
||||||
}
|
|
||||||
argVal := reflect.ValueOf(arg)
|
|
||||||
if arg == nil {
|
|
||||||
argVal = reflect.Zero(reflect.TypeOf((*interface{})(nil)).Elem())
|
|
||||||
} else if !argVal.Type().AssignableTo(inType) {
|
|
||||||
return nil, fmt.Errorf("expected type %s for parameter %d but got %T",
|
|
||||||
inType.String(), i, arg)
|
|
||||||
}
|
|
||||||
in[i] = argVal
|
|
||||||
}
|
|
||||||
return in, nil
|
|
||||||
}
|
|
||||||
@ -1,162 +0,0 @@
|
|||||||
package gval
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_toFunc(t *testing.T) {
|
|
||||||
myError := fmt.Errorf("my error")
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
function interface{}
|
|
||||||
arguments []interface{}
|
|
||||||
want interface{}
|
|
||||||
wantErr error
|
|
||||||
wantAnyErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "empty",
|
|
||||||
function: func() {},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "one arg",
|
|
||||||
function: func(a interface{}) {
|
|
||||||
if a != true {
|
|
||||||
panic("fail")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
arguments: []interface{}{true},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "three args",
|
|
||||||
function: func(a, b, c interface{}) {
|
|
||||||
if a != 1 || b != 2 || c != 3 {
|
|
||||||
panic("fail")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
arguments: []interface{}{1, 2, 3},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "input types",
|
|
||||||
function: func(a int, b string, c bool) {
|
|
||||||
if a != 1 || b != "2" || !c {
|
|
||||||
panic("fail")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
arguments: []interface{}{1, "2", true},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "wronge input type int",
|
|
||||||
function: func(a int, b string, c bool) {},
|
|
||||||
arguments: []interface{}{"1", "2", true},
|
|
||||||
wantAnyErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "wronge input type string",
|
|
||||||
function: func(a int, b string, c bool) {},
|
|
||||||
arguments: []interface{}{1, 2, true},
|
|
||||||
wantAnyErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "wronge input type bool",
|
|
||||||
function: func(a int, b string, c bool) {},
|
|
||||||
arguments: []interface{}{1, "2", "true"},
|
|
||||||
wantAnyErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "wronge input number",
|
|
||||||
function: func(a int, b string, c bool) {},
|
|
||||||
arguments: []interface{}{1, "2"},
|
|
||||||
wantAnyErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "one return",
|
|
||||||
function: func() bool {
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "three returns",
|
|
||||||
function: func() (bool, string, int) {
|
|
||||||
return true, "2", 3
|
|
||||||
},
|
|
||||||
want: []interface{}{true, "2", 3},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "error",
|
|
||||||
function: func() error {
|
|
||||||
return myError
|
|
||||||
},
|
|
||||||
wantErr: myError,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "none error",
|
|
||||||
function: func() error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "one return with error",
|
|
||||||
function: func() (bool, error) {
|
|
||||||
return false, myError
|
|
||||||
},
|
|
||||||
want: false,
|
|
||||||
wantErr: myError,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "three returns with error",
|
|
||||||
function: func() (bool, string, int, error) {
|
|
||||||
return false, "", 0, myError
|
|
||||||
},
|
|
||||||
want: []interface{}{false, "", 0},
|
|
||||||
wantErr: myError,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "context not expiring",
|
|
||||||
function: func(ctx context.Context) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "context expires",
|
|
||||||
function: func(ctx context.Context) error {
|
|
||||||
time.Sleep(20 * time.Millisecond)
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
wantErr: context.DeadlineExceeded,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "nil arg",
|
|
||||||
function: func(a interface{}) bool {
|
|
||||||
return a == nil
|
|
||||||
},
|
|
||||||
arguments: []interface{}{nil},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
|
|
||||||
got, err := toFunc(tt.function)(ctx, tt.arguments...)
|
|
||||||
cancel()
|
|
||||||
|
|
||||||
if tt.wantAnyErr {
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.Fatalf("toFunc()(args...) = error(nil), but wantAnyErr")
|
|
||||||
}
|
|
||||||
if err != tt.wantErr {
|
|
||||||
t.Fatalf("toFunc()(args...) = error(%v), wantErr (%v)", err, tt.wantErr)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("toFunc()(args...) = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
334
src/gval/gval.go
334
src/gval/gval.go
@ -1,334 +0,0 @@
|
|||||||
// Package gval provides a generic expression language.
|
|
||||||
// All functions, infix and prefix operators can be replaced by composing languages into a new one.
|
|
||||||
//
|
|
||||||
// The package contains concrete expression languages for common application in text, arithmetic, decimal arithmetic, propositional logic and so on.
|
|
||||||
// They can be used as basis for a custom expression language or to evaluate expressions directly.
|
|
||||||
package gval
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"text/scanner"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/shopspring/decimal"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Evaluate given parameter with given expression in gval full language
|
|
||||||
func Evaluate(expression string, parameter interface{}, opts ...Language) (interface{}, error) {
|
|
||||||
return EvaluateWithContext(context.Background(), expression, parameter, opts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate given parameter with given expression in gval full language using a context
|
|
||||||
func EvaluateWithContext(c context.Context, expression string, parameter interface{}, opts ...Language) (interface{}, error) {
|
|
||||||
l := full
|
|
||||||
if len(opts) > 0 {
|
|
||||||
l = NewLanguage(append([]Language{l}, opts...)...)
|
|
||||||
}
|
|
||||||
return l.EvaluateWithContext(c, expression, parameter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Full is the union of Arithmetic, Bitmask, Text, PropositionalLogic, TernaryOperator, and Json
|
|
||||||
//
|
|
||||||
// Operator in: a in b is true iff value a is an element of array b
|
|
||||||
// Operator ??: a ?? b returns a if a is not false or nil, otherwise n
|
|
||||||
//
|
|
||||||
// Function Date: Date(a) parses string a. a must match RFC3339, ISO8601, ruby date, or unix date
|
|
||||||
func Full(extensions ...Language) Language {
|
|
||||||
if len(extensions) == 0 {
|
|
||||||
return full
|
|
||||||
}
|
|
||||||
return NewLanguage(append([]Language{full}, extensions...)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TernaryOperator contains following Operator
|
|
||||||
//
|
|
||||||
// ?: a ? b : c returns b if bool a is true, otherwise b
|
|
||||||
func TernaryOperator() Language {
|
|
||||||
return ternaryOperator
|
|
||||||
}
|
|
||||||
|
|
||||||
// Arithmetic contains base, plus(+), minus(-), divide(/), power(**), negative(-)
|
|
||||||
// and numerical order (<=,<,>,>=)
|
|
||||||
//
|
|
||||||
// Arithmetic operators expect float64 operands.
|
|
||||||
// Called with unfitting input, they try to convert the input to float64.
|
|
||||||
// They can parse strings and convert any type of int or float.
|
|
||||||
func Arithmetic() Language {
|
|
||||||
return arithmetic
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecimalArithmetic contains base, plus(+), minus(-), divide(/), power(**), negative(-)
|
|
||||||
// and numerical order (<=,<,>,>=)
|
|
||||||
//
|
|
||||||
// DecimalArithmetic operators expect decimal.Decimal operands (github.com/shopspring/decimal)
|
|
||||||
// and are used to calculate money/decimal rather than floating point calculations.
|
|
||||||
// Called with unfitting input, they try to convert the input to decimal.Decimal.
|
|
||||||
// They can parse strings and convert any type of int or float.
|
|
||||||
func DecimalArithmetic() Language {
|
|
||||||
return decimalArithmetic
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bitmask contains base, bitwise and(&), bitwise or(|) and bitwise not(^).
|
|
||||||
//
|
|
||||||
// Bitmask operators expect float64 operands.
|
|
||||||
// Called with unfitting input they try to convert the input to float64.
|
|
||||||
// They can parse strings and convert any type of int or float.
|
|
||||||
func Bitmask() Language {
|
|
||||||
return bitmask
|
|
||||||
}
|
|
||||||
|
|
||||||
// Text contains base, lexical order on strings (<=,<,>,>=),
|
|
||||||
// regex match (=~) and regex not match (!~)
|
|
||||||
func Text() Language {
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
|
|
||||||
// PropositionalLogic contains base, not(!), and (&&), or (||) and Base.
|
|
||||||
//
|
|
||||||
// Propositional operator expect bool operands.
|
|
||||||
// Called with unfitting input they try to convert the input to bool.
|
|
||||||
// Numbers other than 0 and the strings "TRUE" and "true" are interpreted as true.
|
|
||||||
// 0 and the strings "FALSE" and "false" are interpreted as false.
|
|
||||||
func PropositionalLogic() Language {
|
|
||||||
return propositionalLogic
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSON contains json objects ({string:expression,...})
|
|
||||||
// and json arrays ([expression, ...])
|
|
||||||
func JSON() Language {
|
|
||||||
return ljson
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parentheses contains support for parentheses.
|
|
||||||
func Parentheses() Language {
|
|
||||||
return parentheses
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ident contains support for variables and functions.
|
|
||||||
func Ident() Language {
|
|
||||||
return ident
|
|
||||||
}
|
|
||||||
|
|
||||||
// Base contains equal (==) and not equal (!=), perentheses and general support for variables, constants and functions
|
|
||||||
// It contains true, false, (floating point) number, string ("" or “) and char (”) constants
|
|
||||||
func Base() Language {
|
|
||||||
return base
|
|
||||||
}
|
|
||||||
|
|
||||||
var full = NewLanguage(arithmetic, bitmask, text, propositionalLogic, ljson,
|
|
||||||
|
|
||||||
InfixOperator("in", inArray),
|
|
||||||
|
|
||||||
InfixShortCircuit("??", func(a interface{}) (interface{}, bool) {
|
|
||||||
v := reflect.ValueOf(a)
|
|
||||||
return a, a != nil && !v.IsZero()
|
|
||||||
}),
|
|
||||||
InfixOperator("??", func(a, b interface{}) (interface{}, error) {
|
|
||||||
if v := reflect.ValueOf(a); a == nil || v.IsZero() {
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
return a, nil
|
|
||||||
}),
|
|
||||||
|
|
||||||
ternaryOperator,
|
|
||||||
|
|
||||||
Function("date", func(arguments ...interface{}) (interface{}, error) {
|
|
||||||
if len(arguments) != 1 {
|
|
||||||
return nil, fmt.Errorf("date() expects exactly one string argument")
|
|
||||||
}
|
|
||||||
s, ok := arguments[0].(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("date() expects exactly one string argument")
|
|
||||||
}
|
|
||||||
for _, format := range [...]string{
|
|
||||||
time.ANSIC,
|
|
||||||
time.UnixDate,
|
|
||||||
time.RubyDate,
|
|
||||||
time.Kitchen,
|
|
||||||
time.RFC3339,
|
|
||||||
time.RFC3339Nano,
|
|
||||||
"2006-01-02", // RFC 3339
|
|
||||||
"2006-01-02 15:04", // RFC 3339 with minutes
|
|
||||||
"2006-01-02 15:04:05", // RFC 3339 with seconds
|
|
||||||
"2006-01-02 15:04:05-07:00", // RFC 3339 with seconds and timezone
|
|
||||||
"2006-01-02T15Z0700", // ISO8601 with hour
|
|
||||||
"2006-01-02T15:04Z0700", // ISO8601 with minutes
|
|
||||||
"2006-01-02T15:04:05Z0700", // ISO8601 with seconds
|
|
||||||
"2006-01-02T15:04:05.999999999Z0700", // ISO8601 with nanoseconds
|
|
||||||
} {
|
|
||||||
ret, err := time.ParseInLocation(format, s, time.Local)
|
|
||||||
if err == nil {
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("date() could not parse %s", s)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
var ternaryOperator = PostfixOperator("?", parseIf)
|
|
||||||
|
|
||||||
var ljson = NewLanguage(
|
|
||||||
PrefixExtension('[', parseJSONArray),
|
|
||||||
PrefixExtension('{', parseJSONObject),
|
|
||||||
)
|
|
||||||
|
|
||||||
var arithmetic = NewLanguage(
|
|
||||||
InfixNumberOperator("+", func(a, b uint) (interface{}, error) { return a + b, nil }),
|
|
||||||
InfixNumberOperator("-", func(a, b uint) (interface{}, error) { return a - b, nil }),
|
|
||||||
InfixNumberOperator("*", func(a, b uint) (interface{}, error) { return a * b, nil }),
|
|
||||||
InfixNumberOperator("/", func(a, b uint) (interface{}, error) { return a / b, nil }),
|
|
||||||
InfixNumberOperator("%", func(a, b uint) (interface{}, error) { return a % b, nil }),
|
|
||||||
|
|
||||||
InfixNumberOperator(">", func(a, b uint) (interface{}, error) { return a > b, nil }),
|
|
||||||
InfixNumberOperator(">=", func(a, b uint) (interface{}, error) { return a >= b, nil }),
|
|
||||||
InfixNumberOperator("<", func(a, b uint) (interface{}, error) { return a < b, nil }),
|
|
||||||
InfixNumberOperator("<=", func(a, b uint) (interface{}, error) { return a <= b, nil }),
|
|
||||||
|
|
||||||
InfixNumberOperator("==", func(a, b uint) (interface{}, error) { return a == b, nil }),
|
|
||||||
InfixNumberOperator("!=", func(a, b uint) (interface{}, error) { return a != b, nil }),
|
|
||||||
|
|
||||||
base,
|
|
||||||
)
|
|
||||||
|
|
||||||
var decimalArithmetic = NewLanguage(
|
|
||||||
InfixDecimalOperator("+", func(a, b decimal.Decimal) (interface{}, error) { return a.Add(b), nil }),
|
|
||||||
InfixDecimalOperator("-", func(a, b decimal.Decimal) (interface{}, error) { return a.Sub(b), nil }),
|
|
||||||
InfixDecimalOperator("*", func(a, b decimal.Decimal) (interface{}, error) { return a.Mul(b), nil }),
|
|
||||||
InfixDecimalOperator("/", func(a, b decimal.Decimal) (interface{}, error) { return a.Div(b), nil }),
|
|
||||||
InfixDecimalOperator("%", func(a, b decimal.Decimal) (interface{}, error) { return a.Mod(b), nil }),
|
|
||||||
InfixDecimalOperator("**", func(a, b decimal.Decimal) (interface{}, error) { return a.Pow(b), nil }),
|
|
||||||
|
|
||||||
InfixDecimalOperator(">", func(a, b decimal.Decimal) (interface{}, error) { return a.GreaterThan(b), nil }),
|
|
||||||
InfixDecimalOperator(">=", func(a, b decimal.Decimal) (interface{}, error) { return a.GreaterThanOrEqual(b), nil }),
|
|
||||||
InfixDecimalOperator("<", func(a, b decimal.Decimal) (interface{}, error) { return a.LessThan(b), nil }),
|
|
||||||
InfixDecimalOperator("<=", func(a, b decimal.Decimal) (interface{}, error) { return a.LessThanOrEqual(b), nil }),
|
|
||||||
|
|
||||||
InfixDecimalOperator("==", func(a, b decimal.Decimal) (interface{}, error) { return a.Equal(b), nil }),
|
|
||||||
InfixDecimalOperator("!=", func(a, b decimal.Decimal) (interface{}, error) { return !a.Equal(b), nil }),
|
|
||||||
base,
|
|
||||||
//Base is before these overrides so that the Base options are overridden
|
|
||||||
PrefixExtension(scanner.Int, parseDecimal),
|
|
||||||
PrefixExtension(scanner.Float, parseDecimal),
|
|
||||||
PrefixOperator("-", func(c context.Context, v interface{}) (interface{}, error) {
|
|
||||||
i, ok := convertToUint(v)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unexpected %v(%T) expected number", v, v)
|
|
||||||
}
|
|
||||||
return -i, nil
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
var bitmask = NewLanguage(
|
|
||||||
InfixNumberOperator("^", func(a, b uint) (interface{}, error) { return uint(int64(a) ^ int64(b)), nil }),
|
|
||||||
InfixNumberOperator("&", func(a, b uint) (interface{}, error) { return uint(int64(a) & int64(b)), nil }),
|
|
||||||
InfixNumberOperator("|", func(a, b uint) (interface{}, error) { return uint(int64(a) | int64(b)), nil }),
|
|
||||||
InfixNumberOperator("<<", func(a, b uint) (interface{}, error) { return uint(int64(a) << uint64(b)), nil }),
|
|
||||||
InfixNumberOperator(">>", func(a, b uint) (interface{}, error) { return uint(int64(a) >> uint64(b)), nil }),
|
|
||||||
|
|
||||||
PrefixOperator("~", func(c context.Context, v interface{}) (interface{}, error) {
|
|
||||||
i, ok := convertToUint(v)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unexpected %T expected number", v)
|
|
||||||
}
|
|
||||||
return float64(^int64(i)), nil
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
var text = NewLanguage(
|
|
||||||
InfixTextOperator("+", func(a, b string) (interface{}, error) { return fmt.Sprintf("%v%v", a, b), nil }),
|
|
||||||
|
|
||||||
InfixTextOperator("<", func(a, b string) (interface{}, error) { return a < b, nil }),
|
|
||||||
InfixTextOperator("<=", func(a, b string) (interface{}, error) { return a <= b, nil }),
|
|
||||||
InfixTextOperator(">", func(a, b string) (interface{}, error) { return a > b, nil }),
|
|
||||||
InfixTextOperator(">=", func(a, b string) (interface{}, error) { return a >= b, nil }),
|
|
||||||
|
|
||||||
InfixEvalOperator("=~", regEx),
|
|
||||||
InfixEvalOperator("!~", notRegEx),
|
|
||||||
base,
|
|
||||||
)
|
|
||||||
|
|
||||||
var propositionalLogic = NewLanguage(
|
|
||||||
PrefixOperator("!", func(c context.Context, v interface{}) (interface{}, error) {
|
|
||||||
b, ok := convertToBool(v)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unexpected %T expected bool", v)
|
|
||||||
}
|
|
||||||
return !b, nil
|
|
||||||
}),
|
|
||||||
|
|
||||||
InfixShortCircuit("&&", func(a interface{}) (interface{}, bool) { return false, a == false }),
|
|
||||||
InfixBoolOperator("&&", func(a, b bool) (interface{}, error) { return a && b, nil }),
|
|
||||||
InfixShortCircuit("||", func(a interface{}) (interface{}, bool) { return true, a == true }),
|
|
||||||
InfixBoolOperator("||", func(a, b bool) (interface{}, error) { return a || b, nil }),
|
|
||||||
|
|
||||||
InfixBoolOperator("==", func(a, b bool) (interface{}, error) { return a == b, nil }),
|
|
||||||
InfixBoolOperator("!=", func(a, b bool) (interface{}, error) { return a != b, nil }),
|
|
||||||
|
|
||||||
base,
|
|
||||||
)
|
|
||||||
|
|
||||||
var parentheses = NewLanguage(
|
|
||||||
PrefixExtension('(', parseParentheses),
|
|
||||||
)
|
|
||||||
|
|
||||||
var ident = NewLanguage(
|
|
||||||
PrefixMetaPrefix(scanner.Ident, parseIdent),
|
|
||||||
)
|
|
||||||
|
|
||||||
var base = NewLanguage(
|
|
||||||
PrefixExtension(scanner.Int, parseNumber),
|
|
||||||
PrefixExtension(scanner.Float, parseNumber),
|
|
||||||
PrefixOperator("-", func(c context.Context, v interface{}) (interface{}, error) {
|
|
||||||
i, ok := convertToUint(v)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unexpected %v(%T) expected number", v, v)
|
|
||||||
}
|
|
||||||
return -i, nil
|
|
||||||
}),
|
|
||||||
|
|
||||||
PrefixExtension(scanner.String, parseString),
|
|
||||||
PrefixExtension(scanner.Char, parseString),
|
|
||||||
PrefixExtension(scanner.RawString, parseString),
|
|
||||||
|
|
||||||
Constant("true", true),
|
|
||||||
Constant("false", false),
|
|
||||||
|
|
||||||
InfixOperator("==", func(a, b interface{}) (interface{}, error) { return reflect.DeepEqual(a, b), nil }),
|
|
||||||
InfixOperator("!=", func(a, b interface{}) (interface{}, error) { return !reflect.DeepEqual(a, b), nil }),
|
|
||||||
parentheses,
|
|
||||||
|
|
||||||
Precedence("??", 0),
|
|
||||||
|
|
||||||
Precedence("||", 20),
|
|
||||||
Precedence("&&", 21),
|
|
||||||
|
|
||||||
Precedence("==", 40),
|
|
||||||
Precedence("!=", 40),
|
|
||||||
Precedence(">", 40),
|
|
||||||
Precedence(">=", 40),
|
|
||||||
Precedence("<", 40),
|
|
||||||
Precedence("<=", 40),
|
|
||||||
Precedence("=~", 40),
|
|
||||||
Precedence("!~", 40),
|
|
||||||
Precedence("in", 40),
|
|
||||||
|
|
||||||
Precedence("^", 60),
|
|
||||||
Precedence("&", 60),
|
|
||||||
Precedence("|", 60),
|
|
||||||
|
|
||||||
Precedence("<<", 90),
|
|
||||||
Precedence(">>", 90),
|
|
||||||
|
|
||||||
Precedence("+", 120),
|
|
||||||
Precedence("-", 120),
|
|
||||||
|
|
||||||
Precedence("*", 150),
|
|
||||||
Precedence("/", 150),
|
|
||||||
Precedence("%", 150),
|
|
||||||
|
|
||||||
Precedence("**", 200),
|
|
||||||
|
|
||||||
ident,
|
|
||||||
)
|
|
||||||
@ -1,396 +0,0 @@
|
|||||||
package gval
|
|
||||||
|
|
||||||
/*
|
|
||||||
Tests to make sure evaluation fails in the expected ways.
|
|
||||||
*/
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestModifierTyping(test *testing.T) {
|
|
||||||
var (
|
|
||||||
invalidOperator = "invalid operation"
|
|
||||||
unknownParameter = "unknown parameter"
|
|
||||||
invalidRegex = "error parsing regex"
|
|
||||||
tooFewArguments = "reflect: Call with too few input arguments"
|
|
||||||
tooManyArguments = "reflect: Call with too many input arguments"
|
|
||||||
mismatchedParameters = "reflect: Call using"
|
|
||||||
custom = "test error"
|
|
||||||
)
|
|
||||||
evaluationTests := []evaluationTest{
|
|
||||||
//ModifierTyping
|
|
||||||
{
|
|
||||||
name: "PLUS literal number to literal bool",
|
|
||||||
expression: "1 + true",
|
|
||||||
want: "1true", // + on string is defined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "PLUS number to bool",
|
|
||||||
expression: "number + bool",
|
|
||||||
want: "1true", // + on string is defined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "MINUS number to bool",
|
|
||||||
expression: "number - bool",
|
|
||||||
wantErr: invalidOperator,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "MINUS number to bool",
|
|
||||||
expression: "number - bool",
|
|
||||||
wantErr: invalidOperator,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "MULTIPLY number to bool",
|
|
||||||
expression: "number * bool",
|
|
||||||
wantErr: invalidOperator,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "DIVIDE number to bool",
|
|
||||||
expression: "number / bool",
|
|
||||||
wantErr: invalidOperator,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "EXPONENT number to bool",
|
|
||||||
expression: "number ** bool",
|
|
||||||
wantErr: invalidOperator,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "MODULUS number to bool",
|
|
||||||
expression: "number % bool",
|
|
||||||
wantErr: invalidOperator,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "XOR number to bool",
|
|
||||||
expression: "number % bool",
|
|
||||||
wantErr: invalidOperator,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "BITWISE_OR number to bool",
|
|
||||||
expression: "number | bool",
|
|
||||||
wantErr: invalidOperator,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "BITWISE_AND number to bool",
|
|
||||||
expression: "number & bool",
|
|
||||||
wantErr: invalidOperator,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "BITWISE_XOR number to bool",
|
|
||||||
expression: "number ^ bool",
|
|
||||||
wantErr: invalidOperator,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "BITWISE_LSHIFT number to bool",
|
|
||||||
expression: "number << bool",
|
|
||||||
wantErr: invalidOperator,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "BITWISE_RSHIFT number to bool",
|
|
||||||
expression: "number >> bool",
|
|
||||||
wantErr: invalidOperator,
|
|
||||||
},
|
|
||||||
//LogicalOperatorTyping
|
|
||||||
{
|
|
||||||
name: "AND number to number",
|
|
||||||
expression: "number && number",
|
|
||||||
want: true, // number != 0 is true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "OR number to number",
|
|
||||||
expression: "number || number",
|
|
||||||
want: true, // number != 0 is true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "AND string to string",
|
|
||||||
expression: "string && string",
|
|
||||||
wantErr: invalidOperator,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "OR string to string",
|
|
||||||
expression: "string || string",
|
|
||||||
wantErr: invalidOperator,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "AND number to string",
|
|
||||||
expression: "number && string",
|
|
||||||
wantErr: invalidOperator,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "OR number to string",
|
|
||||||
expression: "number || string",
|
|
||||||
wantErr: invalidOperator,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "AND bool to string",
|
|
||||||
expression: "bool && string",
|
|
||||||
wantErr: invalidOperator,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "OR string to bool",
|
|
||||||
expression: "string || bool",
|
|
||||||
wantErr: invalidOperator,
|
|
||||||
},
|
|
||||||
//ComparatorTyping
|
|
||||||
{
|
|
||||||
name: "GT literal bool to literal bool",
|
|
||||||
expression: "true > true",
|
|
||||||
want: false, //lexical order on "true"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "GT bool to bool",
|
|
||||||
expression: "bool > bool",
|
|
||||||
want: false, //lexical order on "true"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "GTE bool to bool",
|
|
||||||
expression: "bool >= bool",
|
|
||||||
want: true, //lexical order on "true"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "LT bool to bool",
|
|
||||||
expression: "bool < bool",
|
|
||||||
want: false, //lexical order on "true"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "LTE bool to bool",
|
|
||||||
expression: "bool <= bool",
|
|
||||||
want: true, //lexical order on "true"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "GT number to string",
|
|
||||||
expression: "number > string",
|
|
||||||
want: false, //lexical order "1" < "foo"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "GTE number to string",
|
|
||||||
expression: "number >= string",
|
|
||||||
want: false, //lexical order "1" < "foo"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "LT number to string",
|
|
||||||
expression: "number < string",
|
|
||||||
want: true, //lexical order "1" < "foo"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "REQ number to string",
|
|
||||||
expression: "number =~ string",
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "REQ number to bool",
|
|
||||||
expression: "number =~ bool",
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "REQ bool to number",
|
|
||||||
expression: "bool =~ number",
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "REQ bool to string",
|
|
||||||
expression: "bool =~ string",
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "NREQ number to string",
|
|
||||||
expression: "number !~ string",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "NREQ number to bool",
|
|
||||||
expression: "number !~ bool",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "NREQ bool to number",
|
|
||||||
expression: "bool !~ number",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "NREQ bool to string",
|
|
||||||
expression: "bool !~ string",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "IN non-array numeric",
|
|
||||||
expression: "1 in 2",
|
|
||||||
wantErr: "expected type []interface{} for in operator but got float64",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "IN non-array string",
|
|
||||||
expression: `1 in "foo"`,
|
|
||||||
wantErr: "expected type []interface{} for in operator but got string",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "IN non-array boolean",
|
|
||||||
expression: "1 in true",
|
|
||||||
wantErr: "expected type []interface{} for in operator but got bool",
|
|
||||||
},
|
|
||||||
//TernaryTyping
|
|
||||||
{
|
|
||||||
name: "Ternary with number",
|
|
||||||
expression: "10 ? true",
|
|
||||||
want: true, // 10 != nil && 10 != false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Ternary with string",
|
|
||||||
expression: `"foo" ? true`,
|
|
||||||
want: true, // "foo" != nil && "foo" != false
|
|
||||||
},
|
|
||||||
//RegexParameterCompilation
|
|
||||||
{
|
|
||||||
name: "Regex equality runtime parsing",
|
|
||||||
expression: `"foo" =~ foo`,
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": "[foo",
|
|
||||||
},
|
|
||||||
wantErr: invalidRegex,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Regex inequality runtime parsing",
|
|
||||||
expression: `"foo" !~ foo`,
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": "[foo",
|
|
||||||
},
|
|
||||||
wantErr: invalidRegex,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Regex equality runtime right side evaluation",
|
|
||||||
expression: `"foo" =~ error()`,
|
|
||||||
wantErr: custom,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Regex inequality runtime right side evaluation",
|
|
||||||
expression: `"foo" !~ error()`,
|
|
||||||
wantErr: custom,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Regex equality runtime left side evaluation",
|
|
||||||
expression: `error() =~ "."`,
|
|
||||||
wantErr: custom,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Regex inequality runtime left side evaluation",
|
|
||||||
expression: `error() !~ "."`,
|
|
||||||
wantErr: custom,
|
|
||||||
},
|
|
||||||
//FuncExecution
|
|
||||||
{
|
|
||||||
name: "Func error bubbling",
|
|
||||||
expression: "error()",
|
|
||||||
extension: Function("error", func(arguments ...interface{}) (interface{}, error) {
|
|
||||||
return nil, errors.New("Huge problems")
|
|
||||||
}),
|
|
||||||
wantErr: "Huge problems",
|
|
||||||
},
|
|
||||||
//InvalidParameterCalls
|
|
||||||
{
|
|
||||||
name: "Missing parameter field reference",
|
|
||||||
expression: "foo.NotExists",
|
|
||||||
parameter: fooFailureParameters,
|
|
||||||
wantErr: unknownParameter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Parameter method call on missing function",
|
|
||||||
expression: "foo.NotExist()",
|
|
||||||
parameter: fooFailureParameters,
|
|
||||||
wantErr: unknownParameter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Nested missing parameter field reference",
|
|
||||||
expression: "foo.Nested.NotExists",
|
|
||||||
parameter: fooFailureParameters,
|
|
||||||
wantErr: unknownParameter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Parameter method call returns error",
|
|
||||||
expression: "foo.AlwaysFail()",
|
|
||||||
parameter: fooFailureParameters,
|
|
||||||
wantErr: "function should always fail",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Too few arguments to parameter call",
|
|
||||||
expression: "foo.FuncArgStr()",
|
|
||||||
parameter: fooFailureParameters,
|
|
||||||
wantErr: tooFewArguments,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Too many arguments to parameter call",
|
|
||||||
expression: `foo.FuncArgStr("foo", "bar", 15)`,
|
|
||||||
parameter: fooFailureParameters,
|
|
||||||
wantErr: tooManyArguments,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Mismatched parameters",
|
|
||||||
expression: "foo.FuncArgStr(5)",
|
|
||||||
parameter: fooFailureParameters,
|
|
||||||
wantErr: mismatchedParameters,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Negative Array Index",
|
|
||||||
expression: "foo[-1]",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": []int{1, 2, 3},
|
|
||||||
},
|
|
||||||
wantErr: unknownParameter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Nested slice call index out of bound",
|
|
||||||
expression: `foo.Nested.Slice[10]`,
|
|
||||||
parameter: map[string]interface{}{"foo": foo},
|
|
||||||
wantErr: unknownParameter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Nested map call missing key",
|
|
||||||
expression: `foo.Nested.Map["d"]`,
|
|
||||||
parameter: map[string]interface{}{"foo": foo},
|
|
||||||
wantErr: unknownParameter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid selector",
|
|
||||||
expression: "hello[world()]",
|
|
||||||
extension: NewLanguage(Base(), Function("world", func() (int, error) {
|
|
||||||
return 0, fmt.Errorf("test error")
|
|
||||||
})),
|
|
||||||
wantErr: "test error",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "eval `nil > 1` returns true #23",
|
|
||||||
expression: `nil > 1`,
|
|
||||||
wantErr: "invalid operation (<nil>) > (float64)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "map with unknown func",
|
|
||||||
expression: `foo.MapWithFunc.NotExist()`,
|
|
||||||
parameter: map[string]interface{}{"foo": foo},
|
|
||||||
wantErr: unknownParameter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "map with unknown func",
|
|
||||||
expression: `foo.SliceWithFunc.NotExist()`,
|
|
||||||
parameter: map[string]interface{}{"foo": foo},
|
|
||||||
wantErr: unknownParameter,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range evaluationTests {
|
|
||||||
if evaluationTests[i].parameter == nil {
|
|
||||||
evaluationTests[i].parameter = map[string]interface{}{
|
|
||||||
"number": 1,
|
|
||||||
"string": "foo",
|
|
||||||
"bool": true,
|
|
||||||
"error": func() (int, error) {
|
|
||||||
return 0, fmt.Errorf("test error")
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
testEvaluate(evaluationTests, test)
|
|
||||||
}
|
|
||||||
@ -1,818 +0,0 @@
|
|||||||
package gval
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
"text/scanner"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNoParameter(t *testing.T) {
|
|
||||||
testEvaluate(
|
|
||||||
[]evaluationTest{
|
|
||||||
{
|
|
||||||
name: "Number",
|
|
||||||
expression: "100",
|
|
||||||
want: 100.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Single PLUS",
|
|
||||||
expression: "51 + 49",
|
|
||||||
want: 100.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Single MINUS",
|
|
||||||
expression: "100 - 51",
|
|
||||||
want: 49.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Single BITWISE AND",
|
|
||||||
expression: "100 & 50",
|
|
||||||
want: 32.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Single BITWISE OR",
|
|
||||||
expression: "100 | 50",
|
|
||||||
want: 118.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Single BITWISE XOR",
|
|
||||||
expression: "100 ^ 50",
|
|
||||||
want: 86.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Single shift left",
|
|
||||||
expression: "2 << 1",
|
|
||||||
want: 4.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Single shift right",
|
|
||||||
expression: "2 >> 1",
|
|
||||||
want: 1.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Single BITWISE NOT",
|
|
||||||
expression: "~10",
|
|
||||||
want: -11.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Single MULTIPLY",
|
|
||||||
expression: "5 * 20",
|
|
||||||
want: 100.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Single DIVIDE",
|
|
||||||
expression: "100 / 20",
|
|
||||||
want: 5.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Single even MODULUS",
|
|
||||||
expression: "100 % 2",
|
|
||||||
want: 0.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Single odd MODULUS",
|
|
||||||
expression: "101 % 2",
|
|
||||||
want: 1.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Single EXPONENT",
|
|
||||||
expression: "10 ** 2",
|
|
||||||
want: 100.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Compound PLUS",
|
|
||||||
expression: "20 + 30 + 50",
|
|
||||||
want: 100.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Compound BITWISE AND",
|
|
||||||
expression: "20 & 30 & 50",
|
|
||||||
want: 16.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Mutiple operators",
|
|
||||||
expression: "20 * 5 - 49",
|
|
||||||
want: 51.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Parenthesis usage",
|
|
||||||
expression: "100 - (5 * 10)",
|
|
||||||
want: 50.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Nested parentheses",
|
|
||||||
expression: "50 + (5 * (15 - 5))",
|
|
||||||
want: 100.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Nested parentheses with bitwise",
|
|
||||||
expression: "100 ^ (23 * (2 | 5))",
|
|
||||||
want: 197.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Logical OR operation of two clauses",
|
|
||||||
expression: "(1 == 1) || (true == true)",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Logical AND operation of two clauses",
|
|
||||||
expression: "(1 == 1) && (true == true)",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Implicit boolean",
|
|
||||||
expression: "2 > 1",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Equal test minus numbers and no spaces",
|
|
||||||
expression: "-1==-1",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Compound boolean",
|
|
||||||
expression: "5 < 10 && 1 < 5",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Evaluated true && false operation (for issue #8)",
|
|
||||||
expression: "1 > 10 && 11 > 10",
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Evaluated true && false operation (for issue #8)",
|
|
||||||
expression: "true == true && false == true",
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Parenthesis boolean",
|
|
||||||
expression: "10 < 50 && (1 != 2 && 1 > 0)",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Comparison of string constants",
|
|
||||||
expression: `"foo" == "foo"`,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "NEQ comparison of string constants",
|
|
||||||
expression: `"foo" != "bar"`,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "REQ comparison of string constants",
|
|
||||||
expression: `"foobar" =~ "oba"`,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "NREQ comparison of string constants",
|
|
||||||
expression: `"foo" !~ "bar"`,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Multiplicative/additive order",
|
|
||||||
expression: "5 + 10 * 2",
|
|
||||||
want: 25.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Multiple constant multiplications",
|
|
||||||
expression: "10 * 10 * 10",
|
|
||||||
want: 1000.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Multiple adds/multiplications",
|
|
||||||
expression: "10 * 10 * 10 + 1 * 10 * 10",
|
|
||||||
want: 1100.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Modulus operatorPrecedence",
|
|
||||||
expression: "1 + 101 % 2 * 5",
|
|
||||||
want: 6.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Exponent operatorPrecedence",
|
|
||||||
expression: "1 + 5 ** 3 % 2 * 5",
|
|
||||||
want: 6.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Bit shift operatorPrecedence",
|
|
||||||
expression: "50 << 1 & 90",
|
|
||||||
want: 64.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Bit shift operatorPrecedence",
|
|
||||||
expression: "90 & 50 << 1",
|
|
||||||
want: 64.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Bit shift operatorPrecedence amongst non-bitwise",
|
|
||||||
expression: "90 + 50 << 1 * 5",
|
|
||||||
want: 4480.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Order of non-commutative same-operatorPrecedence operators (additive)",
|
|
||||||
expression: "1 - 2 - 4 - 8",
|
|
||||||
want: -13.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Order of non-commutative same-operatorPrecedence operators (multiplicative)",
|
|
||||||
expression: "1 * 4 / 2 * 8",
|
|
||||||
want: 16.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Null coalesce operatorPrecedence",
|
|
||||||
expression: "true ?? true ? 100 + 200 : 400",
|
|
||||||
want: 300.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Identical date equivalence",
|
|
||||||
expression: `"2014-01-02 14:12:22" == "2014-01-02 14:12:22"`,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Positive date GT",
|
|
||||||
expression: `"2014-01-02 14:12:22" > "2014-01-02 12:12:22"`,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Negative date GT",
|
|
||||||
expression: `"2014-01-02 14:12:22" > "2014-01-02 16:12:22"`,
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Positive date GTE",
|
|
||||||
expression: `"2014-01-02 14:12:22" >= "2014-01-02 12:12:22"`,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Negative date GTE",
|
|
||||||
expression: `"2014-01-02 14:12:22" >= "2014-01-02 16:12:22"`,
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Positive date LT",
|
|
||||||
expression: `"2014-01-02 14:12:22" < "2014-01-02 16:12:22"`,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Negative date LT",
|
|
||||||
expression: `"2014-01-02 14:12:22" < "2014-01-02 11:12:22"`,
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Positive date LTE",
|
|
||||||
expression: `"2014-01-02 09:12:22" <= "2014-01-02 12:12:22"`,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Negative date LTE",
|
|
||||||
expression: `"2014-01-02 14:12:22" <= "2014-01-02 11:12:22"`,
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Sign prefix comparison",
|
|
||||||
expression: "-1 < 0",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Lexicographic LT",
|
|
||||||
expression: `"ab" < "abc"`,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Lexicographic LTE",
|
|
||||||
expression: `"ab" <= "abc"`,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Lexicographic GT",
|
|
||||||
expression: `"aba" > "abc"`,
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Lexicographic GTE",
|
|
||||||
expression: `"aba" >= "abc"`,
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Boolean sign prefix comparison",
|
|
||||||
expression: "!true == false",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Inversion of clause",
|
|
||||||
expression: "!(10 < 0)",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Negation after modifier",
|
|
||||||
expression: "10 * -10",
|
|
||||||
want: -100.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Ternary with single boolean",
|
|
||||||
expression: "true ? 10",
|
|
||||||
want: 10.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Ternary nil with single boolean",
|
|
||||||
expression: "false ? 10",
|
|
||||||
want: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Ternary with comparator boolean",
|
|
||||||
expression: "10 > 5 ? 35.50",
|
|
||||||
want: 35.50,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Ternary nil with comparator boolean",
|
|
||||||
expression: "1 > 5 ? 35.50",
|
|
||||||
want: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Ternary with parentheses",
|
|
||||||
expression: "(5 * (15 - 5)) > 5 ? 35.50",
|
|
||||||
want: 35.50,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Ternary operatorPrecedence",
|
|
||||||
expression: "true ? 35.50 > 10",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Ternary-else",
|
|
||||||
expression: "false ? 35.50 : 50",
|
|
||||||
want: 50.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Ternary-else inside clause",
|
|
||||||
expression: "(false ? 5 : 35.50) > 10",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Ternary-else (true-case) inside clause",
|
|
||||||
expression: "(true ? 1 : 5) < 10",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Ternary-else before comparator (negative case)",
|
|
||||||
expression: "true ? 1 : 5 > 10",
|
|
||||||
want: 1.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Nested ternaries (#32)",
|
|
||||||
expression: "(2 == 2) ? 1 : (true ? 2 : 3)",
|
|
||||||
want: 1.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Nested ternaries, right case (#32)",
|
|
||||||
expression: "false ? 1 : (true ? 2 : 3)",
|
|
||||||
want: 2.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Doubly-nested ternaries (#32)",
|
|
||||||
expression: "true ? (false ? 1 : (false ? 2 : 3)) : (false ? 4 : 5)",
|
|
||||||
want: 3.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "String to string concat",
|
|
||||||
expression: `"foo" + "bar" == "foobar"`,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "String to float64 concat",
|
|
||||||
expression: `"foo" + 123 == "foo123"`,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Float64 to string concat",
|
|
||||||
expression: `123 + "bar" == "123bar"`,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "String to date concat",
|
|
||||||
expression: `"foo" + "02/05/1970" == "foobar"`,
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "String to bool concat",
|
|
||||||
expression: `"foo" + true == "footrue"`,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Bool to string concat",
|
|
||||||
expression: `true + "bar" == "truebar"`,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Null coalesce left",
|
|
||||||
expression: "1 ?? 2",
|
|
||||||
want: 1.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Array membership literals",
|
|
||||||
expression: "1 in [1, 2, 3]",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Array membership literal with inversion",
|
|
||||||
expression: "!(1 in [1, 2, 3])",
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Logical operator reordering (#30)",
|
|
||||||
expression: "(true && true) || (true && false)",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Logical operator reordering without parens (#30)",
|
|
||||||
expression: "true && true || true && false",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Logical operator reordering with multiple OR (#30)",
|
|
||||||
expression: "false || true && true || false",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Left-side multiple consecutive (should be reordered) operators",
|
|
||||||
expression: "(10 * 10 * 10) > 10",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Three-part non-paren logical op reordering (#44)",
|
|
||||||
expression: "false && true || true",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Three-part non-paren logical op reordering (#44), second one",
|
|
||||||
expression: "true || false && true",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Logical operator reordering without parens (#45)",
|
|
||||||
expression: "true && true || false && false",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Single function",
|
|
||||||
expression: "foo()",
|
|
||||||
extension: Function("foo", func(arguments ...interface{}) (interface{}, error) {
|
|
||||||
return true, nil
|
|
||||||
}),
|
|
||||||
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Func with argument",
|
|
||||||
expression: "passthrough(1)",
|
|
||||||
extension: Function("passthrough", func(arguments ...interface{}) (interface{}, error) {
|
|
||||||
return arguments[0], nil
|
|
||||||
}),
|
|
||||||
want: 1.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Func with arguments",
|
|
||||||
expression: "passthrough(1, 2)",
|
|
||||||
extension: Function("passthrough", func(arguments ...interface{}) (interface{}, error) {
|
|
||||||
return arguments[0].(float64) + arguments[1].(float64), nil
|
|
||||||
}),
|
|
||||||
want: 3.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Nested function with operatorPrecedence",
|
|
||||||
expression: "sum(1, sum(2, 3), 2 + 2, true ? 4 : 5)",
|
|
||||||
extension: Function("sum", func(arguments ...interface{}) (interface{}, error) {
|
|
||||||
sum := 0.0
|
|
||||||
for _, v := range arguments {
|
|
||||||
sum += v.(float64)
|
|
||||||
}
|
|
||||||
return sum, nil
|
|
||||||
}),
|
|
||||||
want: 14.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Empty function and modifier, compared",
|
|
||||||
expression: "numeric()-1 > 0",
|
|
||||||
extension: Function("numeric", func(arguments ...interface{}) (interface{}, error) {
|
|
||||||
return 2.0, nil
|
|
||||||
}),
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Empty function comparator",
|
|
||||||
expression: "numeric() > 0",
|
|
||||||
extension: Function("numeric", func(arguments ...interface{}) (interface{}, error) {
|
|
||||||
return 2.0, nil
|
|
||||||
}),
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Empty function logical operator",
|
|
||||||
expression: "success() && !false",
|
|
||||||
extension: Function("success", func(arguments ...interface{}) (interface{}, error) {
|
|
||||||
return true, nil
|
|
||||||
}),
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Empty function ternary",
|
|
||||||
expression: "nope() ? 1 : 2.0",
|
|
||||||
extension: Function("nope", func(arguments ...interface{}) (interface{}, error) {
|
|
||||||
return false, nil
|
|
||||||
}),
|
|
||||||
want: 2.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Empty function null coalesce",
|
|
||||||
expression: "null() ?? 2",
|
|
||||||
extension: Function("null", func(arguments ...interface{}) (interface{}, error) {
|
|
||||||
return nil, nil
|
|
||||||
}),
|
|
||||||
want: 2.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Empty function with prefix",
|
|
||||||
expression: "-ten()",
|
|
||||||
extension: Function("ten", func(arguments ...interface{}) (interface{}, error) {
|
|
||||||
return 10.0, nil
|
|
||||||
}),
|
|
||||||
want: -10.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Empty function as part of chain",
|
|
||||||
expression: "10 - numeric() - 2",
|
|
||||||
extension: Function("numeric", func(arguments ...interface{}) (interface{}, error) {
|
|
||||||
return 5.0, nil
|
|
||||||
}),
|
|
||||||
want: 3.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Empty function near separator",
|
|
||||||
expression: "10 in [1, 2, 3, ten(), 8]",
|
|
||||||
extension: Function("ten", func(arguments ...interface{}) (interface{}, error) {
|
|
||||||
return 10.0, nil
|
|
||||||
}),
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Enclosed empty function with modifier and comparator (#28)",
|
|
||||||
expression: "(ten() - 1) > 3",
|
|
||||||
extension: Function("ten", func(arguments ...interface{}) (interface{}, error) {
|
|
||||||
return 10.0, nil
|
|
||||||
}),
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Array",
|
|
||||||
expression: `[(ten() - 1) > 3, (ten() - 1),"hey"]`,
|
|
||||||
extension: Function("ten", func(arguments ...interface{}) (interface{}, error) {
|
|
||||||
return 10.0, nil
|
|
||||||
}),
|
|
||||||
want: []interface{}{true, 9., "hey"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Object",
|
|
||||||
expression: `{1: (ten() - 1) > 3, 7 + ".X" : (ten() - 1),"hello" : "hey"}`,
|
|
||||||
extension: Function("ten", func(arguments ...interface{}) (interface{}, error) {
|
|
||||||
return 10.0, nil
|
|
||||||
}),
|
|
||||||
want: map[string]interface{}{"1": true, "7.X": 9., "hello": "hey"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Object negativ value",
|
|
||||||
expression: `{1: -1,"hello" : "hey"}`,
|
|
||||||
want: map[string]interface{}{"1": -1., "hello": "hey"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Empty Array",
|
|
||||||
expression: `[]`,
|
|
||||||
want: []interface{}{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Empty Object",
|
|
||||||
expression: `{}`,
|
|
||||||
want: map[string]interface{}{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Variadic",
|
|
||||||
expression: `sum(1,2,3,4)`,
|
|
||||||
extension: Function("sum", func(arguments ...float64) (interface{}, error) {
|
|
||||||
sum := 0.
|
|
||||||
for _, a := range arguments {
|
|
||||||
sum += a
|
|
||||||
}
|
|
||||||
return sum, nil
|
|
||||||
}),
|
|
||||||
want: 10.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Ident Operator",
|
|
||||||
expression: `1 plus 1`,
|
|
||||||
extension: InfixNumberOperator("plus", func(a, b float64) (interface{}, error) {
|
|
||||||
return a + b, nil
|
|
||||||
}),
|
|
||||||
want: 2.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Postfix Operator",
|
|
||||||
expression: `4§`,
|
|
||||||
extension: PostfixOperator("§", func(_ context.Context, _ *Parser, eval Evaluable) (Evaluable, error) {
|
|
||||||
return func(ctx context.Context, parameter interface{}) (interface{}, error) {
|
|
||||||
i, err := eval.EvalInt(ctx, parameter)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("§%d", i), nil
|
|
||||||
}, nil
|
|
||||||
}),
|
|
||||||
want: "§4",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Tabs as non-whitespace",
|
|
||||||
expression: "4\t5\t6",
|
|
||||||
extension: NewLanguage(
|
|
||||||
Init(func(ctx context.Context, p *Parser) (Evaluable, error) {
|
|
||||||
p.SetWhitespace('\n', '\r', ' ')
|
|
||||||
return p.ParseExpression(ctx)
|
|
||||||
}),
|
|
||||||
InfixNumberOperator("\t", func(a, b float64) (interface{}, error) {
|
|
||||||
return a * b, nil
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
want: 120.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Handle all other prefixes",
|
|
||||||
expression: "^foo + $bar + &baz",
|
|
||||||
extension: DefaultExtension(func(ctx context.Context, p *Parser) (Evaluable, error) {
|
|
||||||
var mul int
|
|
||||||
switch p.TokenText() {
|
|
||||||
case "^":
|
|
||||||
mul = 1
|
|
||||||
case "$":
|
|
||||||
mul = 2
|
|
||||||
case "&":
|
|
||||||
mul = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
switch p.Scan() {
|
|
||||||
case scanner.Ident:
|
|
||||||
return p.Const(mul * len(p.TokenText())), nil
|
|
||||||
default:
|
|
||||||
return nil, p.Expected("length multiplier", scanner.Ident)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
want: 18.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Embed languages",
|
|
||||||
expression: "left { 5 + 5 } right",
|
|
||||||
extension: func() Language {
|
|
||||||
step := func(ctx context.Context, p *Parser, cur Evaluable) (Evaluable, error) {
|
|
||||||
next, err := p.ParseExpression(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return func(ctx context.Context, parameter interface{}) (interface{}, error) {
|
|
||||||
us, err := cur.EvalString(ctx, parameter)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
them, err := next.EvalString(ctx, parameter)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return us + them, nil
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewLanguage(
|
|
||||||
Init(func(ctx context.Context, p *Parser) (Evaluable, error) {
|
|
||||||
p.SetWhitespace()
|
|
||||||
p.SetMode(0)
|
|
||||||
|
|
||||||
return p.ParseExpression(ctx)
|
|
||||||
}),
|
|
||||||
DefaultExtension(func(ctx context.Context, p *Parser) (Evaluable, error) {
|
|
||||||
return step(ctx, p, p.Const(p.TokenText()))
|
|
||||||
}),
|
|
||||||
PrefixExtension(scanner.EOF, func(ctx context.Context, p *Parser) (Evaluable, error) {
|
|
||||||
return p.Const(""), nil
|
|
||||||
}),
|
|
||||||
PrefixExtension('{', func(ctx context.Context, p *Parser) (Evaluable, error) {
|
|
||||||
eval, err := p.ParseSublanguage(ctx, Full())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch p.Scan() {
|
|
||||||
case '}':
|
|
||||||
default:
|
|
||||||
return nil, p.Expected("embedded", '}')
|
|
||||||
}
|
|
||||||
|
|
||||||
return step(ctx, p, eval)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}(),
|
|
||||||
want: "left 10 right",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Late binding",
|
|
||||||
expression: "5 * [ 10 * { 20 / [ 10 ] } ]",
|
|
||||||
extension: func() Language {
|
|
||||||
var inner, outer Language
|
|
||||||
|
|
||||||
parseCurly := func(ctx context.Context, p *Parser) (Evaluable, error) {
|
|
||||||
eval, err := p.ParseSublanguage(ctx, outer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.Scan() != '}' {
|
|
||||||
return nil, p.Expected("end", '}')
|
|
||||||
}
|
|
||||||
|
|
||||||
return eval, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
parseSquare := func(ctx context.Context, p *Parser) (Evaluable, error) {
|
|
||||||
eval, err := p.ParseSublanguage(ctx, inner)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.Scan() != ']' {
|
|
||||||
return nil, p.Expected("end", ']')
|
|
||||||
}
|
|
||||||
|
|
||||||
return eval, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
inner = Full(PrefixExtension('{', parseCurly))
|
|
||||||
outer = Full(PrefixExtension('[', parseSquare))
|
|
||||||
return outer
|
|
||||||
}(),
|
|
||||||
want: 100.0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
t,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,719 +0,0 @@
|
|||||||
package gval
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/shopspring/decimal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParameterized(t *testing.T) {
|
|
||||||
testEvaluate(
|
|
||||||
[]evaluationTest{
|
|
||||||
{
|
|
||||||
name: "Single parameter modified by constant",
|
|
||||||
expression: "foo + 2",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": 2.0,
|
|
||||||
},
|
|
||||||
want: 4.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Single parameter modified by variable",
|
|
||||||
expression: "foo * bar",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": 5.0,
|
|
||||||
"bar": 2.0,
|
|
||||||
},
|
|
||||||
want: 10.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Single parameter modified by variable",
|
|
||||||
expression: `foo["hey"] * bar[1]`,
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": map[string]interface{}{"hey": 5.0},
|
|
||||||
"bar": []interface{}{7., 2.0},
|
|
||||||
},
|
|
||||||
want: 10.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Multiple multiplications of the same parameter",
|
|
||||||
expression: "foo * foo * foo",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": 10.0,
|
|
||||||
},
|
|
||||||
want: 1000.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Multiple additions of the same parameter",
|
|
||||||
expression: "foo + foo + foo",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": 10.0,
|
|
||||||
},
|
|
||||||
want: 30.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "NoSpaceOperator",
|
|
||||||
expression: "true&&name",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"name": true,
|
|
||||||
},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Parameter name sensitivity",
|
|
||||||
expression: "foo + FoO + FOO",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": 8.0,
|
|
||||||
"FoO": 4.0,
|
|
||||||
"FOO": 2.0,
|
|
||||||
},
|
|
||||||
want: 14.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Sign prefix comparison against prefixed variable",
|
|
||||||
expression: "-1 < -foo",
|
|
||||||
parameter: map[string]interface{}{"foo": -8.0},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Fixed-point parameter",
|
|
||||||
expression: "foo > 1",
|
|
||||||
parameter: map[string]interface{}{"foo": 2},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Modifier after closing clause",
|
|
||||||
expression: "(2 + 2) + 2 == 6",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Comparator after closing clause",
|
|
||||||
expression: "(2 + 2) >= 4",
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Two-boolean logical operation (for issue #8)",
|
|
||||||
expression: "(foo == true) || (bar == true)",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": true,
|
|
||||||
"bar": false,
|
|
||||||
},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Two-variable integer logical operation (for issue #8)",
|
|
||||||
expression: "foo > 10 && bar > 10",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": 1,
|
|
||||||
"bar": 11,
|
|
||||||
},
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Regex against right-hand parameter",
|
|
||||||
expression: `"foobar" =~ foo`,
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": "obar",
|
|
||||||
},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Not-regex against right-hand parameter",
|
|
||||||
expression: `"foobar" !~ foo`,
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": "baz",
|
|
||||||
},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Regex against two parameter",
|
|
||||||
expression: `foo =~ bar`,
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": "foobar",
|
|
||||||
"bar": "oba",
|
|
||||||
},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Not-regex against two parameter",
|
|
||||||
expression: "foo !~ bar",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": "foobar",
|
|
||||||
"bar": "baz",
|
|
||||||
},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Pre-compiled regex",
|
|
||||||
expression: "foo =~ bar",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": "foobar",
|
|
||||||
"bar": regexp.MustCompile("[fF][oO]+"),
|
|
||||||
},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Pre-compiled not-regex",
|
|
||||||
expression: "foo !~ bar",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": "foobar",
|
|
||||||
"bar": regexp.MustCompile("[fF][oO]+"),
|
|
||||||
},
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Single boolean parameter",
|
|
||||||
expression: "commission ? 10",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"commission": true},
|
|
||||||
want: 10.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "True comparator with a parameter",
|
|
||||||
expression: `partner == "amazon" ? 10`,
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"partner": "amazon"},
|
|
||||||
want: 10.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "False comparator with a parameter",
|
|
||||||
expression: `partner == "amazon" ? 10`,
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"partner": "ebay"},
|
|
||||||
want: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "True comparator with multiple parameters",
|
|
||||||
expression: "theft && period == 24 ? 60",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"theft": true,
|
|
||||||
"period": 24,
|
|
||||||
},
|
|
||||||
want: 60.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "False comparator with multiple parameters",
|
|
||||||
expression: "theft && period == 24 ? 60",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"theft": false,
|
|
||||||
"period": 24,
|
|
||||||
},
|
|
||||||
want: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "String concat with single string parameter",
|
|
||||||
expression: `foo + "bar"`,
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": "baz"},
|
|
||||||
want: "bazbar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "String concat with multiple string parameter",
|
|
||||||
expression: "foo + bar",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": "baz",
|
|
||||||
"bar": "quux",
|
|
||||||
},
|
|
||||||
want: "bazquux",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "String concat with float parameter",
|
|
||||||
expression: "foo + bar",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": "baz",
|
|
||||||
"bar": 123.0,
|
|
||||||
},
|
|
||||||
want: "baz123",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Mixed multiple string concat",
|
|
||||||
expression: `foo + 123 + "bar" + true`,
|
|
||||||
parameter: map[string]interface{}{"foo": "baz"},
|
|
||||||
want: "baz123bartrue",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Integer width spectrum",
|
|
||||||
expression: "uint8 + uint16 + uint32 + uint64 + int8 + int16 + int32 + int64",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"uint8": uint8(0),
|
|
||||||
"uint16": uint16(0),
|
|
||||||
"uint32": uint32(0),
|
|
||||||
"uint64": uint64(0),
|
|
||||||
"int8": int8(0),
|
|
||||||
"int16": int16(0),
|
|
||||||
"int32": int32(0),
|
|
||||||
"int64": int64(0),
|
|
||||||
},
|
|
||||||
want: 0.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Null coalesce right",
|
|
||||||
expression: "foo ?? 1.0",
|
|
||||||
parameter: map[string]interface{}{"foo": nil},
|
|
||||||
want: 1.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Multiple comparator/logical operators (#30)",
|
|
||||||
expression: "(foo >= 2887057408 && foo <= 2887122943) || (foo >= 168100864 && foo <= 168118271)",
|
|
||||||
parameter: map[string]interface{}{"foo": 2887057409},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Multiple comparator/logical operators, opposite order (#30)",
|
|
||||||
expression: "(foo >= 168100864 && foo <= 168118271) || (foo >= 2887057408 && foo <= 2887122943)",
|
|
||||||
parameter: map[string]interface{}{"foo": 2887057409},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Multiple comparator/logical operators, small value (#30)",
|
|
||||||
expression: "(foo >= 2887057408 && foo <= 2887122943) || (foo >= 168100864 && foo <= 168118271)",
|
|
||||||
parameter: map[string]interface{}{"foo": 168100865},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Multiple comparator/logical operators, small value, opposite order (#30)",
|
|
||||||
expression: "(foo >= 168100864 && foo <= 168118271) || (foo >= 2887057408 && foo <= 2887122943)",
|
|
||||||
parameter: map[string]interface{}{"foo": 168100865},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Incomparable array equality comparison",
|
|
||||||
expression: "arr == arr",
|
|
||||||
parameter: map[string]interface{}{"arr": []int{0, 0, 0}},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Incomparable array not-equality comparison",
|
|
||||||
expression: "arr != arr",
|
|
||||||
parameter: map[string]interface{}{"arr": []int{0, 0, 0}},
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Mixed function and parameters",
|
|
||||||
expression: "sum(1.2, amount) + name",
|
|
||||||
extension: Function("sum", func(arguments ...interface{}) (interface{}, error) {
|
|
||||||
sum := 0.0
|
|
||||||
for _, v := range arguments {
|
|
||||||
sum += v.(float64)
|
|
||||||
}
|
|
||||||
return sum, nil
|
|
||||||
},
|
|
||||||
),
|
|
||||||
parameter: map[string]interface{}{"amount": .8,
|
|
||||||
"name": "awesome",
|
|
||||||
},
|
|
||||||
|
|
||||||
want: "2awesome",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Short-circuit OR",
|
|
||||||
expression: "true || fail()",
|
|
||||||
extension: Function("fail", func(arguments ...interface{}) (interface{}, error) {
|
|
||||||
return nil, fmt.Errorf("Did not short-circuit")
|
|
||||||
}),
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Short-circuit AND",
|
|
||||||
expression: "false && fail()",
|
|
||||||
extension: Function("fail", func(arguments ...interface{}) (interface{}, error) {
|
|
||||||
return nil, fmt.Errorf("Did not short-circuit")
|
|
||||||
}),
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Short-circuit ternary",
|
|
||||||
expression: "true ? 1 : fail()",
|
|
||||||
extension: Function("fail", func(arguments ...interface{}) (interface{}, error) {
|
|
||||||
return nil, fmt.Errorf("Did not short-circuit")
|
|
||||||
}),
|
|
||||||
want: 1.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Short-circuit coalesce",
|
|
||||||
expression: `"foo" ?? fail()`,
|
|
||||||
extension: Function("fail", func(arguments ...interface{}) (interface{}, error) {
|
|
||||||
return nil, fmt.Errorf("Did not short-circuit")
|
|
||||||
}),
|
|
||||||
want: "foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Simple parameter call",
|
|
||||||
expression: "foo.String",
|
|
||||||
parameter: map[string]interface{}{"foo": foo},
|
|
||||||
want: foo.String,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Simple parameter function call",
|
|
||||||
expression: "foo.Func()",
|
|
||||||
parameter: map[string]interface{}{"foo": foo},
|
|
||||||
want: "funk",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Simple parameter call from pointer",
|
|
||||||
expression: "fooptr.String",
|
|
||||||
parameter: map[string]interface{}{"fooptr": &foo},
|
|
||||||
want: foo.String,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Simple parameter function call from pointer",
|
|
||||||
expression: "fooptr.Func()",
|
|
||||||
parameter: map[string]interface{}{"fooptr": &foo},
|
|
||||||
want: "funk",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Simple parameter call",
|
|
||||||
expression: `foo.String == "hi"`,
|
|
||||||
parameter: map[string]interface{}{"foo": foo},
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Simple parameter call with modifier",
|
|
||||||
expression: `foo.String + "hi"`,
|
|
||||||
parameter: map[string]interface{}{"foo": foo},
|
|
||||||
want: foo.String + "hi",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Simple parameter function call, two-arg return",
|
|
||||||
expression: `foo.Func2()`,
|
|
||||||
parameter: map[string]interface{}{"foo": foo},
|
|
||||||
want: "frink",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Simple parameter function call, one arg",
|
|
||||||
expression: `foo.FuncArgStr("boop")`,
|
|
||||||
parameter: map[string]interface{}{"foo": foo},
|
|
||||||
want: "boop",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Simple parameter function call, one arg",
|
|
||||||
expression: `foo.FuncArgStr("boop") + "hi"`,
|
|
||||||
parameter: map[string]interface{}{"foo": foo},
|
|
||||||
want: "boophi",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Nested parameter function call",
|
|
||||||
expression: `foo.Nested.Dunk("boop")`,
|
|
||||||
parameter: map[string]interface{}{"foo": foo},
|
|
||||||
want: "boopdunk",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Nested parameter call",
|
|
||||||
expression: "foo.Nested.Funk",
|
|
||||||
parameter: map[string]interface{}{"foo": foo},
|
|
||||||
want: "funkalicious",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Nested map call",
|
|
||||||
expression: `foo.Nested.Map["a"]`,
|
|
||||||
parameter: map[string]interface{}{"foo": foo},
|
|
||||||
want: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Nested slice call",
|
|
||||||
expression: `foo.Nested.Slice[1]`,
|
|
||||||
parameter: map[string]interface{}{"foo": foo},
|
|
||||||
want: 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Parameter call with + modifier",
|
|
||||||
expression: "1 + foo.Int",
|
|
||||||
parameter: map[string]interface{}{"foo": foo},
|
|
||||||
want: 102.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Parameter string call with + modifier",
|
|
||||||
expression: `"woop" + (foo.String)`,
|
|
||||||
parameter: map[string]interface{}{"foo": foo},
|
|
||||||
want: "woopstring!",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Parameter call with && operator",
|
|
||||||
expression: "true && foo.BoolFalse",
|
|
||||||
parameter: map[string]interface{}{"foo": foo},
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Null coalesce nested parameter",
|
|
||||||
expression: "foo.Nil ?? false",
|
|
||||||
parameter: map[string]interface{}{"foo": foo},
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "input functions",
|
|
||||||
expression: "func1() + func2()",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"func1": func() float64 { return 2000 },
|
|
||||||
"func2": func() float64 { return 2001 },
|
|
||||||
},
|
|
||||||
want: 4001.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "input functions",
|
|
||||||
expression: "func1(date1) + func2(date2)",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"date1": func() interface{} {
|
|
||||||
y2k, _ := time.Parse("2006", "2000")
|
|
||||||
return y2k
|
|
||||||
}(),
|
|
||||||
"date2": func() interface{} {
|
|
||||||
y2k1, _ := time.Parse("2006", "2001")
|
|
||||||
return y2k1
|
|
||||||
}(),
|
|
||||||
},
|
|
||||||
extension: NewLanguage(
|
|
||||||
Function("func1", func(arguments ...interface{}) (interface{}, error) {
|
|
||||||
return float64(arguments[0].(time.Time).Year()), nil
|
|
||||||
}),
|
|
||||||
Function("func2", func(arguments ...interface{}) (interface{}, error) {
|
|
||||||
return float64(arguments[0].(time.Time).Year()), nil
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
want: 4001.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "complex64 number as parameter",
|
|
||||||
expression: "complex64",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"complex64": complex64(0),
|
|
||||||
"complex128": complex128(0),
|
|
||||||
},
|
|
||||||
want: complex64(0),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "complex128 number as parameter",
|
|
||||||
expression: "complex128",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"complex64": complex64(0),
|
|
||||||
"complex128": complex128(0),
|
|
||||||
},
|
|
||||||
want: complex128(0),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "coalesce with undefined",
|
|
||||||
expression: "fooz ?? foo",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": "bar",
|
|
||||||
},
|
|
||||||
want: "bar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "map[interface{}]interface{}",
|
|
||||||
expression: "foo",
|
|
||||||
parameter: map[interface{}]interface{}{
|
|
||||||
"foo": "bar",
|
|
||||||
},
|
|
||||||
want: "bar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "method on pointer type",
|
|
||||||
expression: "foo.PointerFunc()",
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": &dummyParameter{},
|
|
||||||
},
|
|
||||||
want: "point",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "custom selector",
|
|
||||||
expression: "hello.world",
|
|
||||||
parameter: "!",
|
|
||||||
extension: NewLanguage(Base(), VariableSelector(func(path Evaluables) Evaluable {
|
|
||||||
return func(c context.Context, v interface{}) (interface{}, error) {
|
|
||||||
keys, err := path.EvalStrings(c, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s%s", strings.Join(keys, " "), v), nil
|
|
||||||
}
|
|
||||||
})),
|
|
||||||
want: "hello world!",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "map[int]int",
|
|
||||||
expression: `a[0] + a[2]`,
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"a": map[int]int{0: 1, 2: 1},
|
|
||||||
},
|
|
||||||
want: 2.,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "map[int]string",
|
|
||||||
expression: `a[0] * a[2]`,
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"a": map[int]string{0: "1", 2: "1"},
|
|
||||||
},
|
|
||||||
want: 1.,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "coalesce typed nil 0",
|
|
||||||
expression: `ProjectID ?? 0`,
|
|
||||||
parameter: struct {
|
|
||||||
ProjectID *uint
|
|
||||||
}{},
|
|
||||||
want: 0.,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "coalesce typed nil 99",
|
|
||||||
expression: `ProjectID ?? 99`,
|
|
||||||
parameter: struct {
|
|
||||||
ProjectID *uint
|
|
||||||
}{},
|
|
||||||
want: 99.,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "operator with typed nil 99",
|
|
||||||
expression: `ProjectID + 99`,
|
|
||||||
parameter: struct {
|
|
||||||
ProjectID *uint
|
|
||||||
}{},
|
|
||||||
want: "<nil>99",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "operator with typed nil if",
|
|
||||||
expression: `Flag ? 1 : 2`,
|
|
||||||
parameter: struct {
|
|
||||||
Flag *uint
|
|
||||||
}{},
|
|
||||||
want: 2.,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Decimal math doesn't experience rounding error",
|
|
||||||
expression: "(x * 12.146) - y",
|
|
||||||
extension: decimalArithmetic,
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"x": 12.5,
|
|
||||||
"y": -5,
|
|
||||||
},
|
|
||||||
want: decimal.NewFromFloat(156.825),
|
|
||||||
equalityFunc: decimalEqualityFunc,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Decimal logical operators fractional difference",
|
|
||||||
expression: "((x * 12.146) - y) > 156.824999999",
|
|
||||||
extension: decimalArithmetic,
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"x": 12.5,
|
|
||||||
"y": -5,
|
|
||||||
},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Decimal logical operators whole number difference",
|
|
||||||
expression: "((x * 12.146) - y) > 156",
|
|
||||||
extension: decimalArithmetic,
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"x": 12.5,
|
|
||||||
"y": -5,
|
|
||||||
},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Decimal logical operators exact decimal match against GT",
|
|
||||||
expression: "((x * 12.146) - y) > 156.825",
|
|
||||||
extension: decimalArithmetic,
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"x": 12.5,
|
|
||||||
"y": -5,
|
|
||||||
},
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Decimal logical operators exact equality",
|
|
||||||
expression: "((x * 12.146) - y) == 156.825",
|
|
||||||
extension: decimalArithmetic,
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"x": 12.5,
|
|
||||||
"y": -5,
|
|
||||||
},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Decimal mixes with string logic with force fail",
|
|
||||||
expression: `(((x * 12.146) - y) == 156.825) && a == "test" && !b && b`,
|
|
||||||
extension: decimalArithmetic,
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"x": 12.5,
|
|
||||||
"y": -5,
|
|
||||||
"a": "test",
|
|
||||||
"b": false,
|
|
||||||
},
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Typed map with function call",
|
|
||||||
expression: `foo.MapWithFunc.Sum("a")`,
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": foo,
|
|
||||||
},
|
|
||||||
want: 3,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Types slice with function call",
|
|
||||||
expression: `foo.SliceWithFunc.Sum("a")`,
|
|
||||||
parameter: map[string]interface{}{
|
|
||||||
"foo": foo,
|
|
||||||
},
|
|
||||||
want: 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
t,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,179 +0,0 @@
|
|||||||
package gval
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp/syntax"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParsingFailure(t *testing.T) {
|
|
||||||
testEvaluate(
|
|
||||||
[]evaluationTest{
|
|
||||||
{
|
|
||||||
name: "Invalid equality comparator",
|
|
||||||
expression: "1 = 1",
|
|
||||||
wantErr: unexpected(`"="`, "operator"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid equality comparator",
|
|
||||||
expression: "1 === 1",
|
|
||||||
wantErr: unexpected(`"="`, "extension"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Too many characters for logical operator",
|
|
||||||
expression: "true &&& false",
|
|
||||||
wantErr: unexpected(`"&"`, "extension"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Too many characters for logical operator",
|
|
||||||
expression: "true ||| false",
|
|
||||||
wantErr: unexpected(`"|"`, "extension"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Premature end to expression, via modifier",
|
|
||||||
expression: "10 > 5 +",
|
|
||||||
wantErr: unexpected("EOF", "extensions"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Premature end to expression, via comparator",
|
|
||||||
expression: "10 + 5 >",
|
|
||||||
wantErr: unexpected("EOF", "extensions"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Premature end to expression, via logical operator",
|
|
||||||
expression: "10 > 5 &&",
|
|
||||||
wantErr: unexpected("EOF", "extensions"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Premature end to expression, via ternary operator",
|
|
||||||
expression: "true ?",
|
|
||||||
wantErr: unexpected("EOF", "extensions"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Hanging REQ",
|
|
||||||
expression: "`wat` =~",
|
|
||||||
wantErr: unexpected("EOF", "extensions"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Invalid operator change to REQ",
|
|
||||||
expression: " / =~",
|
|
||||||
wantErr: unexpected(`"/"`, "extensions"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid starting token, comparator",
|
|
||||||
expression: "> 10",
|
|
||||||
wantErr: unexpected(`">"`, "extensions"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid starting token, modifier",
|
|
||||||
expression: "+ 5",
|
|
||||||
wantErr: unexpected(`"+"`, "extensions"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid starting token, logical operator",
|
|
||||||
expression: "&& 5 < 10",
|
|
||||||
wantErr: unexpected(`"&"`, "extensions"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid NUMERIC transition",
|
|
||||||
expression: "10 10",
|
|
||||||
wantErr: unexpected(`Int`, "operator"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid STRING transition",
|
|
||||||
expression: "`foo` `foo`",
|
|
||||||
wantErr: `String while scanning operator`, // can't use func unexpected because the token was changed from String to RawString in go 1.11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid operator transition",
|
|
||||||
expression: "10 > < 10",
|
|
||||||
wantErr: unexpected(`"<"`, "extensions"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Starting with unbalanced parens",
|
|
||||||
expression: " ) ( arg2",
|
|
||||||
wantErr: unexpected(`")"`, "extensions"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Unclosed bracket",
|
|
||||||
expression: "[foo bar",
|
|
||||||
wantErr: unexpected(`EOF`, "extensions"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Unclosed quote",
|
|
||||||
expression: "foo == `responseTime",
|
|
||||||
wantErr: "could not parse string",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Constant regex pattern fail to compile",
|
|
||||||
expression: "foo =~ `[abc`",
|
|
||||||
wantErr: string(syntax.ErrMissingBracket),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Constant unmatch regex pattern fail to compile",
|
|
||||||
expression: "foo !~ `[abc`",
|
|
||||||
wantErr: string(syntax.ErrMissingBracket),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Unbalanced parentheses",
|
|
||||||
expression: "10 > (1 + 50",
|
|
||||||
wantErr: unexpected(`EOF`, "parentheses"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Multiple radix",
|
|
||||||
expression: "127.0.0.1",
|
|
||||||
wantErr: unexpected(`Float`, "operator"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
name: "Hanging accessor",
|
|
||||||
expression: "foo.Bar.",
|
|
||||||
wantErr: unexpected(`EOF`, "field"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Incomplete Hex",
|
|
||||||
expression: "0x",
|
|
||||||
wantErr: `strconv.ParseFloat: parsing "0x": invalid syntax`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid Hex literal",
|
|
||||||
expression: "0x > 0",
|
|
||||||
wantErr: `strconv.ParseFloat: parsing "0x": invalid syntax`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Hex float (Unsupported)",
|
|
||||||
expression: "0x1.1",
|
|
||||||
wantErr: `strconv.ParseFloat: parsing "0x1.1": invalid syntax`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Hex invalid letter",
|
|
||||||
expression: "0x12g1",
|
|
||||||
wantErr: `strconv.ParseFloat: parsing "0x12": invalid syntax`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Error after camouflage",
|
|
||||||
expression: "0 + ,",
|
|
||||||
wantErr: `unexpected "," while scanning extensions`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
t,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func unknownOp(op string) string {
|
|
||||||
return "unknown operator " + op
|
|
||||||
}
|
|
||||||
|
|
||||||
func unexpected(token, unit string) string {
|
|
||||||
return "unexpected " + token + " while scanning " + unit
|
|
||||||
}
|
|
||||||
@ -1,153 +0,0 @@
|
|||||||
package gval
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/shopspring/decimal"
|
|
||||||
)
|
|
||||||
|
|
||||||
type evaluationTest struct {
|
|
||||||
name string
|
|
||||||
expression string
|
|
||||||
extension Language
|
|
||||||
parameter interface{}
|
|
||||||
want interface{}
|
|
||||||
equalityFunc func(x, y interface{}) bool
|
|
||||||
wantErr string
|
|
||||||
}
|
|
||||||
|
|
||||||
func testEvaluate(tests []evaluationTest, t *testing.T) {
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := Evaluate(tt.expression, tt.parameter, tt.extension)
|
|
||||||
|
|
||||||
if tt.wantErr != "" {
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("Evaluate(%s) expected error but got %v", tt.expression, got)
|
|
||||||
}
|
|
||||||
if !strings.Contains(err.Error(), tt.wantErr) {
|
|
||||||
t.Fatalf("Evaluate(%s) expected error %s but got error %v", tt.expression, tt.wantErr, err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Evaluate() error = %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ef := tt.equalityFunc; ef != nil {
|
|
||||||
if !ef(got, tt.want) {
|
|
||||||
t.Errorf("Evaluate(%s) = %v, want %v", tt.expression, got, tt.want)
|
|
||||||
}
|
|
||||||
} else if !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("Evaluate(%s) = %v, want %v", tt.expression, got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// dummyParameter used to test "parameter calls".
|
|
||||||
type dummyParameter struct {
|
|
||||||
String string
|
|
||||||
Int int
|
|
||||||
BoolFalse bool
|
|
||||||
Nil interface{}
|
|
||||||
Nested dummyNestedParameter
|
|
||||||
MapWithFunc dummyMapWithFunc
|
|
||||||
SliceWithFunc dummySliceWithFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d dummyParameter) Func() string {
|
|
||||||
return "funk"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d dummyParameter) Func2() (string, error) {
|
|
||||||
return "frink", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dummyParameter) PointerFunc() (string, error) {
|
|
||||||
return "point", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d dummyParameter) FuncErr() (string, error) {
|
|
||||||
return "", fmt.Errorf("fumps")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d dummyParameter) FuncArgStr(arg1 string) string {
|
|
||||||
return arg1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d dummyParameter) AlwaysFail() (interface{}, error) {
|
|
||||||
return nil, fmt.Errorf("function should always fail")
|
|
||||||
}
|
|
||||||
|
|
||||||
type dummyNestedParameter struct {
|
|
||||||
Funk string
|
|
||||||
Map map[string]int
|
|
||||||
Slice []int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d dummyNestedParameter) Dunk(arg1 string) string {
|
|
||||||
return arg1 + "dunk"
|
|
||||||
}
|
|
||||||
|
|
||||||
var foo = dummyParameter{
|
|
||||||
String: "string!",
|
|
||||||
Int: 101,
|
|
||||||
BoolFalse: false,
|
|
||||||
Nil: nil,
|
|
||||||
Nested: dummyNestedParameter{
|
|
||||||
Funk: "funkalicious",
|
|
||||||
Map: map[string]int{"a": 1, "b": 2, "c": 3},
|
|
||||||
Slice: []int{1, 2, 3},
|
|
||||||
},
|
|
||||||
MapWithFunc: dummyMapWithFunc{"a": {1, 2}, "b": {3, 4}},
|
|
||||||
SliceWithFunc: dummySliceWithFunc{"a", "b", "c", "a"},
|
|
||||||
}
|
|
||||||
|
|
||||||
var fooFailureParameters = map[string]interface{}{
|
|
||||||
"foo": foo,
|
|
||||||
"fooptr": &foo,
|
|
||||||
}
|
|
||||||
|
|
||||||
var decimalEqualityFunc = func(x, y interface{}) bool {
|
|
||||||
v1, ok1 := x.(decimal.Decimal)
|
|
||||||
v2, ok2 := y.(decimal.Decimal)
|
|
||||||
|
|
||||||
if !ok1 || !ok2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return v1.Equal(v2)
|
|
||||||
}
|
|
||||||
|
|
||||||
type dummyMapWithFunc map[string][]int
|
|
||||||
|
|
||||||
func (m dummyMapWithFunc) Sum(key string) int {
|
|
||||||
values, ok := m[key]
|
|
||||||
if !ok {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
sum := 0
|
|
||||||
for _, v := range values {
|
|
||||||
sum += v
|
|
||||||
}
|
|
||||||
|
|
||||||
return sum
|
|
||||||
}
|
|
||||||
|
|
||||||
type dummySliceWithFunc []string
|
|
||||||
|
|
||||||
func (m dummySliceWithFunc) Sum(key string) int {
|
|
||||||
sum := 0
|
|
||||||
for _, v := range m {
|
|
||||||
if v == key {
|
|
||||||
sum += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sum
|
|
||||||
}
|
|
||||||
@ -1,281 +0,0 @@
|
|||||||
package gval
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"text/scanner"
|
|
||||||
"unicode"
|
|
||||||
|
|
||||||
"github.com/shopspring/decimal"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Language is an expression language
|
|
||||||
type Language struct {
|
|
||||||
prefixes map[interface{}]extension
|
|
||||||
operators map[string]operator
|
|
||||||
operatorSymbols map[rune]struct{}
|
|
||||||
init extension
|
|
||||||
def extension
|
|
||||||
selector func(Evaluables) Evaluable
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLanguage returns the union of given Languages as new Language.
|
|
||||||
func NewLanguage(bases ...Language) Language {
|
|
||||||
l := newLanguage()
|
|
||||||
for _, base := range bases {
|
|
||||||
for i, e := range base.prefixes {
|
|
||||||
l.prefixes[i] = e
|
|
||||||
}
|
|
||||||
for i, e := range base.operators {
|
|
||||||
l.operators[i] = e.merge(l.operators[i])
|
|
||||||
l.operators[i].initiate(i)
|
|
||||||
}
|
|
||||||
for i := range base.operatorSymbols {
|
|
||||||
l.operatorSymbols[i] = struct{}{}
|
|
||||||
}
|
|
||||||
if base.init != nil {
|
|
||||||
l.init = base.init
|
|
||||||
}
|
|
||||||
if base.def != nil {
|
|
||||||
l.def = base.def
|
|
||||||
}
|
|
||||||
if base.selector != nil {
|
|
||||||
l.selector = base.selector
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
func newLanguage() Language {
|
|
||||||
return Language{
|
|
||||||
prefixes: map[interface{}]extension{},
|
|
||||||
operators: map[string]operator{},
|
|
||||||
operatorSymbols: map[rune]struct{}{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEvaluable returns an Evaluable for given expression in the specified language
|
|
||||||
func (l Language) NewEvaluable(expression string) (Evaluable, error) {
|
|
||||||
return l.NewEvaluableWithContext(context.Background(), expression)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEvaluableWithContext returns an Evaluable for given expression in the specified language using context
|
|
||||||
func (l Language) NewEvaluableWithContext(c context.Context, expression string) (Evaluable, error) {
|
|
||||||
p := newParser(expression, l)
|
|
||||||
|
|
||||||
eval, err := p.parse(c)
|
|
||||||
if err == nil && p.isCamouflaged() && p.lastScan != scanner.EOF {
|
|
||||||
err = p.camouflage
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
pos := p.scanner.Pos()
|
|
||||||
return nil, fmt.Errorf("parsing error: %s - %d:%d %w", p.scanner.Position, pos.Line, pos.Column, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return eval, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate given parameter with given expression
|
|
||||||
func (l Language) Evaluate(expression string, parameter interface{}) (interface{}, error) {
|
|
||||||
return l.EvaluateWithContext(context.Background(), expression, parameter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate given parameter with given expression using context
|
|
||||||
func (l Language) EvaluateWithContext(c context.Context, expression string, parameter interface{}) (interface{}, error) {
|
|
||||||
eval, err := l.NewEvaluableWithContext(c, expression)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
v, err := eval(c, parameter)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("can not evaluate %s: %w", expression, err)
|
|
||||||
}
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function returns a Language with given function.
|
|
||||||
// Function has no conversion for input types.
|
|
||||||
//
|
|
||||||
// If the function returns an error it must be the last return parameter.
|
|
||||||
//
|
|
||||||
// If the function has (without the error) more then one return parameter,
|
|
||||||
// it returns them as []interface{}.
|
|
||||||
func Function(name string, function interface{}) Language {
|
|
||||||
l := newLanguage()
|
|
||||||
l.prefixes[name] = func(c context.Context, p *Parser) (eval Evaluable, err error) {
|
|
||||||
args := []Evaluable{}
|
|
||||||
scan := p.Scan()
|
|
||||||
switch scan {
|
|
||||||
case '(':
|
|
||||||
args, err = p.parseArguments(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
p.Camouflage("function call", '(')
|
|
||||||
}
|
|
||||||
return p.callFunc(toFunc(function), args...), nil
|
|
||||||
}
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constant returns a Language with given constant
|
|
||||||
func Constant(name string, value interface{}) Language {
|
|
||||||
l := newLanguage()
|
|
||||||
l.prefixes[l.makePrefixKey(name)] = func(c context.Context, p *Parser) (eval Evaluable, err error) {
|
|
||||||
return p.Const(value), nil
|
|
||||||
}
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrefixExtension extends a Language
|
|
||||||
func PrefixExtension(r rune, ext func(context.Context, *Parser) (Evaluable, error)) Language {
|
|
||||||
l := newLanguage()
|
|
||||||
l.prefixes[r] = ext
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init is a language that does no parsing, but invokes the given function when
|
|
||||||
// parsing starts. It is incumbent upon the function to call ParseExpression to
|
|
||||||
// continue parsing.
|
|
||||||
//
|
|
||||||
// This function can be used to customize the parser settings, such as
|
|
||||||
// whitespace or ident behavior.
|
|
||||||
func Init(ext func(context.Context, *Parser) (Evaluable, error)) Language {
|
|
||||||
l := newLanguage()
|
|
||||||
l.init = ext
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultExtension is a language that runs the given function if no other
|
|
||||||
// prefix matches.
|
|
||||||
func DefaultExtension(ext func(context.Context, *Parser) (Evaluable, error)) Language {
|
|
||||||
l := newLanguage()
|
|
||||||
l.def = ext
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrefixMetaPrefix chooses a Prefix to be executed
|
|
||||||
func PrefixMetaPrefix(r rune, ext func(context.Context, *Parser) (call string, alternative func() (Evaluable, error), err error)) Language {
|
|
||||||
l := newLanguage()
|
|
||||||
l.prefixes[r] = func(c context.Context, p *Parser) (Evaluable, error) {
|
|
||||||
call, alternative, err := ext(c, p)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if prefix, ok := p.prefixes[l.makePrefixKey(call)]; ok {
|
|
||||||
return prefix(c, p)
|
|
||||||
}
|
|
||||||
return alternative()
|
|
||||||
}
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrefixOperator returns a Language with given prefix
|
|
||||||
func PrefixOperator(name string, e Evaluable) Language {
|
|
||||||
l := newLanguage()
|
|
||||||
l.prefixes[l.makePrefixKey(name)] = func(c context.Context, p *Parser) (Evaluable, error) {
|
|
||||||
eval, err := p.ParseNextExpression(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
prefix := func(c context.Context, v interface{}) (interface{}, error) {
|
|
||||||
a, err := eval(c, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return e(c, a)
|
|
||||||
}
|
|
||||||
if eval.IsConst() {
|
|
||||||
v, err := prefix(c, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
prefix = p.Const(v)
|
|
||||||
}
|
|
||||||
return prefix, nil
|
|
||||||
}
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// PostfixOperator extends a Language.
|
|
||||||
func PostfixOperator(name string, ext func(context.Context, *Parser, Evaluable) (Evaluable, error)) Language {
|
|
||||||
l := newLanguage()
|
|
||||||
l.operators[l.makeInfixKey(name)] = postfix{
|
|
||||||
f: func(c context.Context, p *Parser, eval Evaluable, pre operatorPrecedence) (Evaluable, error) {
|
|
||||||
return ext(c, p, eval)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// InfixOperator for two arbitrary values.
|
|
||||||
func InfixOperator(name string, f func(a, b interface{}) (interface{}, error)) Language {
|
|
||||||
return newLanguageOperator(name, &infix{arbitrary: f})
|
|
||||||
}
|
|
||||||
|
|
||||||
// InfixShortCircuit operator is called after the left operand is evaluated.
|
|
||||||
func InfixShortCircuit(name string, f func(a interface{}) (interface{}, bool)) Language {
|
|
||||||
return newLanguageOperator(name, &infix{shortCircuit: f})
|
|
||||||
}
|
|
||||||
|
|
||||||
// InfixTextOperator for two text values.
|
|
||||||
func InfixTextOperator(name string, f func(a, b string) (interface{}, error)) Language {
|
|
||||||
return newLanguageOperator(name, &infix{text: f})
|
|
||||||
}
|
|
||||||
|
|
||||||
// InfixNumberOperator for two number values.
|
|
||||||
func InfixNumberOperator(name string, f func(a, b uint) (interface{}, error)) Language {
|
|
||||||
return newLanguageOperator(name, &infix{number: f})
|
|
||||||
}
|
|
||||||
|
|
||||||
// InfixDecimalOperator for two decimal values.
|
|
||||||
func InfixDecimalOperator(name string, f func(a, b decimal.Decimal) (interface{}, error)) Language {
|
|
||||||
return newLanguageOperator(name, &infix{decimal: f})
|
|
||||||
}
|
|
||||||
|
|
||||||
// InfixBoolOperator for two bool values.
|
|
||||||
func InfixBoolOperator(name string, f func(a, b bool) (interface{}, error)) Language {
|
|
||||||
return newLanguageOperator(name, &infix{boolean: f})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Precedence of operator. The Operator with higher operatorPrecedence is evaluated first.
|
|
||||||
func Precedence(name string, operatorPrecendence uint8) Language {
|
|
||||||
return newLanguageOperator(name, operatorPrecedence(operatorPrecendence))
|
|
||||||
}
|
|
||||||
|
|
||||||
// InfixEvalOperator operates on the raw operands.
|
|
||||||
// Therefore it cannot be combined with operators for other operand types.
|
|
||||||
func InfixEvalOperator(name string, f func(a, b Evaluable) (Evaluable, error)) Language {
|
|
||||||
return newLanguageOperator(name, directInfix{infixBuilder: f})
|
|
||||||
}
|
|
||||||
|
|
||||||
func newLanguageOperator(name string, op operator) Language {
|
|
||||||
op.initiate(name)
|
|
||||||
l := newLanguage()
|
|
||||||
l.operators[l.makeInfixKey(name)] = op
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Language) makePrefixKey(key string) interface{} {
|
|
||||||
runes := []rune(key)
|
|
||||||
if len(runes) == 1 && !unicode.IsLetter(runes[0]) {
|
|
||||||
return runes[0]
|
|
||||||
}
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Language) makeInfixKey(key string) string {
|
|
||||||
for _, r := range key {
|
|
||||||
l.operatorSymbols[r] = struct{}{}
|
|
||||||
}
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
||||||
// VariableSelector returns a Language which uses given variable selector.
|
|
||||||
// It must be combined with a Language that uses the vatiable selector. E.g. gval.Base().
|
|
||||||
func VariableSelector(selector func(path Evaluables) Evaluable) Language {
|
|
||||||
l := newLanguage()
|
|
||||||
l.selector = selector
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
@ -1,403 +0,0 @@
|
|||||||
package gval
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/shopspring/decimal"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stage struct {
|
|
||||||
Evaluable
|
|
||||||
infixBuilder
|
|
||||||
operatorPrecedence
|
|
||||||
}
|
|
||||||
|
|
||||||
type stageStack []stage //operatorPrecedence in stacktStage is continuously, monotone ascending
|
|
||||||
|
|
||||||
func (s *stageStack) push(b stage) error {
|
|
||||||
for len(*s) > 0 && s.peek().operatorPrecedence >= b.operatorPrecedence {
|
|
||||||
a := s.pop()
|
|
||||||
eval, err := a.infixBuilder(a.Evaluable, b.Evaluable)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if a.IsConst() && b.IsConst() {
|
|
||||||
v, err := eval(nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.Evaluable = constant(v)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
b.Evaluable = eval
|
|
||||||
}
|
|
||||||
*s = append(*s, b)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stageStack) peek() stage {
|
|
||||||
return (*s)[len(*s)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stageStack) pop() stage {
|
|
||||||
a := s.peek()
|
|
||||||
(*s) = (*s)[:len(*s)-1]
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
type infixBuilder func(a, b Evaluable) (Evaluable, error)
|
|
||||||
|
|
||||||
func (l Language) isSymbolOperation(r rune) bool {
|
|
||||||
_, in := l.operatorSymbols[r]
|
|
||||||
return in
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l Language) isOperatorPrefix(op string) bool {
|
|
||||||
for k := range l.operators {
|
|
||||||
if strings.HasPrefix(k, op) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (op *infix) initiate(name string) {
|
|
||||||
f := func(a, b interface{}) (interface{}, error) {
|
|
||||||
return nil, fmt.Errorf("invalid operation (%T) %s (%T)", a, name, b)
|
|
||||||
}
|
|
||||||
if op.arbitrary != nil {
|
|
||||||
f = op.arbitrary
|
|
||||||
}
|
|
||||||
for _, typeConvertion := range []bool{true, false} {
|
|
||||||
if op.text != nil && (!typeConvertion || op.arbitrary == nil) {
|
|
||||||
f = getStringOpFunc(op.text, f, typeConvertion)
|
|
||||||
}
|
|
||||||
if op.boolean != nil {
|
|
||||||
f = getBoolOpFunc(op.boolean, f, typeConvertion)
|
|
||||||
}
|
|
||||||
if op.number != nil {
|
|
||||||
f = getUintOpFunc(op.number, f, typeConvertion)
|
|
||||||
}
|
|
||||||
if op.decimal != nil {
|
|
||||||
f = getDecimalOpFunc(op.decimal, f, typeConvertion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if op.shortCircuit == nil {
|
|
||||||
op.builder = func(a, b Evaluable) (Evaluable, error) {
|
|
||||||
return func(c context.Context, x interface{}) (interface{}, error) {
|
|
||||||
a, err := a(c, x)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b, err := b(c, x)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return f(a, b)
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
shortF := op.shortCircuit
|
|
||||||
op.builder = func(a, b Evaluable) (Evaluable, error) {
|
|
||||||
return func(c context.Context, x interface{}) (interface{}, error) {
|
|
||||||
a, err := a(c, x)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if r, ok := shortF(a); ok {
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
b, err := b(c, x)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return f(a, b)
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type opFunc func(a, b interface{}) (interface{}, error)
|
|
||||||
|
|
||||||
func getStringOpFunc(s func(a, b string) (interface{}, error), f opFunc, typeConversion bool) opFunc {
|
|
||||||
if typeConversion {
|
|
||||||
return func(a, b interface{}) (interface{}, error) {
|
|
||||||
if a != nil && b != nil {
|
|
||||||
return s(fmt.Sprintf("%v", a), fmt.Sprintf("%v", b))
|
|
||||||
}
|
|
||||||
return f(a, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return func(a, b interface{}) (interface{}, error) {
|
|
||||||
s1, k := a.(string)
|
|
||||||
s2, l := b.(string)
|
|
||||||
if k && l {
|
|
||||||
return s(s1, s2)
|
|
||||||
}
|
|
||||||
return f(a, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func convertToBool(o interface{}) (bool, bool) {
|
|
||||||
if b, ok := o.(bool); ok {
|
|
||||||
return b, true
|
|
||||||
}
|
|
||||||
v := reflect.ValueOf(o)
|
|
||||||
|
|
||||||
if v.Kind() == reflect.Func {
|
|
||||||
if vt := v.Type(); vt.NumIn() == 0 && vt.NumOut() == 1 {
|
|
||||||
retType := vt.Out(0)
|
|
||||||
|
|
||||||
if retType.Kind() == reflect.Bool {
|
|
||||||
funcResults := v.Call([]reflect.Value{})
|
|
||||||
v = funcResults[0]
|
|
||||||
o = v.Interface()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for o != nil && v.Kind() == reflect.Ptr {
|
|
||||||
v = v.Elem()
|
|
||||||
if !v.IsValid() {
|
|
||||||
return false, false
|
|
||||||
}
|
|
||||||
o = v.Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
if o == false || o == nil || o == "false" || o == "FALSE" {
|
|
||||||
return false, true
|
|
||||||
}
|
|
||||||
if o == true || o == "true" || o == "TRUE" {
|
|
||||||
return true, true
|
|
||||||
}
|
|
||||||
if f, ok := convertToUint(o); ok {
|
|
||||||
return f != 0., true
|
|
||||||
}
|
|
||||||
return false, false
|
|
||||||
}
|
|
||||||
func getBoolOpFunc(o func(a, b bool) (interface{}, error), f opFunc, typeConversion bool) opFunc {
|
|
||||||
if typeConversion {
|
|
||||||
return func(a, b interface{}) (interface{}, error) {
|
|
||||||
x, k := convertToBool(a)
|
|
||||||
y, l := convertToBool(b)
|
|
||||||
if k && l {
|
|
||||||
return o(x, y)
|
|
||||||
}
|
|
||||||
return f(a, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return func(a, b interface{}) (interface{}, error) {
|
|
||||||
x, k := a.(bool)
|
|
||||||
y, l := b.(bool)
|
|
||||||
if k && l {
|
|
||||||
return o(x, y)
|
|
||||||
}
|
|
||||||
return f(a, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func convertToUint(o interface{}) (uint, bool) {
|
|
||||||
if i, ok := o.(uint); ok {
|
|
||||||
return i, true
|
|
||||||
}
|
|
||||||
|
|
||||||
v := reflect.ValueOf(o)
|
|
||||||
for o != nil && v.Kind() == reflect.Ptr {
|
|
||||||
v = v.Elem()
|
|
||||||
if !v.IsValid() {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
o = v.Interface()
|
|
||||||
}
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
return uint(v.Int()), true
|
|
||||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
||||||
return uint(v.Uint()), true
|
|
||||||
}
|
|
||||||
if s, ok := o.(string); ok {
|
|
||||||
u, err := strconv.ParseUint(s, 0, 32)
|
|
||||||
if err == nil {
|
|
||||||
return uint(u), true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
func getUintOpFunc(o func(a, b uint) (interface{}, error), f opFunc, typeConversion bool) opFunc {
|
|
||||||
if typeConversion {
|
|
||||||
return func(a, b interface{}) (interface{}, error) {
|
|
||||||
x, k := convertToUint(a)
|
|
||||||
y, l := convertToUint(b)
|
|
||||||
if k && l {
|
|
||||||
return o(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
return f(a, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return func(a, b interface{}) (interface{}, error) {
|
|
||||||
x, k := a.(uint)
|
|
||||||
y, l := b.(uint)
|
|
||||||
if k && l {
|
|
||||||
return o(x, y)
|
|
||||||
}
|
|
||||||
return f(a, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func convertToDecimal(o interface{}) (decimal.Decimal, bool) {
|
|
||||||
if i, ok := o.(decimal.Decimal); ok {
|
|
||||||
return i, true
|
|
||||||
}
|
|
||||||
if i, ok := o.(float64); ok {
|
|
||||||
return decimal.NewFromFloat(i), true
|
|
||||||
}
|
|
||||||
v := reflect.ValueOf(o)
|
|
||||||
for o != nil && v.Kind() == reflect.Ptr {
|
|
||||||
v = v.Elem()
|
|
||||||
if !v.IsValid() {
|
|
||||||
return decimal.Zero, false
|
|
||||||
}
|
|
||||||
o = v.Interface()
|
|
||||||
}
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
return decimal.NewFromInt(v.Int()), true
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
||||||
return decimal.NewFromFloat(float64(v.Uint())), true
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return decimal.NewFromFloat(v.Float()), true
|
|
||||||
}
|
|
||||||
if s, ok := o.(string); ok {
|
|
||||||
f, err := strconv.ParseFloat(s, 64)
|
|
||||||
if err == nil {
|
|
||||||
return decimal.NewFromFloat(f), true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return decimal.Zero, false
|
|
||||||
}
|
|
||||||
func getDecimalOpFunc(o func(a, b decimal.Decimal) (interface{}, error), f opFunc, typeConversion bool) opFunc {
|
|
||||||
if typeConversion {
|
|
||||||
return func(a, b interface{}) (interface{}, error) {
|
|
||||||
x, k := convertToDecimal(a)
|
|
||||||
y, l := convertToDecimal(b)
|
|
||||||
if k && l {
|
|
||||||
return o(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
return f(a, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return func(a, b interface{}) (interface{}, error) {
|
|
||||||
x, k := a.(decimal.Decimal)
|
|
||||||
y, l := b.(decimal.Decimal)
|
|
||||||
if k && l {
|
|
||||||
return o(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
return f(a, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type operator interface {
|
|
||||||
merge(operator) operator
|
|
||||||
precedence() operatorPrecedence
|
|
||||||
initiate(name string)
|
|
||||||
}
|
|
||||||
|
|
||||||
type operatorPrecedence uint8
|
|
||||||
|
|
||||||
func (pre operatorPrecedence) merge(op operator) operator {
|
|
||||||
if op, ok := op.(operatorPrecedence); ok {
|
|
||||||
if op > pre {
|
|
||||||
return op
|
|
||||||
}
|
|
||||||
return pre
|
|
||||||
}
|
|
||||||
if op == nil {
|
|
||||||
return pre
|
|
||||||
}
|
|
||||||
return op.merge(pre)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pre operatorPrecedence) precedence() operatorPrecedence {
|
|
||||||
return pre
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pre operatorPrecedence) initiate(name string) {}
|
|
||||||
|
|
||||||
type infix struct {
|
|
||||||
operatorPrecedence
|
|
||||||
number func(a, b uint) (interface{}, error)
|
|
||||||
decimal func(a, b decimal.Decimal) (interface{}, error)
|
|
||||||
boolean func(a, b bool) (interface{}, error)
|
|
||||||
text func(a, b string) (interface{}, error)
|
|
||||||
arbitrary func(a, b interface{}) (interface{}, error)
|
|
||||||
shortCircuit func(a interface{}) (interface{}, bool)
|
|
||||||
builder infixBuilder
|
|
||||||
}
|
|
||||||
|
|
||||||
func (op infix) merge(op2 operator) operator {
|
|
||||||
switch op2 := op2.(type) {
|
|
||||||
case *infix:
|
|
||||||
if op.number == nil {
|
|
||||||
op.number = op2.number
|
|
||||||
}
|
|
||||||
if op.decimal == nil {
|
|
||||||
op.decimal = op2.decimal
|
|
||||||
}
|
|
||||||
if op.boolean == nil {
|
|
||||||
op.boolean = op2.boolean
|
|
||||||
}
|
|
||||||
if op.text == nil {
|
|
||||||
op.text = op2.text
|
|
||||||
}
|
|
||||||
if op.arbitrary == nil {
|
|
||||||
op.arbitrary = op2.arbitrary
|
|
||||||
}
|
|
||||||
if op.shortCircuit == nil {
|
|
||||||
op.shortCircuit = op2.shortCircuit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if op2 != nil && op2.precedence() > op.operatorPrecedence {
|
|
||||||
op.operatorPrecedence = op2.precedence()
|
|
||||||
}
|
|
||||||
return &op
|
|
||||||
}
|
|
||||||
|
|
||||||
type directInfix struct {
|
|
||||||
operatorPrecedence
|
|
||||||
infixBuilder
|
|
||||||
}
|
|
||||||
|
|
||||||
func (op directInfix) merge(op2 operator) operator {
|
|
||||||
switch op2 := op2.(type) {
|
|
||||||
case operatorPrecedence:
|
|
||||||
op.operatorPrecedence = op2
|
|
||||||
}
|
|
||||||
if op2 != nil && op2.precedence() > op.operatorPrecedence {
|
|
||||||
op.operatorPrecedence = op2.precedence()
|
|
||||||
}
|
|
||||||
return op
|
|
||||||
}
|
|
||||||
|
|
||||||
type extension func(context.Context, *Parser) (Evaluable, error)
|
|
||||||
|
|
||||||
type postfix struct {
|
|
||||||
operatorPrecedence
|
|
||||||
f func(context.Context, *Parser, Evaluable, operatorPrecedence) (Evaluable, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (op postfix) merge(op2 operator) operator {
|
|
||||||
switch op2 := op2.(type) {
|
|
||||||
case postfix:
|
|
||||||
if op2.f != nil {
|
|
||||||
op.f = op2.f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if op2 != nil && op2.precedence() > op.operatorPrecedence {
|
|
||||||
op.operatorPrecedence = op2.precedence()
|
|
||||||
}
|
|
||||||
return op
|
|
||||||
}
|
|
||||||
@ -1,166 +0,0 @@
|
|||||||
package gval
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_Infix(t *testing.T) {
|
|
||||||
type subTest struct {
|
|
||||||
name string
|
|
||||||
a interface{}
|
|
||||||
b interface{}
|
|
||||||
wantRet interface{}
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
infix
|
|
||||||
subTests []subTest
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"number operator",
|
|
||||||
infix{
|
|
||||||
number: func(a, b float64) (interface{}, error) { return a * b, nil },
|
|
||||||
},
|
|
||||||
[]subTest{
|
|
||||||
{"float64 arguments", 7., 3., 21.},
|
|
||||||
{"int arguments", 7, 3, 21.},
|
|
||||||
{"string arguments", "7", "3.", 21.},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"number and string operator",
|
|
||||||
infix{
|
|
||||||
number: func(a, b float64) (interface{}, error) { return a + b, nil },
|
|
||||||
text: func(a, b string) (interface{}, error) { return fmt.Sprintf("%v%v", a, b), nil },
|
|
||||||
},
|
|
||||||
|
|
||||||
[]subTest{
|
|
||||||
{"float64 arguments", 7., 3., 10.},
|
|
||||||
{"int arguments", 7, 3, 10.},
|
|
||||||
{"number string arguments", "7", "3.", "73."},
|
|
||||||
{"string arguments", "hello ", "world", "hello world"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bool operator",
|
|
||||||
infix{
|
|
||||||
shortCircuit: func(a interface{}) (interface{}, bool) { return false, a == false },
|
|
||||||
boolean: func(a, b bool) (interface{}, error) { return a && b, nil },
|
|
||||||
},
|
|
||||||
|
|
||||||
[]subTest{
|
|
||||||
{"bool arguments", false, true, false},
|
|
||||||
{"number arguments", 0, true, false},
|
|
||||||
{"lower string arguments", "false", "true", false},
|
|
||||||
{"upper string arguments", "TRUE", "FALSE", false},
|
|
||||||
{"shortCircuit", false, "not a boolean", false},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bool, number, text and interface operator",
|
|
||||||
infix{
|
|
||||||
number: func(a, b float64) (interface{}, error) { return a == b, nil },
|
|
||||||
boolean: func(a, b bool) (interface{}, error) { return a == b, nil },
|
|
||||||
text: func(a, b string) (interface{}, error) { return a == b, nil },
|
|
||||||
arbitrary: func(a, b interface{}) (interface{}, error) { return a == b, nil },
|
|
||||||
},
|
|
||||||
|
|
||||||
[]subTest{
|
|
||||||
{"number string and int arguments", "7", 7, true},
|
|
||||||
{"bool string and bool arguments", "true", true, true},
|
|
||||||
{"string arguments", "hello", "hello", true},
|
|
||||||
{"upper string arguments", "TRUE", "FALSE", false},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
tt.infix.initiate("<" + tt.name + ">")
|
|
||||||
builder := tt.infix.builder
|
|
||||||
for _, tt := range tt.subTests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
eval, err := builder(constant(tt.a), constant(tt.b))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
got, err := eval(context.Background(), nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(got, tt.wantRet) {
|
|
||||||
t.Fatalf("binaryOperator() eval() = %v, want %v", got, tt.wantRet)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_stageStack_push(t *testing.T) {
|
|
||||||
p := (*Parser)(nil)
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
pres []operatorPrecedence
|
|
||||||
expect string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"flat",
|
|
||||||
[]operatorPrecedence{1, 1, 1, 1},
|
|
||||||
"((((AB)C)D)E)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"asc",
|
|
||||||
[]operatorPrecedence{1, 2, 3, 4},
|
|
||||||
"(A(B(C(DE))))",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"desc",
|
|
||||||
[]operatorPrecedence{4, 3, 2, 1},
|
|
||||||
"((((AB)C)D)E)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"mixed",
|
|
||||||
[]operatorPrecedence{1, 2, 1, 1},
|
|
||||||
"(((A(BC))D)E)",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
X := int('A')
|
|
||||||
|
|
||||||
op := func(a, b Evaluable) (Evaluable, error) {
|
|
||||||
return func(c context.Context, o interface{}) (interface{}, error) {
|
|
||||||
aa, _ := a.EvalString(c, nil)
|
|
||||||
bb, _ := b.EvalString(c, nil)
|
|
||||||
s := "(" + aa + bb + ")"
|
|
||||||
return s, nil
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
stack := stageStack{}
|
|
||||||
for _, pre := range tt.pres {
|
|
||||||
if err := stack.push(stage{p.Const(string(rune(X))), op, pre}); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
X++
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := stack.push(stage{p.Const(string(rune(X))), nil, 0}); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(stack) != 1 {
|
|
||||||
t.Fatalf("stack must hold exactly one element")
|
|
||||||
}
|
|
||||||
|
|
||||||
got, _ := stack[0].EvalString(context.Background(), nil)
|
|
||||||
if got != tt.expect {
|
|
||||||
t.Fatalf("got %s but expected %s", got, tt.expect)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,347 +0,0 @@
|
|||||||
package gval
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"text/scanner"
|
|
||||||
|
|
||||||
"github.com/shopspring/decimal"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseExpression scans an expression into an Evaluable.
|
|
||||||
func (p *Parser) ParseExpression(c context.Context) (eval Evaluable, err error) {
|
|
||||||
stack := stageStack{}
|
|
||||||
for {
|
|
||||||
eval, err = p.ParseNextExpression(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if stage, err := p.parseOperator(c, &stack, eval); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if err = stack.push(stage); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if stack.peek().infixBuilder == nil {
|
|
||||||
return stack.pop().Evaluable, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseNextExpression scans the expression ignoring following operators
|
|
||||||
func (p *Parser) ParseNextExpression(c context.Context) (eval Evaluable, err error) {
|
|
||||||
scan := p.Scan()
|
|
||||||
ex, ok := p.prefixes[scan]
|
|
||||||
if !ok {
|
|
||||||
if scan != scanner.EOF && p.def != nil {
|
|
||||||
return p.def(c, p)
|
|
||||||
}
|
|
||||||
return nil, p.Expected("extensions")
|
|
||||||
}
|
|
||||||
return ex(c, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseSublanguage sets the next language for this parser to parse and calls
|
|
||||||
// its initialization function, usually ParseExpression.
|
|
||||||
func (p *Parser) ParseSublanguage(c context.Context, l Language) (Evaluable, error) {
|
|
||||||
if p.isCamouflaged() {
|
|
||||||
panic("can not ParseSublanguage() on camouflaged Parser")
|
|
||||||
}
|
|
||||||
curLang := p.Language
|
|
||||||
curWhitespace := p.scanner.Whitespace
|
|
||||||
curMode := p.scanner.Mode
|
|
||||||
curIsIdentRune := p.scanner.IsIdentRune
|
|
||||||
|
|
||||||
p.Language = l
|
|
||||||
p.resetScannerProperties()
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
p.Language = curLang
|
|
||||||
p.scanner.Whitespace = curWhitespace
|
|
||||||
p.scanner.Mode = curMode
|
|
||||||
p.scanner.IsIdentRune = curIsIdentRune
|
|
||||||
}()
|
|
||||||
|
|
||||||
return p.parse(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Parser) parse(c context.Context) (Evaluable, error) {
|
|
||||||
if p.init != nil {
|
|
||||||
return p.init(c, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.ParseExpression(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseString(c context.Context, p *Parser) (Evaluable, error) {
|
|
||||||
s, err := strconv.Unquote(p.TokenText())
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not parse string: %w", err)
|
|
||||||
}
|
|
||||||
return p.Const(s), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseNumber(c context.Context, p *Parser) (Evaluable, error) {
|
|
||||||
n, err := strconv.ParseUint(p.TokenText(), 0, 32)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return p.Const(n), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDecimal(c context.Context, p *Parser) (Evaluable, error) {
|
|
||||||
n, err := strconv.ParseFloat(p.TokenText(), 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return p.Const(decimal.NewFromFloat(n)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseParentheses(c context.Context, p *Parser) (Evaluable, error) {
|
|
||||||
eval, err := p.ParseExpression(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
switch p.Scan() {
|
|
||||||
case ')':
|
|
||||||
return eval, nil
|
|
||||||
default:
|
|
||||||
return nil, p.Expected("parentheses", ')')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Parser) parseOperator(c context.Context, stack *stageStack, eval Evaluable) (st stage, err error) {
|
|
||||||
for {
|
|
||||||
scan := p.Scan()
|
|
||||||
op := p.TokenText()
|
|
||||||
mustOp := false
|
|
||||||
if p.isSymbolOperation(scan) {
|
|
||||||
scan = p.Peek()
|
|
||||||
for p.isSymbolOperation(scan) && p.isOperatorPrefix(op+string(scan)) {
|
|
||||||
mustOp = true
|
|
||||||
op += string(scan)
|
|
||||||
p.Next()
|
|
||||||
scan = p.Peek()
|
|
||||||
}
|
|
||||||
} else if scan != scanner.Ident {
|
|
||||||
p.Camouflage("operator")
|
|
||||||
return stage{Evaluable: eval}, nil
|
|
||||||
}
|
|
||||||
switch operator := p.operators[op].(type) {
|
|
||||||
case *infix:
|
|
||||||
return stage{
|
|
||||||
Evaluable: eval,
|
|
||||||
infixBuilder: operator.builder,
|
|
||||||
operatorPrecedence: operator.operatorPrecedence,
|
|
||||||
}, nil
|
|
||||||
case directInfix:
|
|
||||||
return stage{
|
|
||||||
Evaluable: eval,
|
|
||||||
infixBuilder: operator.infixBuilder,
|
|
||||||
operatorPrecedence: operator.operatorPrecedence,
|
|
||||||
}, nil
|
|
||||||
case postfix:
|
|
||||||
if err = stack.push(stage{
|
|
||||||
operatorPrecedence: operator.operatorPrecedence,
|
|
||||||
Evaluable: eval,
|
|
||||||
}); err != nil {
|
|
||||||
return stage{}, err
|
|
||||||
}
|
|
||||||
eval, err = operator.f(c, p, stack.pop().Evaluable, operator.operatorPrecedence)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !mustOp {
|
|
||||||
p.Camouflage("operator")
|
|
||||||
return stage{Evaluable: eval}, nil
|
|
||||||
}
|
|
||||||
return stage{}, fmt.Errorf("unknown operator %s", op)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseIdent(c context.Context, p *Parser) (call string, alternative func() (Evaluable, error), err error) {
|
|
||||||
token := p.TokenText()
|
|
||||||
return token,
|
|
||||||
func() (Evaluable, error) {
|
|
||||||
fullname := token
|
|
||||||
|
|
||||||
keys := []Evaluable{p.Const(token)}
|
|
||||||
for {
|
|
||||||
scan := p.Scan()
|
|
||||||
switch scan {
|
|
||||||
case '.':
|
|
||||||
scan = p.Scan()
|
|
||||||
switch scan {
|
|
||||||
case scanner.Ident:
|
|
||||||
token = p.TokenText()
|
|
||||||
keys = append(keys, p.Const(token))
|
|
||||||
default:
|
|
||||||
return nil, p.Expected("field", scanner.Ident)
|
|
||||||
}
|
|
||||||
case '(':
|
|
||||||
args, err := p.parseArguments(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return p.callEvaluable(fullname, p.Var(keys...), args...), nil
|
|
||||||
case '[':
|
|
||||||
key, err := p.ParseExpression(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
switch p.Scan() {
|
|
||||||
case ']':
|
|
||||||
keys = append(keys, key)
|
|
||||||
default:
|
|
||||||
return nil, p.Expected("array key", ']')
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
p.Camouflage("variable", '.', '(', '[')
|
|
||||||
return p.Var(keys...), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Parser) parseArguments(c context.Context) (args []Evaluable, err error) {
|
|
||||||
if p.Scan() == ')' {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.Camouflage("scan arguments", ')')
|
|
||||||
for {
|
|
||||||
arg, err := p.ParseExpression(c)
|
|
||||||
args = append(args, arg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
switch p.Scan() {
|
|
||||||
case ')':
|
|
||||||
return args, nil
|
|
||||||
case ',':
|
|
||||||
default:
|
|
||||||
return nil, p.Expected("arguments", ')', ',')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func inArray(a, b interface{}) (interface{}, error) {
|
|
||||||
col, ok := b.([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("expected type []interface{} for in operator but got %T", b)
|
|
||||||
}
|
|
||||||
for _, value := range col {
|
|
||||||
if reflect.DeepEqual(a, value) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseIf(c context.Context, p *Parser, e Evaluable) (Evaluable, error) {
|
|
||||||
a, err := p.ParseExpression(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b := p.Const(nil)
|
|
||||||
switch p.Scan() {
|
|
||||||
case ':':
|
|
||||||
b, err = p.ParseExpression(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case scanner.EOF:
|
|
||||||
default:
|
|
||||||
return nil, p.Expected("<> ? <> : <>", ':', scanner.EOF)
|
|
||||||
}
|
|
||||||
return func(c context.Context, v interface{}) (interface{}, error) {
|
|
||||||
x, err := e(c, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if valX := reflect.ValueOf(x); x == nil || valX.IsZero() {
|
|
||||||
return b(c, v)
|
|
||||||
}
|
|
||||||
return a(c, v)
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseJSONArray(c context.Context, p *Parser) (Evaluable, error) {
|
|
||||||
evals := []Evaluable{}
|
|
||||||
for {
|
|
||||||
switch p.Scan() {
|
|
||||||
default:
|
|
||||||
p.Camouflage("array", ',', ']')
|
|
||||||
eval, err := p.ParseExpression(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
evals = append(evals, eval)
|
|
||||||
case ',':
|
|
||||||
case ']':
|
|
||||||
return func(c context.Context, v interface{}) (interface{}, error) {
|
|
||||||
vs := make([]interface{}, len(evals))
|
|
||||||
for i, e := range evals {
|
|
||||||
eval, err := e(c, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
vs[i] = eval
|
|
||||||
}
|
|
||||||
|
|
||||||
return vs, nil
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseJSONObject(c context.Context, p *Parser) (Evaluable, error) {
|
|
||||||
type kv struct {
|
|
||||||
key Evaluable
|
|
||||||
value Evaluable
|
|
||||||
}
|
|
||||||
evals := []kv{}
|
|
||||||
for {
|
|
||||||
switch p.Scan() {
|
|
||||||
default:
|
|
||||||
p.Camouflage("object", ',', '}')
|
|
||||||
key, err := p.ParseExpression(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if p.Scan() != ':' {
|
|
||||||
if err != nil {
|
|
||||||
return nil, p.Expected("object", ':')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
value, err := p.ParseExpression(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
evals = append(evals, kv{key, value})
|
|
||||||
case ',':
|
|
||||||
case '}':
|
|
||||||
return func(c context.Context, v interface{}) (interface{}, error) {
|
|
||||||
vs := map[string]interface{}{}
|
|
||||||
for _, e := range evals {
|
|
||||||
value, err := e.value(c, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
key, err := e.key.EvalString(c, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
vs[key] = value
|
|
||||||
}
|
|
||||||
return vs, nil
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,147 +0,0 @@
|
|||||||
package gval
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"text/scanner"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Parser parses expressions in a Language into an Evaluable
|
|
||||||
type Parser struct {
|
|
||||||
scanner scanner.Scanner
|
|
||||||
Language
|
|
||||||
lastScan rune
|
|
||||||
camouflage error
|
|
||||||
}
|
|
||||||
|
|
||||||
func newParser(expression string, l Language) *Parser {
|
|
||||||
sc := scanner.Scanner{}
|
|
||||||
sc.Init(strings.NewReader(expression))
|
|
||||||
sc.Error = func(*scanner.Scanner, string) {}
|
|
||||||
sc.Filename = expression + "\t"
|
|
||||||
p := &Parser{scanner: sc, Language: l}
|
|
||||||
p.resetScannerProperties()
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Parser) resetScannerProperties() {
|
|
||||||
p.scanner.Whitespace = scanner.GoWhitespace
|
|
||||||
p.scanner.Mode = scanner.GoTokens
|
|
||||||
p.scanner.IsIdentRune = func(r rune, pos int) bool {
|
|
||||||
return unicode.IsLetter(r) || r == '_' || (pos > 0 && unicode.IsDigit(r))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetWhitespace sets the behavior of the whitespace matcher. The given
|
|
||||||
// characters must be less than or equal to 0x20 (' ').
|
|
||||||
func (p *Parser) SetWhitespace(chars ...rune) {
|
|
||||||
var mask uint64
|
|
||||||
for _, char := range chars {
|
|
||||||
mask |= 1 << uint(char)
|
|
||||||
}
|
|
||||||
|
|
||||||
p.scanner.Whitespace = mask
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMode sets the tokens that the underlying scanner will match.
|
|
||||||
func (p *Parser) SetMode(mode uint) {
|
|
||||||
p.scanner.Mode = mode
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetIsIdentRuneFunc sets the function that matches ident characters in the
|
|
||||||
// underlying scanner.
|
|
||||||
func (p *Parser) SetIsIdentRuneFunc(fn func(ch rune, i int) bool) {
|
|
||||||
p.scanner.IsIdentRune = fn
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan reads the next token or Unicode character from source and returns it.
|
|
||||||
// It only recognizes tokens t for which the respective Mode bit (1<<-t) is set.
|
|
||||||
// It returns scanner.EOF at the end of the source.
|
|
||||||
func (p *Parser) Scan() rune {
|
|
||||||
if p.isCamouflaged() {
|
|
||||||
p.camouflage = nil
|
|
||||||
return p.lastScan
|
|
||||||
}
|
|
||||||
p.camouflage = nil
|
|
||||||
p.lastScan = p.scanner.Scan()
|
|
||||||
return p.lastScan
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Parser) isCamouflaged() bool {
|
|
||||||
return p.camouflage != nil && p.camouflage != errCamouflageAfterNext
|
|
||||||
}
|
|
||||||
|
|
||||||
// Camouflage rewind the last Scan(). The Parser holds the camouflage error until
|
|
||||||
// the next Scan()
|
|
||||||
// Do not call Rewind() on a camouflaged Parser
|
|
||||||
func (p *Parser) Camouflage(unit string, expected ...rune) {
|
|
||||||
if p.isCamouflaged() {
|
|
||||||
panic(fmt.Errorf("can only Camouflage() after Scan(): %w", p.camouflage))
|
|
||||||
}
|
|
||||||
p.camouflage = p.Expected(unit, expected...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Peek returns the next Unicode character in the source without advancing
|
|
||||||
// the scanner. It returns EOF if the scanner's position is at the last
|
|
||||||
// character of the source.
|
|
||||||
// Do not call Peek() on a camouflaged Parser
|
|
||||||
func (p *Parser) Peek() rune {
|
|
||||||
if p.isCamouflaged() {
|
|
||||||
panic("can not Peek() on camouflaged Parser")
|
|
||||||
}
|
|
||||||
return p.scanner.Peek()
|
|
||||||
}
|
|
||||||
|
|
||||||
var errCamouflageAfterNext = fmt.Errorf("Camouflage() after Next()")
|
|
||||||
|
|
||||||
// Next reads and returns the next Unicode character.
|
|
||||||
// It returns EOF at the end of the source.
|
|
||||||
// Do not call Next() on a camouflaged Parser
|
|
||||||
func (p *Parser) Next() rune {
|
|
||||||
if p.isCamouflaged() {
|
|
||||||
panic("can not Next() on camouflaged Parser")
|
|
||||||
}
|
|
||||||
p.camouflage = errCamouflageAfterNext
|
|
||||||
return p.scanner.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TokenText returns the string corresponding to the most recently scanned token.
|
|
||||||
// Valid after calling Scan().
|
|
||||||
func (p *Parser) TokenText() string {
|
|
||||||
return p.scanner.TokenText()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expected returns an error signaling an unexpected Scan() result
|
|
||||||
func (p *Parser) Expected(unit string, expected ...rune) error {
|
|
||||||
return unexpectedRune{unit, expected, p.lastScan}
|
|
||||||
}
|
|
||||||
|
|
||||||
type unexpectedRune struct {
|
|
||||||
unit string
|
|
||||||
expected []rune
|
|
||||||
got rune
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err unexpectedRune) Error() string {
|
|
||||||
exp := bytes.Buffer{}
|
|
||||||
runes := err.expected
|
|
||||||
switch len(runes) {
|
|
||||||
default:
|
|
||||||
for _, r := range runes[:len(runes)-2] {
|
|
||||||
exp.WriteString(scanner.TokenString(r))
|
|
||||||
exp.WriteString(", ")
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
case 2:
|
|
||||||
exp.WriteString(scanner.TokenString(runes[len(runes)-2]))
|
|
||||||
exp.WriteString(" or ")
|
|
||||||
fallthrough
|
|
||||||
case 1:
|
|
||||||
exp.WriteString(scanner.TokenString(runes[len(runes)-1]))
|
|
||||||
case 0:
|
|
||||||
return fmt.Sprintf("unexpected %s while scanning %s", scanner.TokenString(err.got), err.unit)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("unexpected %s while scanning %s expected %s", scanner.TokenString(err.got), err.unit, exp.String())
|
|
||||||
}
|
|
||||||
@ -1,148 +0,0 @@
|
|||||||
package gval
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"text/scanner"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParser_Scan(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input string
|
|
||||||
Language
|
|
||||||
do func(p *Parser)
|
|
||||||
wantScan rune
|
|
||||||
wantToken string
|
|
||||||
wantPanic bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "camouflage",
|
|
||||||
input: "$abc",
|
|
||||||
do: func(p *Parser) {
|
|
||||||
p.Scan()
|
|
||||||
p.Camouflage("test")
|
|
||||||
},
|
|
||||||
wantScan: '$',
|
|
||||||
wantToken: "$",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "camouflage with next",
|
|
||||||
input: "$abc",
|
|
||||||
do: func(p *Parser) {
|
|
||||||
p.Scan()
|
|
||||||
p.Camouflage("test")
|
|
||||||
p.Next()
|
|
||||||
},
|
|
||||||
wantPanic: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "camouflage scan camouflage",
|
|
||||||
input: "$abc",
|
|
||||||
do: func(p *Parser) {
|
|
||||||
p.Scan()
|
|
||||||
p.Camouflage("test")
|
|
||||||
p.Scan()
|
|
||||||
p.Camouflage("test2")
|
|
||||||
},
|
|
||||||
wantScan: '$',
|
|
||||||
wantToken: "$",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "camouflage with peek",
|
|
||||||
input: "$abc",
|
|
||||||
do: func(p *Parser) {
|
|
||||||
p.Scan()
|
|
||||||
p.Camouflage("test")
|
|
||||||
p.Peek()
|
|
||||||
},
|
|
||||||
wantPanic: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "next and peek",
|
|
||||||
input: "$#abc",
|
|
||||||
do: func(p *Parser) {
|
|
||||||
p.Scan()
|
|
||||||
p.Next()
|
|
||||||
p.Peek()
|
|
||||||
},
|
|
||||||
wantScan: scanner.Ident,
|
|
||||||
wantToken: "abc",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "scan token camouflage token",
|
|
||||||
input: "abc",
|
|
||||||
do: func(p *Parser) {
|
|
||||||
p.Scan()
|
|
||||||
p.TokenText()
|
|
||||||
p.Camouflage("test")
|
|
||||||
},
|
|
||||||
wantScan: scanner.Ident,
|
|
||||||
wantToken: "abc",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "scan token peek camouflage token",
|
|
||||||
input: "abc",
|
|
||||||
do: func(p *Parser) {
|
|
||||||
p.Scan()
|
|
||||||
p.TokenText()
|
|
||||||
p.Peek()
|
|
||||||
p.Camouflage("test")
|
|
||||||
},
|
|
||||||
wantScan: scanner.Ident,
|
|
||||||
wantToken: "abc",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "tokenize all whitespace",
|
|
||||||
input: "foo\tbar\nbaz",
|
|
||||||
do: func(p *Parser) {
|
|
||||||
p.SetWhitespace()
|
|
||||||
p.Scan()
|
|
||||||
},
|
|
||||||
wantScan: '\t',
|
|
||||||
wantToken: "\t",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "custom ident",
|
|
||||||
input: "$#foo",
|
|
||||||
do: func(p *Parser) {
|
|
||||||
p.SetIsIdentRuneFunc(func(ch rune, i int) bool { return unicode.IsLetter(ch) || ch == '#' })
|
|
||||||
p.Scan()
|
|
||||||
},
|
|
||||||
wantScan: scanner.Ident,
|
|
||||||
wantToken: "#foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "do not scan idents",
|
|
||||||
input: "abc",
|
|
||||||
do: func(p *Parser) {
|
|
||||||
p.SetMode(scanner.GoTokens ^ scanner.ScanIdents)
|
|
||||||
p.Scan()
|
|
||||||
},
|
|
||||||
wantScan: 'b',
|
|
||||||
wantToken: "b",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
defer func() {
|
|
||||||
err := recover()
|
|
||||||
if err != nil && !tt.wantPanic {
|
|
||||||
t.Fatalf("unexpected panic: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
p := newParser(tt.input, tt.Language)
|
|
||||||
tt.do(p)
|
|
||||||
if tt.wantPanic {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
scan := p.Scan()
|
|
||||||
token := p.TokenText()
|
|
||||||
|
|
||||||
if scan != tt.wantScan || token != tt.wantToken {
|
|
||||||
t.Errorf("Parser.Scan() = %v (%v), want %v (%v)", scan, token, tt.wantScan, tt.wantToken)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,124 +0,0 @@
|
|||||||
package gval
|
|
||||||
|
|
||||||
// Courtesy of abrander
|
|
||||||
// ref: https://gist.github.com/abrander/fa05ae9b181b48ffe7afb12c961b6e90
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
hello = "hello"
|
|
||||||
empty struct{}
|
|
||||||
empty2 *string
|
|
||||||
|
|
||||||
values = []interface{}{
|
|
||||||
-1,
|
|
||||||
0,
|
|
||||||
12,
|
|
||||||
13,
|
|
||||||
"",
|
|
||||||
"hello",
|
|
||||||
&hello,
|
|
||||||
nil,
|
|
||||||
"nil",
|
|
||||||
empty,
|
|
||||||
empty2,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
time.Now(),
|
|
||||||
rune('r'),
|
|
||||||
int64(34),
|
|
||||||
time.Duration(0),
|
|
||||||
"true",
|
|
||||||
"false",
|
|
||||||
"\ntrue\n",
|
|
||||||
"\nfalse\n",
|
|
||||||
"12",
|
|
||||||
"nil",
|
|
||||||
"arg1",
|
|
||||||
"arg2",
|
|
||||||
int(12),
|
|
||||||
int32(12),
|
|
||||||
int64(12),
|
|
||||||
complex(1.0, 1.0),
|
|
||||||
[]byte{0, 0, 0},
|
|
||||||
[]int{0, 0, 0},
|
|
||||||
[]string{},
|
|
||||||
"[]",
|
|
||||||
"{}",
|
|
||||||
"\"\"",
|
|
||||||
"\"12\"",
|
|
||||||
"\"hello\"",
|
|
||||||
".*",
|
|
||||||
"==",
|
|
||||||
"!=",
|
|
||||||
">",
|
|
||||||
">=",
|
|
||||||
"<",
|
|
||||||
"<=",
|
|
||||||
"=~",
|
|
||||||
"!~",
|
|
||||||
"in",
|
|
||||||
"&&",
|
|
||||||
"||",
|
|
||||||
"^",
|
|
||||||
"&",
|
|
||||||
"|",
|
|
||||||
">>",
|
|
||||||
"<<",
|
|
||||||
"+",
|
|
||||||
"-",
|
|
||||||
"*",
|
|
||||||
"/",
|
|
||||||
"%",
|
|
||||||
"**",
|
|
||||||
"-",
|
|
||||||
"!",
|
|
||||||
"~",
|
|
||||||
"?",
|
|
||||||
":",
|
|
||||||
"??",
|
|
||||||
"+",
|
|
||||||
"-",
|
|
||||||
"*",
|
|
||||||
"/",
|
|
||||||
"%",
|
|
||||||
"**",
|
|
||||||
"&",
|
|
||||||
"|",
|
|
||||||
"^",
|
|
||||||
">>",
|
|
||||||
"<<",
|
|
||||||
",",
|
|
||||||
"(",
|
|
||||||
")",
|
|
||||||
"[",
|
|
||||||
"]",
|
|
||||||
"\n",
|
|
||||||
"\000",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const SEED = 1487873697990155515
|
|
||||||
|
|
||||||
func BenchmarkRandom(bench *testing.B) {
|
|
||||||
rand.Seed(SEED)
|
|
||||||
for i := 0; i < bench.N; i++ {
|
|
||||||
num := rand.Intn(3) + 2
|
|
||||||
expression := ""
|
|
||||||
|
|
||||||
for n := 0; n < num; n++ {
|
|
||||||
expression += fmt.Sprintf(" %s", getRandom(values))
|
|
||||||
}
|
|
||||||
|
|
||||||
Evaluate(expression, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRandom(haystack []interface{}) interface{} {
|
|
||||||
i := rand.Intn(len(haystack))
|
|
||||||
return haystack[i]
|
|
||||||
}
|
|
||||||
256
src/main.go
256
src/main.go
@ -1,256 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
_ "embed"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"okemu/config"
|
|
||||||
"okemu/debug"
|
|
||||||
"okemu/debug/zrcp"
|
|
||||||
"okemu/forms"
|
|
||||||
"okemu/logger"
|
|
||||||
"okemu/okean240"
|
|
||||||
"runtime"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
|
||||||
"fyne.io/fyne/v2/canvas"
|
|
||||||
"fyne.io/fyne/v2/widget"
|
|
||||||
"github.com/loov/hrtime"
|
|
||||||
"github.com/romychs/z80go/dis"
|
|
||||||
)
|
|
||||||
|
|
||||||
var Version = "v1.0.3"
|
|
||||||
var BuildTime = "2026-04-08"
|
|
||||||
|
|
||||||
const defaultTimerClkPeriod = 433
|
|
||||||
const defaultCpuClkPeriod = 310
|
|
||||||
|
|
||||||
const windowsTimerClkPeriod = 400
|
|
||||||
const windowsCpuClkPeriod = 300
|
|
||||||
|
|
||||||
const maxDelta = 3
|
|
||||||
const diffScale = 15.0
|
|
||||||
|
|
||||||
////go:embed hex/m80.hex
|
|
||||||
//var serialBytes []byte
|
|
||||||
|
|
||||||
////go:embed bin/jack.com
|
|
||||||
//var ramBytes []byte
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
fmt.Printf("Starting Ocean-240.2 emulator %s build at %s\n", Version, BuildTime)
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// base log init
|
|
||||||
logger.InitLogging()
|
|
||||||
|
|
||||||
// load config yml file
|
|
||||||
config.LoadConfig()
|
|
||||||
|
|
||||||
conf := config.GetConfig()
|
|
||||||
|
|
||||||
// Reconfigure logging by config values
|
|
||||||
logger.ReconfigureLogging(conf)
|
|
||||||
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
cpuClkPeriod.Store(windowsCpuClkPeriod)
|
|
||||||
timerClkPeriod.Store(windowsTimerClkPeriod)
|
|
||||||
} else {
|
|
||||||
cpuClkPeriod.Store(defaultCpuClkPeriod)
|
|
||||||
timerClkPeriod.Store(defaultTimerClkPeriod)
|
|
||||||
}
|
|
||||||
|
|
||||||
debugger := debug.NewDebugger()
|
|
||||||
computer := okean240.NewComputer(conf, debugger)
|
|
||||||
|
|
||||||
computer.AutoLoadFloppy()
|
|
||||||
|
|
||||||
disassm := dis.NewDisassembler(computer)
|
|
||||||
|
|
||||||
w, raster, label := forms.NewMainWindow(computer, conf, "Океан 240.2 "+Version)
|
|
||||||
|
|
||||||
//dezog := dzrp.NewDZRP(conf, debugger, disassm, computer)
|
|
||||||
dezog := zrcp.NewZRCP(conf, debugger, disassm, computer)
|
|
||||||
|
|
||||||
go cpuClock(computer, dezog)
|
|
||||||
go timerClock(computer)
|
|
||||||
go screen(ctx, computer, raster, label)
|
|
||||||
|
|
||||||
if conf.Debugger.Enabled {
|
|
||||||
go dezog.SetupTcpHandler()
|
|
||||||
}
|
|
||||||
|
|
||||||
(*w).ShowAndRun()
|
|
||||||
computer.AutoSaveFloppy()
|
|
||||||
logger.CloseLogs()
|
|
||||||
}
|
|
||||||
|
|
||||||
func screen(ctx context.Context, computer *okean240.ComputerType, raster *canvas.Raster, label *widget.Label) {
|
|
||||||
ticker := time.NewTicker(20 * time.Millisecond)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
frame := 0
|
|
||||||
var pre uint64 = 0
|
|
||||||
var preTim uint64 = 0
|
|
||||||
var cpuFreq float64 = 0
|
|
||||||
var timerFreq float64 = 0
|
|
||||||
timeStart := hrtime.Now()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ticker.C:
|
|
||||||
frame++
|
|
||||||
// redraw screen here
|
|
||||||
fyne.Do(func() {
|
|
||||||
// status for every 50 frames
|
|
||||||
if frame%10 == 0 {
|
|
||||||
timeElapsed := hrtime.Since(timeStart)
|
|
||||||
period := float64(timeElapsed.Nanoseconds()) / 1_000_000.0
|
|
||||||
|
|
||||||
cpuFreq = math.Round(float64(cpuTicks.Load()-pre)/period) / 1000.0
|
|
||||||
timerFreq = math.Round(float64(timerTicks.Load()-preTim)/period) / 1000.0
|
|
||||||
|
|
||||||
adjustPeriods(computer, cpuFreq, timerFreq)
|
|
||||||
|
|
||||||
if frame%50 == 0 {
|
|
||||||
label.SetText(formatLabel(computer, cpuFreq, timerFreq))
|
|
||||||
//log.Debugf("Cpu clk period: %d, Timer clock period: %d, frame time: %1.3fms", cpuClkPeriod.Load(), timerClkPeriod.Load(), period/5.0)
|
|
||||||
//logger.FlushLogs()
|
|
||||||
}
|
|
||||||
pre = cpuTicks.Load()
|
|
||||||
preTim = timerTicks.Load()
|
|
||||||
timeStart = hrtime.Now()
|
|
||||||
}
|
|
||||||
raster.Refresh()
|
|
||||||
})
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// adjustPeriods Adjust periods for CPU and Timer clock frequencies
|
|
||||||
func adjustPeriods(c *okean240.ComputerType, cpuFreq float64, timerFreq float64) {
|
|
||||||
// adjust cpu clock if not full speed
|
|
||||||
if !c.FullSpeed() {
|
|
||||||
calcPeriod(cpuFreq, okean240.CPUFrequency, okean240.CPUFrequencyHi, okean240.CPUFrequencyLow, &cpuClkPeriod)
|
|
||||||
}
|
|
||||||
// adjust timerClock clock
|
|
||||||
calcPeriod(timerFreq, okean240.TimerFrequency, okean240.TimerFrequencyHi, okean240.TimerFrequencyLow, &timerClkPeriod)
|
|
||||||
}
|
|
||||||
|
|
||||||
// calcPeriod calc new value period to adjust frequency of timer or CPU
|
|
||||||
func calcPeriod(curFreq float64, destFreq float64, hiLimit float64, loLimit float64, period *atomic.Int64) {
|
|
||||||
if curFreq > hiLimit && period.Load() < 2000 {
|
|
||||||
period.Add(calcDelta(curFreq, destFreq))
|
|
||||||
} else if curFreq < loLimit && period.Load() > 0 {
|
|
||||||
period.Add(-calcDelta(curFreq, destFreq))
|
|
||||||
if period.Load() < 0 {
|
|
||||||
period.Store(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// calcDelta calculate step to change period
|
|
||||||
func calcDelta(currentFreq float64, destFreq float64) int64 {
|
|
||||||
delta := int64(math.Round(math.Abs(destFreq-currentFreq) * diffScale))
|
|
||||||
if delta < 1 {
|
|
||||||
return 1
|
|
||||||
} else if delta > maxDelta {
|
|
||||||
return maxDelta
|
|
||||||
}
|
|
||||||
return delta
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatLabel(computer *okean240.ComputerType, freq float64, freqTim float64) string {
|
|
||||||
return fmt.Sprintf("Screen size: %dx%d | Fcpu: %1.3fMHz | Ftmr: %1.3fMHz | Debugger: %s", // CP:%d TP:%d",
|
|
||||||
computer.ScreenWidth(), computer.ScreenHeight(), freq, freqTim, computer.DebuggerState())
|
|
||||||
}
|
|
||||||
|
|
||||||
var timerTicks atomic.Uint64
|
|
||||||
var timerClkPeriod atomic.Int64 // period in nanos for 1.5MHz frequency
|
|
||||||
var cpuClkPeriod atomic.Int64 // period in nanos for 2.5MHz frequency
|
|
||||||
|
|
||||||
func timerClock(computer *okean240.ComputerType) {
|
|
||||||
timeStart := hrtime.Now()
|
|
||||||
for {
|
|
||||||
elapsed := hrtime.Since(timeStart)
|
|
||||||
if int64(elapsed) > timerClkPeriod.Load() {
|
|
||||||
timeStart = hrtime.Now()
|
|
||||||
computer.TimerClk()
|
|
||||||
timerTicks.Add(1)
|
|
||||||
runtime.Gosched()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var cpuTicks atomic.Uint64
|
|
||||||
|
|
||||||
func cpuClock(computer *okean240.ComputerType, dezog debug.DEZOG) {
|
|
||||||
|
|
||||||
cpuTicks.Store(0)
|
|
||||||
nextTick := uint64(0)
|
|
||||||
|
|
||||||
var bp uint16
|
|
||||||
var bpType byte
|
|
||||||
timeStart := hrtime.Now()
|
|
||||||
|
|
||||||
for {
|
|
||||||
elapsed := hrtime.Since(timeStart)
|
|
||||||
if computer.FullSpeed() || int64(elapsed) >= cpuClkPeriod.Load() {
|
|
||||||
timeStart = hrtime.Now()
|
|
||||||
bp = 0
|
|
||||||
bpType = 0
|
|
||||||
|
|
||||||
// 2.5MHz frequency
|
|
||||||
cpuTicks.Add(1)
|
|
||||||
if !computer.PendingHardReset() && !computer.PendingCpuReset() {
|
|
||||||
if computer.FullSpeed() {
|
|
||||||
// Max frequency
|
|
||||||
_, bp, bpType = computer.Do()
|
|
||||||
nextTick = cpuTicks.Load()
|
|
||||||
} else if cpuTicks.Load() >= nextTick {
|
|
||||||
var t uint32
|
|
||||||
t, bp, bpType = computer.Do()
|
|
||||||
nextTick = cpuTicks.Load() + uint64(t)
|
|
||||||
runtime.Gosched()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Breakpoint hit
|
|
||||||
if bp > 0 || bpType != 0 {
|
|
||||||
dezog.BreakpointHit(bp, bpType)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if computer.PendingHardReset() {
|
|
||||||
computer.HardReset()
|
|
||||||
} else {
|
|
||||||
computer.Reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//func initPerf() {
|
|
||||||
// f, err := os.Create("okemu.prof")
|
|
||||||
// if err != nil {
|
|
||||||
// log.Warn("Can not create prof file", err)
|
|
||||||
// }
|
|
||||||
// defer func(f *os.File) {
|
|
||||||
// err := f.Close()
|
|
||||||
// if err != nil {
|
|
||||||
// log.Warn("Can not close prof file", err)
|
|
||||||
// }
|
|
||||||
// }(f)
|
|
||||||
// if err := pprof.StartCPUProfile(f); err != nil {
|
|
||||||
// log.Warn("Can not start CPU profiling", err)
|
|
||||||
// }
|
|
||||||
// defer pprof.StopCPUProfile()
|
|
||||||
//}
|
|
||||||
@ -1,557 +0,0 @@
|
|||||||
package okean240
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
_ "embed"
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"image/color"
|
|
||||||
"okemu/config"
|
|
||||||
"okemu/debug"
|
|
||||||
"okemu/debug/breakpoint"
|
|
||||||
"okemu/gval"
|
|
||||||
"okemu/okean240/fdc"
|
|
||||||
"okemu/okean240/pic"
|
|
||||||
"okemu/okean240/pit"
|
|
||||||
"okemu/okean240/usart"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
|
||||||
"github.com/romychs/z80go"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
const DefaultCPUFrequency = 2_500_000
|
|
||||||
const CPUFrequencyLow float64 = 2.48
|
|
||||||
const CPUFrequency float64 = 2.50
|
|
||||||
const CPUFrequencyHi float64 = 2.52
|
|
||||||
const TimerFrequencyLow float64 = 1.47
|
|
||||||
const TimerFrequency float64 = 1.50
|
|
||||||
const TimerFrequencyHi float64 = 1.53
|
|
||||||
|
|
||||||
type ComputerType struct {
|
|
||||||
cpu *z80go.CPU
|
|
||||||
memory Memory
|
|
||||||
ioPorts [256]byte
|
|
||||||
cycles uint64
|
|
||||||
tstatesPartial uint64
|
|
||||||
dd17EnableOut bool
|
|
||||||
colorMode bool
|
|
||||||
screenWidth int
|
|
||||||
screenHeight int
|
|
||||||
vRAM *MemoryBlock
|
|
||||||
palette byte
|
|
||||||
bgColor byte
|
|
||||||
pit *pit.I8253
|
|
||||||
usart *usart.I8251
|
|
||||||
pic *pic.I8259
|
|
||||||
fdc *fdc.FloppyDriveController
|
|
||||||
kbdBuffer []byte
|
|
||||||
vShift byte
|
|
||||||
hShift byte
|
|
||||||
cpuFrequency uint32
|
|
||||||
//
|
|
||||||
debugger *debug.Debugger
|
|
||||||
config *config.OkEmuConfig
|
|
||||||
kbAck atomic.Bool
|
|
||||||
fullSpeed atomic.Bool
|
|
||||||
pendingCpuReset atomic.Bool
|
|
||||||
pendingHardReset atomic.Bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type Snapshot struct {
|
|
||||||
CPU *z80go.CPU `json:"cpu,omitempty"`
|
|
||||||
Memory string `json:"memory,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
const VRAMBlock0 = 3
|
|
||||||
const VRAMBlock1 = 7
|
|
||||||
const VidVsuBit = 0x80
|
|
||||||
const VidColorBit = 0x40
|
|
||||||
|
|
||||||
//type ComputerInterface interface {
|
|
||||||
// Run()
|
|
||||||
// Reset()
|
|
||||||
// GetPixel(x uint16, y uint16) color.RGBA
|
|
||||||
// Do() uint64
|
|
||||||
// TimerClk()
|
|
||||||
// PutKey(key *fyne.KeyEvent)
|
|
||||||
// PutRune(key rune)
|
|
||||||
// PutCtrlKey(shortcut fyne.Shortcut)
|
|
||||||
// SaveFloppy()
|
|
||||||
// LoadFloppy()
|
|
||||||
// CPUState() *z80.CPU
|
|
||||||
// SetCPUState(state *z80.CPU)
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (c *ComputerType) CPUState() *z80go.CPU {
|
|
||||||
return c.cpu.GetState()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) SetCPUState(state *z80go.CPU) {
|
|
||||||
c.cpu.SetState(state)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) M1MemRead(addr uint16) byte {
|
|
||||||
return c.memory.M1MemRead(addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) MemRead(addr uint16) byte {
|
|
||||||
return c.memory.MemRead(addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) MemWrite(addr uint16, val byte) {
|
|
||||||
c.memory.MemWrite(addr, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewComputer Builds new computer
|
|
||||||
func NewComputer(cfg *config.OkEmuConfig, deb *debug.Debugger) *ComputerType {
|
|
||||||
c := ComputerType{}
|
|
||||||
c.config = cfg
|
|
||||||
c.cpu = z80go.NewCPU(&c)
|
|
||||||
c.debugger = deb
|
|
||||||
|
|
||||||
c.HardReset()
|
|
||||||
|
|
||||||
return &c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset Only CPU reset.
|
|
||||||
func (c *ComputerType) Reset() {
|
|
||||||
// CPU
|
|
||||||
c.cpu.Reset()
|
|
||||||
c.cycles = 0
|
|
||||||
c.tstatesPartial = 0
|
|
||||||
c.pendingCpuReset.Store(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HardReset full computer reset, Use SetPendingHardReset flag to set flag before call this method
|
|
||||||
func (c *ComputerType) HardReset() {
|
|
||||||
c.cpu.Reset()
|
|
||||||
c.memory = Memory{}
|
|
||||||
c.memory.Init(c.config.MonitorFile, c.config.CPMFile)
|
|
||||||
|
|
||||||
c.cycles = 0
|
|
||||||
c.tstatesPartial = 0
|
|
||||||
c.dd17EnableOut = false
|
|
||||||
c.screenWidth = 512
|
|
||||||
c.screenHeight = 256
|
|
||||||
c.vRAM = c.memory.allMemory[3]
|
|
||||||
c.palette = 0
|
|
||||||
c.bgColor = 0
|
|
||||||
|
|
||||||
c.vShift = 0
|
|
||||||
c.hShift = 0
|
|
||||||
|
|
||||||
c.pit = pit.New()
|
|
||||||
c.kbAck.Store(false)
|
|
||||||
c.usart = usart.New()
|
|
||||||
c.pic = pic.NewI8259()
|
|
||||||
c.fdc = fdc.NewFDC(c.config)
|
|
||||||
|
|
||||||
c.cpuFrequency = DefaultCPUFrequency
|
|
||||||
c.fullSpeed.Store(false)
|
|
||||||
c.pendingHardReset.Store(false)
|
|
||||||
c.pendingCpuReset.Store(false)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) getContext() map[string]interface{} {
|
|
||||||
ctx := make(map[string]interface{})
|
|
||||||
s := c.cpu.GetState()
|
|
||||||
ctx["A"] = s.A
|
|
||||||
ctx["B"] = s.B
|
|
||||||
ctx["C"] = s.C
|
|
||||||
ctx["D"] = s.D
|
|
||||||
ctx["E"] = s.E
|
|
||||||
ctx["H"] = s.H
|
|
||||||
ctx["L"] = s.L
|
|
||||||
ctx["A'"] = s.AAlt
|
|
||||||
ctx["B'"] = s.BAlt
|
|
||||||
ctx["C'"] = s.CAlt
|
|
||||||
ctx["D'"] = s.DAlt
|
|
||||||
ctx["E'"] = s.EAlt
|
|
||||||
ctx["H'"] = s.HAlt
|
|
||||||
ctx["L'"] = s.LAlt
|
|
||||||
ctx["PC"] = s.PC
|
|
||||||
ctx["SP"] = s.SP
|
|
||||||
ctx["IX"] = s.IX
|
|
||||||
ctx["IY"] = s.IY
|
|
||||||
ctx["ZF"] = s.Flags.Z
|
|
||||||
ctx["SF"] = s.Flags.S
|
|
||||||
ctx["NF"] = s.Flags.N
|
|
||||||
ctx["PF"] = s.Flags.P
|
|
||||||
ctx["HF"] = s.Flags.H
|
|
||||||
ctx["YF"] = s.Flags.Y
|
|
||||||
ctx["XF"] = s.Flags.X
|
|
||||||
ctx["CF"] = s.Flags.C
|
|
||||||
ctx["BC"] = uint16(s.B)<<8 | uint16(s.C)
|
|
||||||
ctx["DE"] = uint16(s.D)<<8 | uint16(s.E)
|
|
||||||
ctx["HL"] = uint16(s.H)<<8 | uint16(s.L)
|
|
||||||
ctx["AF"] = uint16(s.A)<<8 | uint16(s.Flags.AsByte())
|
|
||||||
ctx["MEM"] = c.memory.MemRead
|
|
||||||
ctx["IO"] = c.IORead
|
|
||||||
return ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) Do() (uint32, uint16, byte) {
|
|
||||||
ticks := uint32(0)
|
|
||||||
var memAccess *map[uint16]byte
|
|
||||||
if c.debugger.StepMode() {
|
|
||||||
if c.debugger.RunMode() || c.debugger.DoStep() {
|
|
||||||
if c.debugger.RunInst() > 0 {
|
|
||||||
// skip first instruction after run-mode activated
|
|
||||||
bpHit, bp := c.debugger.CheckBreakpoints(c.getContext())
|
|
||||||
if bpHit {
|
|
||||||
//c.debugger.SetRunMode(false)
|
|
||||||
return 0, bp, 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.debugger.SaveHistory(c.cpu.GetState())
|
|
||||||
ticks, memAccess = c.cpu.RunInstruction()
|
|
||||||
mHit, mAddr, mTyp := c.debugger.CheckMemBreakpoints(memAccess)
|
|
||||||
if mHit {
|
|
||||||
return ticks, mAddr, mTyp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ticks, memAccess = c.cpu.RunInstruction()
|
|
||||||
}
|
|
||||||
c.cycles += uint64(ticks)
|
|
||||||
c.tstatesPartial += uint64(ticks)
|
|
||||||
return ticks, 0, 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) GetPixel(x uint16, y uint16) color.RGBA {
|
|
||||||
if y > 255 {
|
|
||||||
return CWhite
|
|
||||||
}
|
|
||||||
|
|
||||||
var addr uint16
|
|
||||||
var resColor color.RGBA
|
|
||||||
|
|
||||||
if c.colorMode {
|
|
||||||
if x > 255 {
|
|
||||||
return CWhite
|
|
||||||
}
|
|
||||||
|
|
||||||
var offset uint16
|
|
||||||
if (c.vShift != 0) && (y > 255-uint16(c.vShift)) {
|
|
||||||
offset = 0x100
|
|
||||||
} else {
|
|
||||||
offset = 0
|
|
||||||
}
|
|
||||||
y += uint16(c.vShift) & 0x00ff
|
|
||||||
x += uint16(c.hShift-7) & 0x00ff
|
|
||||||
|
|
||||||
// Color 256x256 mode
|
|
||||||
addr = ((x & 0xf8) << 6) | y
|
|
||||||
|
|
||||||
a1 := (addr - offset) & 0x3fff
|
|
||||||
a2 := (a1 + 0x100) & 0x3fff
|
|
||||||
|
|
||||||
cl := (c.vRAM.memory[a1] >> (x & 0x07)) & 1
|
|
||||||
cl |= ((c.vRAM.memory[a2] >> (x & 0x07)) & 1) << 1
|
|
||||||
if cl == 0 {
|
|
||||||
//resColor = BgColorPalette[c.bgColor]
|
|
||||||
resColor = ColorPalette[c.palette][cl]
|
|
||||||
} else {
|
|
||||||
resColor = ColorPalette[c.palette][cl]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if x > 511 {
|
|
||||||
return CWhite
|
|
||||||
}
|
|
||||||
|
|
||||||
var offset uint16
|
|
||||||
if (c.vShift != 0) && (y > 255-uint16(c.vShift)) {
|
|
||||||
offset = 0x100
|
|
||||||
} else {
|
|
||||||
offset = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shifts
|
|
||||||
y += uint16(c.vShift) & 0x00ff
|
|
||||||
x += uint16(c.hShift-7) & 0x001ff
|
|
||||||
|
|
||||||
// Mono 512x256 mode
|
|
||||||
addr = (((x & 0x1f8) << 5) + y) - offset
|
|
||||||
pix := c.vRAM.memory[addr&0x3fff] >> (x & 0x07) & 1
|
|
||||||
if c.palette == 6 {
|
|
||||||
if pix == 0 {
|
|
||||||
resColor = CBlack
|
|
||||||
} else {
|
|
||||||
resColor = CLGreen
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if pix == 0 {
|
|
||||||
resColor = BgColorPalette[c.bgColor]
|
|
||||||
} else {
|
|
||||||
resColor = MonoPalette[c.palette]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resColor
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) ScreenWidth() int {
|
|
||||||
return c.screenWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) ScreenHeight() int {
|
|
||||||
return c.screenHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) Cycles() uint64 {
|
|
||||||
return c.cycles
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) ResetTStatesPartial() {
|
|
||||||
c.tstatesPartial = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) TStatesPartial() uint64 {
|
|
||||||
return c.tstatesPartial
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) TimerClk() {
|
|
||||||
// DD70 KR580VI53 CLK0, CKL1 @ 1.5MHz
|
|
||||||
c.pit.Tick(0)
|
|
||||||
c.pit.Tick(1)
|
|
||||||
|
|
||||||
// IRQ from timer
|
|
||||||
if c.pit.Fired(0) {
|
|
||||||
c.pic.SetIRQ(RstTimerNo)
|
|
||||||
}
|
|
||||||
// clock for SIO KR580VV51
|
|
||||||
if c.pit.Fired(1) {
|
|
||||||
c.usart.Tick()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) SaveFloppy(drive byte) error {
|
|
||||||
return c.fdc.SaveFloppy(drive)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) SetSerialBytes(bytes []byte) {
|
|
||||||
c.usart.SetRxBytes(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) SetRamBytes(bytes []byte) {
|
|
||||||
addr := 0x100
|
|
||||||
for i := 0; i < len(bytes); i++ {
|
|
||||||
c.memory.MemWrite(uint16(addr), bytes[i])
|
|
||||||
addr++
|
|
||||||
}
|
|
||||||
pages := len(bytes) / 256
|
|
||||||
if len(bytes)%256 != 0 {
|
|
||||||
pages++
|
|
||||||
}
|
|
||||||
log.Debugf("Loaded bytes: %d; blocks: %d", len(bytes), pages)
|
|
||||||
//c.cpu.SP = 0x100
|
|
||||||
//c.cpu.PC = 0x100
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) Dump(start uint16, length uint16) {
|
|
||||||
file, err := os.Create("dump.dat")
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer func(file *os.File) {
|
|
||||||
err := file.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
}(file)
|
|
||||||
|
|
||||||
var buffer []byte
|
|
||||||
for addr := start; addr < start+length; addr++ {
|
|
||||||
buffer = append(buffer, c.memory.MemRead(addr))
|
|
||||||
}
|
|
||||||
_, err = file.Write(buffer)
|
|
||||||
err = binary.Write(file, binary.LittleEndian, buffer)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Save memory dump failed:", err)
|
|
||||||
} else {
|
|
||||||
log.Debug("Memory dump saved successfully")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) CPUFrequency() uint32 {
|
|
||||||
return c.cpuFrequency
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) SetCPUFrequency(frequency uint32) {
|
|
||||||
c.cpuFrequency = frequency
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) DebuggerState() string {
|
|
||||||
if c.debugger.StepMode() {
|
|
||||||
if c.debugger.RunMode() {
|
|
||||||
return "Run"
|
|
||||||
}
|
|
||||||
return "Step"
|
|
||||||
}
|
|
||||||
return "Off"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) memoryAsHexStr() string {
|
|
||||||
res := ""
|
|
||||||
for addr := 0; addr <= 65535; addr++ {
|
|
||||||
res += fmt.Sprintf("%02X", c.memory.MemRead(uint16(addr)))
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) SaveSnapshot(fn string) error {
|
|
||||||
// take snapshot
|
|
||||||
s := Snapshot{
|
|
||||||
CPU: c.cpu.GetState(),
|
|
||||||
Memory: c.memoryAsHexStr(),
|
|
||||||
}
|
|
||||||
// convert to JSON
|
|
||||||
b, err := json.Marshal(s)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// and save
|
|
||||||
err = os.WriteFile(fn, b, 0644)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) LoadSnapshot(fn string) error {
|
|
||||||
// read snapshot file
|
|
||||||
b, err := os.ReadFile(fn)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// unmarshal from JSON
|
|
||||||
var result Snapshot
|
|
||||||
err = json.Unmarshal(b, &result)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.cpu.SetState(result.CPU)
|
|
||||||
return c.restoreMemoryFromHex(result.Memory)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) restoreMemoryFromHex(memory string) error {
|
|
||||||
for addr := 0; addr <= 65535; addr++ {
|
|
||||||
b, e := strconv.ParseUint(memory[addr*2:addr*2+2], 16, 8)
|
|
||||||
if e != nil {
|
|
||||||
log.Error(e)
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
c.memory.MemWrite(uint16(addr), byte(b))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) AutoSaveFloppy() {
|
|
||||||
for drv := byte(0); drv < fdc.TotalDrives; drv++ {
|
|
||||||
if c.config.FDC[drv].AutoSave {
|
|
||||||
e := c.fdc.SaveFloppy(drv)
|
|
||||||
if e != nil {
|
|
||||||
log.Error(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) AutoLoadFloppy() {
|
|
||||||
for drv := byte(0); drv < fdc.TotalDrives; drv++ {
|
|
||||||
if c.config.FDC[drv].AutoLoad {
|
|
||||||
e := c.fdc.LoadFloppyFile(drv, c.config.FDC[drv].FloppyFile)
|
|
||||||
if e != nil {
|
|
||||||
log.Error(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) ClearCodeCoverage() {
|
|
||||||
c.cpu.ClearCodeCoverage()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) SetCodeCoverage(enabled bool) {
|
|
||||||
c.cpu.SetCodeCoverage(enabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) CodeCoverage() map[uint16]bool {
|
|
||||||
return c.cpu.CodeCoverage()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) SetExtendedStack(enabled bool) {
|
|
||||||
c.cpu.SetExtendedStack(enabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) ExtendedStack() (map[uint16]z80go.PushValueType, error) {
|
|
||||||
return c.cpu.ExtendedStack()
|
|
||||||
}
|
|
||||||
|
|
||||||
var language gval.Language
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
language = gval.NewLanguage(gval.Base(), gval.Arithmetic(), gval.Bitmask(), gval.PropositionalLogic(),
|
|
||||||
gval.Function("PEEKW", breakpoint.CfPeekW),
|
|
||||||
gval.Function("PEEK", breakpoint.CfPeek),
|
|
||||||
gval.Function("BYTE", breakpoint.CfByte),
|
|
||||||
gval.Function("WORD", breakpoint.CfWord),
|
|
||||||
gval.Function("ABS", breakpoint.CfAbs),
|
|
||||||
gval.Function("IN", breakpoint.CfIn),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) Evaluate(expression string) (string, error) {
|
|
||||||
params := c.getContext()
|
|
||||||
bc := context.WithValue(context.Background(), "MEM", params["MEM"])
|
|
||||||
bc = context.WithValue(bc, "IO", params["IO"])
|
|
||||||
eval, err := language.NewEvaluable(expression)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("error: %s", err.Error())
|
|
||||||
}
|
|
||||||
value, err := eval.EvalUint(bc, params)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("error: %s", err.Error())
|
|
||||||
}
|
|
||||||
return strconv.FormatUint(uint64(value), 10), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) MemoryPages() []byte {
|
|
||||||
return c.memory.MemoryWindows()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) SetFullSpeed(full bool) {
|
|
||||||
c.fullSpeed.Store(full)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) FullSpeed() bool {
|
|
||||||
return c.fullSpeed.Load()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) SetPendingHardReset(pending bool) {
|
|
||||||
c.pendingHardReset.Store(pending)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) PendingHardReset() bool {
|
|
||||||
return c.pendingHardReset.Load()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) SetPendingCpuReset(pending bool) {
|
|
||||||
c.pendingCpuReset.Store(pending)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) PendingCpuReset() bool {
|
|
||||||
return c.pendingCpuReset.Load()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) LoadFloppyData(drive byte, reader fyne.URIReadCloser) error {
|
|
||||||
return c.fdc.LoadFloppyData(drive, reader)
|
|
||||||
}
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
package okean240
|
|
||||||
|
|
||||||
func calculateVAddress(x uint16, y uint16) (uint16, uint16) {
|
|
||||||
|
|
||||||
var offset uint16
|
|
||||||
if (c.vShift != 0) && (y > 255-uint16(c.vShift)) {
|
|
||||||
offset = 0x100
|
|
||||||
} else {
|
|
||||||
offset = 0
|
|
||||||
}
|
|
||||||
y += uint16(c.vShift) & 0x00ff
|
|
||||||
x += uint16(c.hShift-7) & 0x00ff
|
|
||||||
|
|
||||||
// Color 256x256 mode
|
|
||||||
addr = ((x & 0xf8) << 6) | y
|
|
||||||
|
|
||||||
a1 := (addr - offset) & 0x3fff
|
|
||||||
a2 := (addr + 0x100 - offset) & 0x3fff
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,157 +0,0 @@
|
|||||||
package okean240
|
|
||||||
|
|
||||||
/*
|
|
||||||
* КР580ВВ55 DD79 USER PORT
|
|
||||||
*/
|
|
||||||
|
|
||||||
// USR_DD79PA User port A
|
|
||||||
//const USR_DD79PA = 0x00
|
|
||||||
|
|
||||||
// USR_DD79PB User port B
|
|
||||||
//const USR_DD79PB = 0x01
|
|
||||||
|
|
||||||
// USR_DD79PC User port C
|
|
||||||
//const USR_DD79PC = 0x02
|
|
||||||
|
|
||||||
// USR_DD79CTR Config
|
|
||||||
// Configure: [1][ma1,ma0][0-aO|1-aI],[0-chO,1-chI],[mb],[0-bO|1-bI],[0-clO,1-clI]
|
|
||||||
// Set bit: [0][xxx][bbb][0|1]
|
|
||||||
//const USR_DD79CTR = 0x03
|
|
||||||
|
|
||||||
/*
|
|
||||||
* КР1818ВГ93 FDC Controller
|
|
||||||
*/
|
|
||||||
|
|
||||||
// FdcCmd FDC Command
|
|
||||||
const FdcCmd = 0x20
|
|
||||||
|
|
||||||
// FdcTrack FDC Track No
|
|
||||||
const FdcTrack = 0x21
|
|
||||||
|
|
||||||
// FdcSect FDC Sector
|
|
||||||
const FdcSect = 0x22
|
|
||||||
|
|
||||||
// FdcData FDC Data
|
|
||||||
const FdcData = 0x23
|
|
||||||
|
|
||||||
// FdcDrq Read DRQ state from FDC
|
|
||||||
const FdcDrq = 0x24
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Floppy Controller port
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Floppy Controller port
|
|
||||||
// WR: 5-SSEN, 4-#DDEN, 3-INIT, 2-DRSEL, 1-MOT1, 0-MOT0
|
|
||||||
// RD: 7-MOTST, 6-SSEL, 5,4-x , 3-DRSEL, 2-MOT1, 1-MOT0, 0-INT
|
|
||||||
const Floppy = 0x25
|
|
||||||
|
|
||||||
/*
|
|
||||||
* КР580ВВ55 DD78 Keyboard
|
|
||||||
*/
|
|
||||||
|
|
||||||
// KbdDd78pa Port A - Keyboard Data
|
|
||||||
const KbdDd78pa = 0x40
|
|
||||||
|
|
||||||
// KbdDd78pb Port B - JST3,SHFT,CTRL,ACK,TAPE5,TAPE4,GK,GC
|
|
||||||
const KbdDd78pb = 0x41
|
|
||||||
|
|
||||||
// KBD_DD78PC Port C - [PC7:5],[KBD_ACK],[PC3:0]
|
|
||||||
const KbdDd78pc = 0x42
|
|
||||||
|
|
||||||
// KBD_DD78CTR Control port
|
|
||||||
// Configure: [1][ma1,ma0][0-aO|1-aI],[0-chO,1-chI],[mb],[0-bO|1-bI],[0-clO,1-clI]
|
|
||||||
// Set bit: [0][xxx][bbb][0|1]
|
|
||||||
// const KBD_DD78CTR = 0x43
|
|
||||||
|
|
||||||
/*
|
|
||||||
* КР580ВИ53 DD70
|
|
||||||
*/
|
|
||||||
|
|
||||||
// TmrDd70c1 Timer load 1
|
|
||||||
const TmrDd70c1 = 0x60
|
|
||||||
|
|
||||||
// TmrDd70c2 Timer load 2
|
|
||||||
const TmrDd70c2 = 0x61
|
|
||||||
|
|
||||||
// TmrDd70c3 Timer load 3
|
|
||||||
const TmrDd70c3 = 0x62
|
|
||||||
|
|
||||||
// TmrDd70ctr
|
|
||||||
// Timer config: [sc1,sc0][rl1,rl0][m2,m1,m0][bcd]
|
|
||||||
//
|
|
||||||
// sc - timer, rl=01-LSB, 10-MSB, 11-LSB+MSB
|
|
||||||
// mode 000 - intRq on fin,
|
|
||||||
// 001 - one shot,
|
|
||||||
// x10 - rate gen,
|
|
||||||
// x11-sq wave
|
|
||||||
const TmrDd70ctr = 0x63
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Programmable Interrupt controller PIC КР580ВН59
|
|
||||||
*/
|
|
||||||
|
|
||||||
const RstKbdNo = 1
|
|
||||||
const RstTimerNo = 4
|
|
||||||
const KbAckBit = 0x80
|
|
||||||
|
|
||||||
// const Rst0Mask = 0x01 // System interrupt
|
|
||||||
const RstKbdMask = 0x02 // Keyboard interrupt
|
|
||||||
// const Rst2Mask = 0x04 // Serial interface interrupt
|
|
||||||
// const Rst3Mask = 0x08 // Printer ready
|
|
||||||
const RstTmrMask = 0x10
|
|
||||||
|
|
||||||
//const Rst5Mask = 0x20 // Power intRq
|
|
||||||
//const Rst6Mask = 0x40 // User device 1 interrupt
|
|
||||||
//const Rst7Mask = 0x80 // User device 1 interrupt
|
|
||||||
|
|
||||||
// PicDd75a Port A (a0=0)
|
|
||||||
const PicDd75a = 0x80
|
|
||||||
|
|
||||||
// PicDd75b Port B (a0=1)
|
|
||||||
const PicDd75b = 0x81
|
|
||||||
|
|
||||||
/*
|
|
||||||
* КР580ВВ51 DD72
|
|
||||||
*/
|
|
||||||
|
|
||||||
// UartDd72rd Serial data
|
|
||||||
const UartDd72rd = 0xA0
|
|
||||||
|
|
||||||
// UartDd72rr Serial enabled [RST,RQ_RX,RST_ERR,PAUSE,RX_EN,RX_RDY,TX_RDY]
|
|
||||||
const UartDd72rr = 0xA1
|
|
||||||
|
|
||||||
/*
|
|
||||||
* КР580ВВ55 DD17 System port
|
|
||||||
*/
|
|
||||||
|
|
||||||
// SysDd17pa Port A - VShift[8..1] Vertical shift
|
|
||||||
const SysDd17pa = 0xC0
|
|
||||||
|
|
||||||
// SysDd17pb Port B - Memory mapper [ROM14,13][REST][ENROM-][A18,17,16][32k]
|
|
||||||
const SysDd17pb = 0xC1
|
|
||||||
|
|
||||||
// SysDd17pc Port C - HShift[HS5..1,SB3..1] Horisontal shift
|
|
||||||
const SysDd17pc = 0xC2
|
|
||||||
|
|
||||||
// SysDd17ctr Configure: [1][ma1,ma0][0-aO|1-aI],[0-chO,1-chI],[mb],[0-bO|1-bI],[0-clO,1-clI]
|
|
||||||
// Set bit: [0][xxx][bbb][0|1]
|
|
||||||
const SysDd17ctr = 0xC3
|
|
||||||
|
|
||||||
/*
|
|
||||||
* КР580ВВ55 DD67
|
|
||||||
*/
|
|
||||||
|
|
||||||
// LPT_DD67PA Port A - Printer Data
|
|
||||||
//const LPT_DD67PA = 0xE0
|
|
||||||
|
|
||||||
// VidDd67pb Port B - Video control [VSU,C/M,FL3:1,COL3:1]
|
|
||||||
const VidDd67pb = 0xE1
|
|
||||||
|
|
||||||
// DD67PC Port C - [USER3:1, STB-LP, BELL, TAPE3:1]
|
|
||||||
//const DD67PC = 0xE2
|
|
||||||
|
|
||||||
// DD67CTR
|
|
||||||
// Configure: [1][ma1,ma0][0-aO|1-aI],[0-chO,1-chI],[mb],[0-bO|1-bI],[0-clO,1-clI]
|
|
||||||
// Set bit: [0][xxx][bbb][0|1]
|
|
||||||
// const DD67CTR = 0xE3
|
|
||||||
@ -1,470 +0,0 @@
|
|||||||
package fdc
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Floppy drive controller, based on
|
|
||||||
* MB8877, К1818ВГ93
|
|
||||||
*
|
|
||||||
* By Romych, 2025.03.05
|
|
||||||
*/
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"okemu/config"
|
|
||||||
"os"
|
|
||||||
"slices"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/howeyc/crc16"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Floppy parameters
|
|
||||||
const (
|
|
||||||
FloppyB = 0
|
|
||||||
FloppyC = 1
|
|
||||||
|
|
||||||
TotalDrives = 2
|
|
||||||
FloppySizeK = 720
|
|
||||||
SectorSize = 512
|
|
||||||
SideCount = 2
|
|
||||||
SectorPerTrack = 9
|
|
||||||
|
|
||||||
SizeInSectors = FloppySizeK * 1024 / SectorSize
|
|
||||||
TracksCount = SizeInSectors / SideCount / SectorPerTrack
|
|
||||||
SectorsPerSide = SizeInSectors / SideCount
|
|
||||||
|
|
||||||
TrackHeaderSize = 146
|
|
||||||
TrackSectorSize = 626
|
|
||||||
TrackFooterSize = 256 * 3
|
|
||||||
TrackBufferSize = TrackHeaderSize + TrackSectorSize*SectorPerTrack + TrackFooterSize
|
|
||||||
)
|
|
||||||
|
|
||||||
// FDC Commands
|
|
||||||
const (
|
|
||||||
CmdRestore = 0x0
|
|
||||||
CmdSeek = 0x1
|
|
||||||
CmdStep = 0x2
|
|
||||||
CmdStepIn = 0x5
|
|
||||||
CmdStepOut = 0x7
|
|
||||||
CmdReadSector = 0x8
|
|
||||||
CmdReadSectorMulti = 0x9
|
|
||||||
CmdWriteSector = 0xa
|
|
||||||
CmdReadAddress = 0xc
|
|
||||||
CmdWriteTrack = 0xf
|
|
||||||
CmdNoCommand = 0xff
|
|
||||||
)
|
|
||||||
|
|
||||||
var interleave = []byte{1, 8, 6, 4, 2, 9, 7, 5, 3}
|
|
||||||
|
|
||||||
const (
|
|
||||||
StatusTR0 = 0x04 // TR0 - Head at track 0
|
|
||||||
StatusRNF = 0x10 // RNF - Record not found
|
|
||||||
// StatusSeekError = 0x10 // Sector out of disk
|
|
||||||
StatusHeadLoaded = 0x20 // Head on disk
|
|
||||||
)
|
|
||||||
|
|
||||||
type SectorType []byte
|
|
||||||
|
|
||||||
type FloppyDriveController struct {
|
|
||||||
// Floppy controller port
|
|
||||||
sideNo byte
|
|
||||||
ddEn byte
|
|
||||||
init byte
|
|
||||||
drive byte
|
|
||||||
mot1 byte
|
|
||||||
mot0 byte
|
|
||||||
intRq byte
|
|
||||||
motSt byte
|
|
||||||
sectorNo byte
|
|
||||||
trackNo byte
|
|
||||||
drq byte
|
|
||||||
// FloppyStorage B and C
|
|
||||||
sectors [TotalDrives][SizeInSectors]SectorType
|
|
||||||
data byte
|
|
||||||
status byte
|
|
||||||
lastCmd byte
|
|
||||||
//curSector *SectorType
|
|
||||||
bytePtr uint16
|
|
||||||
trackBuffer []byte
|
|
||||||
// floppyFile []string
|
|
||||||
config *config.OkEmuConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
type FloppyDriveControllerInterface interface {
|
|
||||||
SetFloppy()
|
|
||||||
Floppy() byte
|
|
||||||
SetCmd(value byte)
|
|
||||||
Status() byte
|
|
||||||
SetTrack(value byte)
|
|
||||||
SetSector(value byte)
|
|
||||||
SetData(value byte)
|
|
||||||
Data() byte
|
|
||||||
Drq() byte
|
|
||||||
SaveFloppy(drive byte)
|
|
||||||
GetSectorNo() uint16
|
|
||||||
Track() byte
|
|
||||||
Sector() byte
|
|
||||||
}
|
|
||||||
|
|
||||||
//var slicer = []uint16{1, 8, 6, 4, 2, 9, 7, 5, 3}
|
|
||||||
|
|
||||||
func getSectorNo(side byte, track byte, sector byte) uint16 {
|
|
||||||
//return (uint16(track)*18 + uint16(side)*9 + slicer[sector-1] - 1)
|
|
||||||
return uint16(side)*SectorsPerSide + uint16(track)*SectorPerTrack + uint16(sector) - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FloppyDriveController) GetSectorNo() uint16 {
|
|
||||||
return getSectorNo(f.sideNo, f.trackNo, f.sectorNo)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FloppyDriveController) SetFloppy(val byte) {
|
|
||||||
// WR: 5-SSEL, 4-#DDEN, 3-INIT, 2-DRSEL, 1-MOT1, 0-MOT0
|
|
||||||
f.sideNo = val >> 5 & 0x01
|
|
||||||
f.ddEn = val >> 4 & 0x01
|
|
||||||
f.init = val >> 3 & 0x01
|
|
||||||
f.drive = (^val) >> 2 & 0x01
|
|
||||||
f.mot1 = val >> 1 & 0x01
|
|
||||||
f.mot0 = val & 0x01
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FloppyDriveController) GetFloppy() byte {
|
|
||||||
// RD: 7-MOTST, 6-SSEL, 5,4-x , 3-DRSEL, 2-MOT1, 1-MOT0, 0-INT
|
|
||||||
floppy := f.intRq | (f.mot0 << 1) | (f.mot1 << 2) | ((^f.drive & 1) << 3) | (f.sideNo << 6) | (f.motSt << 7)
|
|
||||||
return floppy
|
|
||||||
}
|
|
||||||
|
|
||||||
var crcTable *crc16.Table
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
crcTable = crc16.MakeTable(0xffff)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FloppyDriveController) SetCmd(value byte) {
|
|
||||||
f.lastCmd = value >> 4
|
|
||||||
switch f.lastCmd {
|
|
||||||
case CmdRestore:
|
|
||||||
log.Trace("CMD Restore (seek trackNo 0)")
|
|
||||||
f.trackNo = 0
|
|
||||||
f.status = StatusTR0 | StatusHeadLoaded // TR0 & Head loaded
|
|
||||||
case CmdSeek:
|
|
||||||
log.Tracef("CMD Seek %x", value&0xf)
|
|
||||||
f.status = StatusHeadLoaded
|
|
||||||
f.trackNo = f.data
|
|
||||||
case CmdStep:
|
|
||||||
log.Tracef("CMD Step %x", value&0xf)
|
|
||||||
f.status = StatusHeadLoaded
|
|
||||||
f.trackNo = f.data
|
|
||||||
case CmdStepIn:
|
|
||||||
log.Tracef("CMD StepIn (Next track) %x", value&0xf)
|
|
||||||
f.status = StatusHeadLoaded
|
|
||||||
if f.trackNo < TracksCount {
|
|
||||||
f.trackNo++
|
|
||||||
}
|
|
||||||
case CmdStepOut:
|
|
||||||
log.Tracef("CMD StepOut (Previous track) %x", value&0xf)
|
|
||||||
f.status = StatusHeadLoaded
|
|
||||||
if f.trackNo > 0 {
|
|
||||||
f.trackNo--
|
|
||||||
}
|
|
||||||
case CmdReadSector:
|
|
||||||
sectorNo := f.GetSectorNo()
|
|
||||||
log.Tracef("CMD Read single sectorNo: %d", sectorNo)
|
|
||||||
if sectorNo < SizeInSectors {
|
|
||||||
f.trackBuffer = slices.Clone(f.sectors[f.drive][sectorNo])
|
|
||||||
f.drq = 1
|
|
||||||
f.status = 0x00
|
|
||||||
} else {
|
|
||||||
f.drq = 0
|
|
||||||
f.status = StatusRNF
|
|
||||||
}
|
|
||||||
case CmdReadSectorMulti:
|
|
||||||
sectorNo := f.GetSectorNo()
|
|
||||||
f.trackBuffer = []byte{}
|
|
||||||
for c := 0; c < SectorPerTrack; c++ {
|
|
||||||
f.trackBuffer = slices.Concat(f.trackBuffer, f.sectors[f.drive][sectorNo])
|
|
||||||
sectorNo++
|
|
||||||
}
|
|
||||||
f.drq = 1
|
|
||||||
f.status = 0x0
|
|
||||||
case CmdWriteSector:
|
|
||||||
sectorNo := f.GetSectorNo()
|
|
||||||
log.Tracef("CMD Write Sector %d", sectorNo)
|
|
||||||
if sectorNo < SizeInSectors {
|
|
||||||
f.bytePtr = 0
|
|
||||||
f.drq = 1
|
|
||||||
f.status = 0x0
|
|
||||||
f.trackBuffer = []byte{}
|
|
||||||
} else {
|
|
||||||
f.drq = 0
|
|
||||||
f.status = StatusRNF
|
|
||||||
}
|
|
||||||
case CmdReadAddress:
|
|
||||||
log.Tracef("CMD ReadAddress %d", value)
|
|
||||||
f.trackBuffer = []byte{f.trackNo, f.sideNo, f.sectorNo, 2}
|
|
||||||
|
|
||||||
checksum := crc16.Checksum(f.trackBuffer, crcTable)
|
|
||||||
f.trackBuffer = append(f.trackBuffer, byte(checksum))
|
|
||||||
f.trackBuffer = append(f.trackBuffer, byte(checksum>>8))
|
|
||||||
|
|
||||||
f.drq = 1
|
|
||||||
f.status = 0x0
|
|
||||||
case CmdWriteTrack:
|
|
||||||
log.Tracef("CMD Write Track %x", f.trackNo)
|
|
||||||
f.drq = 1
|
|
||||||
f.status = 0x00
|
|
||||||
f.trackBuffer = []byte{}
|
|
||||||
default:
|
|
||||||
log.Errorf("Unknown CMD: %x VAL: %x", f.lastCmd, value&0xf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FloppyDriveController) Status() byte {
|
|
||||||
return f.status
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FloppyDriveController) SetTrackNo(value byte) {
|
|
||||||
//log.Tracef("FDC Track: %d", value)
|
|
||||||
if value > TracksCount {
|
|
||||||
f.status |= 0x10 /// RNF
|
|
||||||
log.Errorf("Track %d not found!", value)
|
|
||||||
} else {
|
|
||||||
f.trackNo = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FloppyDriveController) SetSectorNo(value byte) {
|
|
||||||
//log.Tracef("FDC Sector: %d", value)
|
|
||||||
if value > SectorPerTrack {
|
|
||||||
f.status |= 0x10
|
|
||||||
log.Errorf("Record not found %d!", value)
|
|
||||||
} else {
|
|
||||||
f.sectorNo = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FloppyDriveController) SetData(value byte) {
|
|
||||||
//log.Tracef("FCD Data: %d", value)
|
|
||||||
if f.lastCmd == CmdWriteTrack {
|
|
||||||
if len(f.trackBuffer) < TrackBufferSize {
|
|
||||||
f.trackBuffer = append(f.trackBuffer, value)
|
|
||||||
f.drq = 1
|
|
||||||
f.status = 0x00
|
|
||||||
} else {
|
|
||||||
//f.dump()
|
|
||||||
f.writeTrack()
|
|
||||||
f.drq = 0
|
|
||||||
f.status = 0x00
|
|
||||||
f.lastCmd = CmdNoCommand
|
|
||||||
}
|
|
||||||
} else if f.lastCmd == CmdWriteSector {
|
|
||||||
if len(f.trackBuffer) < SectorSize {
|
|
||||||
f.trackBuffer = append(f.trackBuffer, value)
|
|
||||||
if len(f.trackBuffer) == SectorSize {
|
|
||||||
f.drq = 0
|
|
||||||
} else {
|
|
||||||
f.drq = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(f.trackBuffer) == SectorSize {
|
|
||||||
f.drq = 0
|
|
||||||
f.sectors[f.drive][f.GetSectorNo()] = slices.Clone(f.trackBuffer)
|
|
||||||
f.lastCmd = CmdNoCommand
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.data = value
|
|
||||||
}
|
|
||||||
|
|
||||||
const SectorInfoSize = 626
|
|
||||||
const SectorInfoOffset = 0x0092
|
|
||||||
const TrackNoOffset = 0x0010
|
|
||||||
const SideNoOffset = 0x0011
|
|
||||||
const SectorNoOffset = 0x0012
|
|
||||||
const SectorLengthOffset = 0x0013
|
|
||||||
const SectorDataOffset = 0x003b
|
|
||||||
|
|
||||||
var SectorLengths = []int{128, 256, 512, 1024}
|
|
||||||
|
|
||||||
func (f *FloppyDriveController) writeTrack() {
|
|
||||||
// skip header
|
|
||||||
ptr := SectorInfoOffset
|
|
||||||
// repeat for every sector on track
|
|
||||||
for sec := 0; sec < SectorPerTrack; sec++ {
|
|
||||||
// get info from header
|
|
||||||
trackNo := f.trackBuffer[ptr+TrackNoOffset]
|
|
||||||
sideNo := f.trackBuffer[ptr+SideNoOffset]
|
|
||||||
sectorNo := f.trackBuffer[ptr+SectorNoOffset]
|
|
||||||
sectorLength := SectorLengths[f.trackBuffer[ptr+SectorLengthOffset]&0x03]
|
|
||||||
// get sector data
|
|
||||||
sectorData := f.trackBuffer[ptr+SectorDataOffset : ptr+SectorDataOffset+sectorLength]
|
|
||||||
absSector := getSectorNo(sideNo, trackNo, sectorNo)
|
|
||||||
log.Debugf("Write Drive: %d; side:%d; T: %d S: %d Len: %d Data: [%X..%X]; Abs sector: %d", f.drive, sideNo, trackNo, sectorNo, len(sectorData), sectorData[0], sectorData[len(sectorData)-1], absSector)
|
|
||||||
// write data to sector buffer
|
|
||||||
f.sectors[f.drive][absSector] = slices.Clone(sectorData)
|
|
||||||
// shift pointer to next sector info block
|
|
||||||
ptr += SectorInfoSize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FloppyDriveController) Data() byte {
|
|
||||||
switch f.lastCmd {
|
|
||||||
case CmdReadSector, CmdReadSectorMulti, CmdReadAddress:
|
|
||||||
if len(f.trackBuffer) > 0 {
|
|
||||||
f.drq = 1
|
|
||||||
f.data = f.trackBuffer[0]
|
|
||||||
f.trackBuffer = f.trackBuffer[1:]
|
|
||||||
}
|
|
||||||
if len(f.trackBuffer) == 0 {
|
|
||||||
f.drq = 0
|
|
||||||
f.status = 0
|
|
||||||
f.lastCmd = CmdNoCommand
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
f.data = 0xff
|
|
||||||
}
|
|
||||||
return f.data
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FloppyDriveController) Drq() byte {
|
|
||||||
return f.drq
|
|
||||||
}
|
|
||||||
|
|
||||||
//func (f *FloppyDriveController) LoadFloppy(drive byte) error {
|
|
||||||
// if drive < TotalDrives {
|
|
||||||
// return f.LoadConfiguredFloppy(drive)
|
|
||||||
// }
|
|
||||||
// return errors.New("DriveNo " + strconv.Itoa(int(drive)) + " out of range")
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (f *FloppyDriveController) SaveFloppy(drive byte) error {
|
|
||||||
if drive < TotalDrives {
|
|
||||||
return saveFloppy(&f.sectors[drive], f.config.FDC[drive].FloppyFile)
|
|
||||||
}
|
|
||||||
return errors.New("DriveNo " + strconv.Itoa(int(drive)) + " out of range")
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFDC(conf *config.OkEmuConfig) *FloppyDriveController {
|
|
||||||
sec := [2][SizeInSectors]SectorType{}
|
|
||||||
// for each drive
|
|
||||||
for d := 0; d < TotalDrives; d++ {
|
|
||||||
// for each sector
|
|
||||||
for i := 0; i < SizeInSectors; i++ {
|
|
||||||
sec[d][i] = bytes.Repeat([]byte{0xe5}, SectorSize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &FloppyDriveController{
|
|
||||||
sideNo: 0,
|
|
||||||
ddEn: 0,
|
|
||||||
init: 0,
|
|
||||||
drive: 0,
|
|
||||||
mot1: 0,
|
|
||||||
mot0: 0,
|
|
||||||
intRq: 0,
|
|
||||||
motSt: 0,
|
|
||||||
drq: 0,
|
|
||||||
lastCmd: 0xff,
|
|
||||||
sectors: sec,
|
|
||||||
bytePtr: 0xffff,
|
|
||||||
//floppyConf: conf.FDC,
|
|
||||||
config: conf,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FloppyDriveController) dump() {
|
|
||||||
log.Trace("Dump Buffer content.")
|
|
||||||
file, err := os.Create("track-" + strconv.Itoa(int(f.trackNo)) + ".dat")
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer func(file *os.File) {
|
|
||||||
err := file.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
}(file)
|
|
||||||
|
|
||||||
err = binary.Write(file, binary.LittleEndian, f.trackBuffer)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Save track content failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FloppyDriveController) Track() byte {
|
|
||||||
return f.trackNo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FloppyDriveController) Sector() byte {
|
|
||||||
return f.sectorNo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FloppyDriveController) LoadFloppyData(drive byte, reader io.ReadCloser) error {
|
|
||||||
if drive >= TotalDrives {
|
|
||||||
return errors.New("Drive " + strconv.Itoa(int(drive+65)) + " out of range")
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
for sector := 0; sector < SizeInSectors; sector++ {
|
|
||||||
var n int
|
|
||||||
n, err = reader.Read(f.sectors[drive][sector])
|
|
||||||
if n != SectorSize {
|
|
||||||
log.Error("Load floppy error, sector size: %d <> %d", n, SectorSize)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Load floppy content failed:", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadFloppyFile load floppy image to sector buffer from file
|
|
||||||
func (f *FloppyDriveController) LoadFloppyFile(drive byte, fileName string) error {
|
|
||||||
//fileName := f.config.FDC[drive].FloppyFile
|
|
||||||
log.Debugf("Load Floppy content from file %s.", fileName)
|
|
||||||
file, err := os.Open(fileName)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func(file *os.File) {
|
|
||||||
err := file.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
}(file)
|
|
||||||
return f.LoadFloppyData(drive, file)
|
|
||||||
}
|
|
||||||
|
|
||||||
// saveFloppy Save specified sectors to file with name fileName
|
|
||||||
func saveFloppy(sectors *[SizeInSectors]SectorType, fileName string) error {
|
|
||||||
log.Debugf("Save Floppy to file %s.", fileName)
|
|
||||||
file, err := os.Create(fileName)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func(file *os.File) {
|
|
||||||
err := file.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
}(file)
|
|
||||||
|
|
||||||
for sector := 0; sector < SizeInSectors; sector++ {
|
|
||||||
var n int
|
|
||||||
n, err = file.Write(sectors[sector])
|
|
||||||
if n != SectorSize {
|
|
||||||
log.Errorf("Save floppy error, sector %d size: %d <> %d", sector, n, SectorSize)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Save floppy content failed:", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@ -1,133 +0,0 @@
|
|||||||
package okean240
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Ocean-240.2
|
|
||||||
* Computer with FDC variant.
|
|
||||||
* IO Ports definitions
|
|
||||||
*
|
|
||||||
* By Romych 2026-03-01
|
|
||||||
*/
|
|
||||||
|
|
||||||
import log "github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
func (c *ComputerType) IORead(port uint16) uint8 {
|
|
||||||
switch port & 0x00ff {
|
|
||||||
case PicDd75a:
|
|
||||||
// PIO xx59, get IRR register
|
|
||||||
irr := c.pic.IRR()
|
|
||||||
// if irq from keyboard and no ACK applied, re-fire
|
|
||||||
if irr&RstKbdMask != 0 && !c.kbAck.Load() {
|
|
||||||
log.Tracef("KBD IRQ REFIRE PC=%04X", c.cpu.PC)
|
|
||||||
c.pic.SetIRQ(RstKbdNo)
|
|
||||||
}
|
|
||||||
return irr
|
|
||||||
case PicDd75b:
|
|
||||||
return c.pic.CSW()
|
|
||||||
case UartDd72rr:
|
|
||||||
// USART VV51 CMD
|
|
||||||
return c.usart.Status()
|
|
||||||
case UartDd72rd:
|
|
||||||
// USART VV51 Data
|
|
||||||
return c.usart.Receive()
|
|
||||||
case KbdDd78pa:
|
|
||||||
// Keyboard data
|
|
||||||
log.Tracef("KBD RD: %d, PC=%04X", c.ioPorts[KbdDd78pa], c.cpu.PC)
|
|
||||||
return c.ioPorts[KbdDd78pa]
|
|
||||||
case KbdDd78pb:
|
|
||||||
return c.ioPorts[KbdDd78pb]
|
|
||||||
case FdcCmd:
|
|
||||||
return c.fdc.Status()
|
|
||||||
case FdcDrq:
|
|
||||||
return c.fdc.Drq()
|
|
||||||
case Floppy:
|
|
||||||
return c.fdc.GetFloppy()
|
|
||||||
case FdcData:
|
|
||||||
return c.fdc.Data()
|
|
||||||
case FdcTrack:
|
|
||||||
return c.fdc.Track()
|
|
||||||
case FdcSect:
|
|
||||||
return c.fdc.Sector()
|
|
||||||
|
|
||||||
default:
|
|
||||||
log.Debugf("IORead from port: %x", port)
|
|
||||||
}
|
|
||||||
return c.ioPorts[byte(port&0x00ff)]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) IOWrite(port uint16, val byte) {
|
|
||||||
bp := byte(port & 0x00ff)
|
|
||||||
c.ioPorts[bp] = val
|
|
||||||
//log.Debugf("OUT (%x), %x", bp, val)
|
|
||||||
switch bp {
|
|
||||||
case SysDd17pb:
|
|
||||||
if c.dd17EnableOut {
|
|
||||||
c.memory.Configure(val)
|
|
||||||
}
|
|
||||||
case SysDd17ctr:
|
|
||||||
c.dd17EnableOut = val == 0x80
|
|
||||||
case VidDd67pb:
|
|
||||||
if val&VidVsuBit == 0 {
|
|
||||||
// video page 0
|
|
||||||
c.vRAM = c.memory.allMemory[VRAMBlock0]
|
|
||||||
} else {
|
|
||||||
// video page 1
|
|
||||||
c.vRAM = c.memory.allMemory[VRAMBlock1]
|
|
||||||
}
|
|
||||||
if val&VidColorBit != 0 {
|
|
||||||
c.colorMode = true
|
|
||||||
c.screenWidth = 256
|
|
||||||
} else {
|
|
||||||
c.colorMode = false
|
|
||||||
c.screenWidth = 512
|
|
||||||
}
|
|
||||||
c.palette = val & 0x07
|
|
||||||
c.bgColor = (val >> 3) & 0x07
|
|
||||||
case SysDd17pa:
|
|
||||||
c.vShift = val
|
|
||||||
case SysDd17pc:
|
|
||||||
c.hShift = val
|
|
||||||
case TmrDd70ctr:
|
|
||||||
// Timer VI63 config register
|
|
||||||
c.pit.Configure(val)
|
|
||||||
case TmrDd70c1:
|
|
||||||
// Timer VI63 counter0 register
|
|
||||||
c.pit.Load(0, val)
|
|
||||||
case TmrDd70c2:
|
|
||||||
// Timer VI63 counter1 register
|
|
||||||
c.pit.Load(1, val)
|
|
||||||
case TmrDd70c3:
|
|
||||||
// Timer VI63 counter2 register
|
|
||||||
c.pit.Load(2, val)
|
|
||||||
|
|
||||||
case UartDd72rr:
|
|
||||||
// USART VV51 CMD
|
|
||||||
c.usart.Command(val)
|
|
||||||
case UartDd72rd:
|
|
||||||
// USART VV51 Data
|
|
||||||
c.usart.Send(val)
|
|
||||||
case PicDd75b:
|
|
||||||
c.pic.SetCSW(val)
|
|
||||||
case FdcCmd:
|
|
||||||
c.fdc.SetCmd(val)
|
|
||||||
case FdcData:
|
|
||||||
c.fdc.SetData(val)
|
|
||||||
case FdcTrack:
|
|
||||||
c.fdc.SetTrackNo(val)
|
|
||||||
case FdcSect:
|
|
||||||
c.fdc.SetSectorNo(val)
|
|
||||||
case Floppy:
|
|
||||||
c.fdc.SetFloppy(val)
|
|
||||||
|
|
||||||
case KbdDd78pc:
|
|
||||||
if val&KbAckBit != 0 {
|
|
||||||
c.kbAck.Store(true)
|
|
||||||
log.Trace("KBD ACK")
|
|
||||||
} else {
|
|
||||||
if c.kbAck.Load() {
|
|
||||||
c.pic.ResetIRQ(RstKbdNo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
//log.Debugf("OUT to Unknown port (%x), %x", bp, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
package okean240
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fyne.io/fyne/v2"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *ComputerType) PutKey(key *fyne.KeyEvent) {
|
|
||||||
|
|
||||||
code := RemapCmdKey[key.Name]
|
|
||||||
if code > 0 {
|
|
||||||
log.Tracef("PutKey keyName: %s", key.Name)
|
|
||||||
c.ioPorts[KbdDd78pa] = code
|
|
||||||
c.kbAck.Store(false)
|
|
||||||
c.pic.SetIRQ(RstKbdNo)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ComputerType) PutRune(key rune) {
|
|
||||||
log.Tracef("Put Rune: %c Lo: %x, Hi: %x", key, key&0xff, key>>8)
|
|
||||||
c.ioPorts[KbdDd78pa] = byte(key & 0xff)
|
|
||||||
c.kbAck.Store(false)
|
|
||||||
c.pic.SetIRQ(RstKbdNo)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
CTRL_C EQU 0x03 ; Warm boot
|
|
||||||
CTRL_H EQU 0x08 ; Backspace
|
|
||||||
CTRL_E EQU 0x05 ; Move to beginning of new line (Physical EOL)
|
|
||||||
CTRL_J EQU 0x0A ; LF - Line Feed
|
|
||||||
CTRL_M EQU 0x0D ; CR - Carriage Return
|
|
||||||
CTRL_P EQU 0x10 ; turn on/off printer
|
|
||||||
CTRL_R EQU 0x12 ; Repeat current cmd line
|
|
||||||
CTRL_S EQU 0x13 ; Temporary stop display data to console (aka DC3)
|
|
||||||
CTRL_U EQU 0x15 ; Cancel (erase) current cmd line
|
|
||||||
CTRL_X EQU 0x18 ; Cancel (erase) current cmd line
|
|
||||||
*/
|
|
||||||
|
|
||||||
func (c *ComputerType) PutCtrlKey(key byte) {
|
|
||||||
c.ioPorts[KbdDd78pa] = key
|
|
||||||
c.pic.SetIRQ(RstKbdNo)
|
|
||||||
c.kbAck.Store(false)
|
|
||||||
c.ioPorts[KbdDd78pb] &= 0x1f | 0x20
|
|
||||||
}
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
package okean240
|
|
||||||
|
|
||||||
import "image/color"
|
|
||||||
|
|
||||||
var CRed = color.RGBA{R: 0xff, G: 0, B: 0, A: 0xff}
|
|
||||||
var CLGreen = color.RGBA{R: 0x00, G: 0xff, B: 0, A: 0xff}
|
|
||||||
var CGreen = color.RGBA{R: 0x12, G: 0x76, B: 0x22, A: 0xff}
|
|
||||||
var CBlue = color.RGBA{R: 0x2A, G: 0x60, B: 0x99, A: 0xff}
|
|
||||||
var CLBlue = color.RGBA{R: 0x72, G: 0x9F, B: 0xCF, A: 0xff}
|
|
||||||
var CCrimson = color.RGBA{R: 0xbf, G: 0x00, B: 0xbf, A: 0xff}
|
|
||||||
var CWhite = color.RGBA{R: 0xee, G: 0xee, B: 0xee, A: 0xff}
|
|
||||||
var CYellow = color.RGBA{R: 0xff, G: 0xff, B: 0x00, A: 0xff}
|
|
||||||
var CBlack = color.RGBA{R: 0, G: 0, B: 0, A: 0xff}
|
|
||||||
|
|
||||||
//// R5 - Style Palette
|
|
||||||
//var ColorPalette = [8][4]color.RGBA{
|
|
||||||
// {CBlack, CRed, CGreen, CBlue}, // 000
|
|
||||||
// {CWhite, CRed, CGreen, CBlue}, // 001
|
|
||||||
// {CRed, CGreen, CLBlue, CYellow}, // 010
|
|
||||||
// {CBlack, CRed, CCrimson, CWhite}, // 011
|
|
||||||
// {CBlack, CRed, CWhite, CBlue}, // 100
|
|
||||||
// {CBlack, CRed, CWhite, CYellow}, // 101
|
|
||||||
// {CGreen, CWhite, CYellow, CBlue}, // 110
|
|
||||||
// {CBlack, CBlack, CBlack, CBlack}, // 111
|
|
||||||
//}
|
|
||||||
|
|
||||||
// ColorPalette R8 style palette
|
|
||||||
var ColorPalette = [8][4]color.RGBA{
|
|
||||||
{CBlack, CGreen, CBlue, CRed}, // 000
|
|
||||||
{CWhite, CGreen, CBlue, CRed}, // 001
|
|
||||||
{CGreen, CBlue, CCrimson, CLBlue}, // 010
|
|
||||||
{CBlack, CGreen, CYellow, CWhite}, // 011
|
|
||||||
{CBlack, CGreen, CLBlue, CRed}, // 100
|
|
||||||
{CBlack, CRed, CBlue, CLBlue}, // 101
|
|
||||||
{CBlue, CWhite, CLBlue, CRed}, // 110
|
|
||||||
{CBlack, CBlack, CBlack, CBlack}, // 111
|
|
||||||
}
|
|
||||||
|
|
||||||
// BgColorPalette Background color palette
|
|
||||||
var BgColorPalette = [8]color.RGBA{
|
|
||||||
CBlack, CBlue, CGreen, CLBlue, CRed, CCrimson /*CYellow*/, CBlack, CWhite,
|
|
||||||
}
|
|
||||||
|
|
||||||
var MonoPalette = [8]color.RGBA{
|
|
||||||
CWhite, CRed, CGreen, CBlue, CLBlue, CYellow, CLGreen, CBlack,
|
|
||||||
}
|
|
||||||
|
|
||||||
//// MonoPalette R8 Style monochrome mode palette
|
|
||||||
//var MonoPalette = [8]color.RGBA{
|
|
||||||
// CRed, CBlue, CCrimson, CGreen, CYellow, CLBlue, CWhite, CBlack,
|
|
||||||
//}
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
package pic
|
|
||||||
|
|
||||||
import log "github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
/*
|
|
||||||
Programmable Interrupt Controller
|
|
||||||
i8058, MSM82C59, КР580ВН59
|
|
||||||
|
|
||||||
By Romych, 2025.03.05
|
|
||||||
*/
|
|
||||||
|
|
||||||
type I8259 struct {
|
|
||||||
irr byte
|
|
||||||
csw byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type I8259Interface interface {
|
|
||||||
SetIRQ(irq byte)
|
|
||||||
IRR() byte
|
|
||||||
CSW() byte
|
|
||||||
SetCSW(val byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IRR Return value of interrupt request register
|
|
||||||
func (c *I8259) IRR() byte {
|
|
||||||
irr := c.irr
|
|
||||||
// Reset the highest IR bit
|
|
||||||
if irr&0x01 != 0 {
|
|
||||||
c.irr &= 0xFE
|
|
||||||
} else if irr&0x02 != 0 {
|
|
||||||
c.irr &= 0xFD
|
|
||||||
} else if irr&0x04 != 0 {
|
|
||||||
c.irr &= 0xFB
|
|
||||||
} else if irr&0x08 != 0 {
|
|
||||||
c.irr &= 0xF7
|
|
||||||
} else if irr&0x10 != 0 {
|
|
||||||
c.irr &= 0xEF
|
|
||||||
} else if irr&0x20 != 0 {
|
|
||||||
c.irr &= 0xDF
|
|
||||||
} else if irr&0x40 != 0 {
|
|
||||||
c.irr &= 0xBF
|
|
||||||
} else if irr&0x80 != 0 {
|
|
||||||
c.irr &= 0x7F
|
|
||||||
}
|
|
||||||
return irr
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResetIRQ Reset interrupt request flag for specified irq
|
|
||||||
func (c *I8259) ResetIRQ(irq byte) {
|
|
||||||
c.irr &= ^(byte(1) << (irq & 0x07))
|
|
||||||
log.Tracef("RESET IRQ %d -> IRR: %08b", irq, c.irr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetIRQ Set interrupt request flag for specified irq
|
|
||||||
func (c *I8259) SetIRQ(irq byte) {
|
|
||||||
c.irr |= 1 << (irq & 0x07)
|
|
||||||
if irq == 1 {
|
|
||||||
log.Tracef("SET IRQ %d -> IRR: %08b", irq, c.irr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CSW Return value of CSW register
|
|
||||||
func (c *I8259) CSW() byte {
|
|
||||||
return c.csw
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCSW Set value of CSW register
|
|
||||||
func (c *I8259) SetCSW(val byte) {
|
|
||||||
c.csw = val
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewI8259 Create and initialize new i8259 controller
|
|
||||||
func NewI8259() *I8259 {
|
|
||||||
return &I8259{
|
|
||||||
irr: 0,
|
|
||||||
csw: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,158 +0,0 @@
|
|||||||
package pit
|
|
||||||
|
|
||||||
/*
|
|
||||||
Programmable Interval Timer
|
|
||||||
i8053, MSM82C53, КР580ВИ53
|
|
||||||
|
|
||||||
By Romych, 2025.03.04
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Timer work modes
|
|
||||||
const (
|
|
||||||
TimerModeIntOnFin = iota
|
|
||||||
TimerModeOneShot
|
|
||||||
TimerModeRateGen
|
|
||||||
TimerModeSqWave
|
|
||||||
)
|
|
||||||
|
|
||||||
// Timer load counter modes
|
|
||||||
const (
|
|
||||||
TimerRLMsbLsb = iota
|
|
||||||
TimerRLLsb
|
|
||||||
TimerRLMsb
|
|
||||||
TimerRLLsbMsb
|
|
||||||
)
|
|
||||||
|
|
||||||
type Timer8253Ch struct {
|
|
||||||
rl byte // load mode
|
|
||||||
mode byte // counter mode
|
|
||||||
bcd bool // decimal/BCD load mode
|
|
||||||
load uint16 // value to count from
|
|
||||||
counter uint16 // timer counter
|
|
||||||
fb bool // first byte load flag
|
|
||||||
started bool // true if timer started
|
|
||||||
fired bool
|
|
||||||
}
|
|
||||||
type I8253 struct {
|
|
||||||
//chNo byte
|
|
||||||
channel [3]Timer8253Ch
|
|
||||||
}
|
|
||||||
|
|
||||||
type I8253Interface interface {
|
|
||||||
Configure(value byte)
|
|
||||||
Load(chNo int, value byte)
|
|
||||||
Counter(chNo int) uint16
|
|
||||||
Fired(chNo int) bool
|
|
||||||
Start(chNo int) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func New() *I8253 {
|
|
||||||
return &I8253{
|
|
||||||
//chNo: 0,
|
|
||||||
channel: [3]Timer8253Ch{
|
|
||||||
{0, 0, false, 0, 0, true, false, false},
|
|
||||||
{0, 0, false, 0, 0, true, false, false},
|
|
||||||
{0, 0, false, 0, 0, true, false, false},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *I8253) Tick(chNo int) {
|
|
||||||
tmr := &t.channel[chNo]
|
|
||||||
if tmr.started {
|
|
||||||
tmr.counter--
|
|
||||||
if tmr.counter == 0 {
|
|
||||||
switch tmr.mode {
|
|
||||||
case TimerModeIntOnFin:
|
|
||||||
{
|
|
||||||
tmr.started = false
|
|
||||||
tmr.fired = true
|
|
||||||
}
|
|
||||||
case TimerModeOneShot:
|
|
||||||
tmr.started = false
|
|
||||||
case TimerModeRateGen:
|
|
||||||
tmr.started = false
|
|
||||||
case TimerModeSqWave:
|
|
||||||
{
|
|
||||||
tmr.started = true
|
|
||||||
tmr.counter = tmr.load
|
|
||||||
tmr.fired = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *I8253) Counter(chNo int) uint16 {
|
|
||||||
return t.channel[chNo].counter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *I8253) Fired(chNo int) bool {
|
|
||||||
f := t.channel[chNo].fired
|
|
||||||
if f {
|
|
||||||
t.channel[chNo].fired = false
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *I8253) Start(chNo int) bool {
|
|
||||||
return t.channel[chNo].started
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Timer config byte: [sc1:0][rl1:0][m2:0][bcd]
|
|
||||||
sc1:0 - timer No
|
|
||||||
rl=01-LSB, 10-MSB, 11-LSB+MSB
|
|
||||||
mode 000 - intRq on fin,
|
|
||||||
001 - one shot,
|
|
||||||
x10 - rate gen,
|
|
||||||
x11 - sq wave
|
|
||||||
*/
|
|
||||||
|
|
||||||
func (t *I8253) Configure(value byte) {
|
|
||||||
chNo := (value & 0xC0) >> 6
|
|
||||||
rl := value & 0x30 >> 4
|
|
||||||
t.channel[chNo].started = false
|
|
||||||
t.channel[chNo].rl = rl
|
|
||||||
t.channel[chNo].mode = (value & 0x0E) >> 1
|
|
||||||
t.channel[chNo].fb = true
|
|
||||||
t.channel[chNo].bcd = value&0x01 == 1
|
|
||||||
t.channel[chNo].load = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *I8253) Load(chNo byte, value byte) {
|
|
||||||
timer := &t.channel[chNo]
|
|
||||||
switch timer.rl {
|
|
||||||
case TimerRLMsbLsb:
|
|
||||||
// MSB+LSB
|
|
||||||
if timer.fb {
|
|
||||||
// MSB
|
|
||||||
timer.load = uint16(value) << 8
|
|
||||||
timer.fb = false
|
|
||||||
} else {
|
|
||||||
// LSB
|
|
||||||
timer.load |= uint16(value)
|
|
||||||
timer.started = true
|
|
||||||
}
|
|
||||||
case TimerRLLsb:
|
|
||||||
// LSB Only
|
|
||||||
timer.load = (timer.load & 0xff00) | uint16(value)
|
|
||||||
timer.started = true
|
|
||||||
case TimerRLMsb:
|
|
||||||
// MSB Only
|
|
||||||
timer.load = (timer.load & 0x00ff) | (uint16(value) << 8)
|
|
||||||
timer.started = true
|
|
||||||
case TimerRLLsbMsb:
|
|
||||||
// LSB+MSB
|
|
||||||
if timer.fb {
|
|
||||||
// LSB
|
|
||||||
timer.load = uint16(value)
|
|
||||||
timer.fb = false
|
|
||||||
} else {
|
|
||||||
// MSB
|
|
||||||
timer.load = (uint16(value) << 8) | (timer.load & 0x00ff)
|
|
||||||
timer.started = true
|
|
||||||
timer.counter = timer.load
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,196 +0,0 @@
|
|||||||
package okean240
|
|
||||||
|
|
||||||
import "fyne.io/fyne/v2"
|
|
||||||
|
|
||||||
var RemapCmdKey = map[fyne.KeyName]byte{
|
|
||||||
fyne.KeyEscape: 0x1B,
|
|
||||||
fyne.KeyReturn: 0x0D,
|
|
||||||
fyne.KeyTab: 0x09,
|
|
||||||
fyne.KeyBackspace: 0x08,
|
|
||||||
fyne.KeyInsert: 0x00,
|
|
||||||
fyne.KeyDelete: 0x08,
|
|
||||||
fyne.KeyRight: 0x84, //0x18,
|
|
||||||
fyne.KeyLeft: 0x93, //0x08,
|
|
||||||
fyne.KeyDown: 0x0A,
|
|
||||||
fyne.KeyUp: 0x85, //0x19,
|
|
||||||
fyne.KeyPageUp: 0x00,
|
|
||||||
fyne.KeyPageDown: 0x00,
|
|
||||||
fyne.KeyHome: 0x0C,
|
|
||||||
fyne.KeyEnd: 0x1A,
|
|
||||||
fyne.KeyF1: 0x00,
|
|
||||||
fyne.KeyF2: 0x00,
|
|
||||||
fyne.KeyF3: 0x00,
|
|
||||||
fyne.KeyF4: 0x00,
|
|
||||||
fyne.KeyF5: 0x00,
|
|
||||||
fyne.KeyF6: 0x00,
|
|
||||||
fyne.KeyF7: 0x00,
|
|
||||||
fyne.KeyF8: 0x00,
|
|
||||||
fyne.KeyF9: 0x00,
|
|
||||||
fyne.KeyF10: 0x00,
|
|
||||||
fyne.KeyF11: 0x00,
|
|
||||||
fyne.KeyF12: 0x00,
|
|
||||||
fyne.KeyEnter: 0x0A,
|
|
||||||
fyne.KeyUnknown: 0x00,
|
|
||||||
}
|
|
||||||
|
|
||||||
//var RemapKey = map[fyne.KeyName]byte{
|
|
||||||
// fyne.KeyEscape: 0x1B,
|
|
||||||
// fyne.KeyReturn: 0x0A,
|
|
||||||
// fyne.KeyTab: 0x09,
|
|
||||||
// fyne.KeyBackspace: 0x08,
|
|
||||||
// fyne.KeyInsert: 0x00,
|
|
||||||
// fyne.KeyDelete: 0x08,
|
|
||||||
// fyne.KeyRight: 0x18,
|
|
||||||
// fyne.KeyLeft: 0x08,
|
|
||||||
// fyne.KeyDown: 0x0A,
|
|
||||||
// fyne.KeyUp: 0x19,
|
|
||||||
// fyne.KeyPageUp: 0x00,
|
|
||||||
// fyne.KeyPageDown: 0x00,
|
|
||||||
// fyne.KeyHome: 0x0C,
|
|
||||||
// fyne.KeyEnd: 0x1A,
|
|
||||||
// fyne.KeyF1: 0x00,
|
|
||||||
// fyne.KeyF2: 0x00,
|
|
||||||
// fyne.KeyF3: 0x00,
|
|
||||||
// fyne.KeyF4: 0x00,
|
|
||||||
// fyne.KeyF5: 0x00,
|
|
||||||
// fyne.KeyF6: 0x00,
|
|
||||||
// fyne.KeyF7: 0x00,
|
|
||||||
// fyne.KeyF8: 0x00,
|
|
||||||
// fyne.KeyF9: 0x00,
|
|
||||||
// fyne.KeyF10: 0x00,
|
|
||||||
// fyne.KeyF11: 0x00,
|
|
||||||
// fyne.KeyF12: 0x00,
|
|
||||||
// fyne.KeyEnter: 0x0D,
|
|
||||||
// fyne.Key0: 0x30,
|
|
||||||
// fyne.Key1: 0x31,
|
|
||||||
// fyne.Key2: 0x32,
|
|
||||||
// fyne.Key3: 0x33,
|
|
||||||
// fyne.Key4: 0x34,
|
|
||||||
// fyne.Key5: 0x35,
|
|
||||||
// fyne.Key6: 0x36,
|
|
||||||
// fyne.Key7: 0x37,
|
|
||||||
// fyne.Key8: 0x38,
|
|
||||||
// fyne.Key9: 0x39,
|
|
||||||
// fyne.KeyA: 0x61,
|
|
||||||
// fyne.KeyB: 0x62,
|
|
||||||
// fyne.KeyC: 0x63,
|
|
||||||
// fyne.KeyD: 0x64,
|
|
||||||
// fyne.KeyE: 0x65,
|
|
||||||
// fyne.KeyF: 0x66,
|
|
||||||
// fyne.KeyG: 0x67,
|
|
||||||
// fyne.KeyH: 0x68,
|
|
||||||
// fyne.KeyI: 0x69,
|
|
||||||
// fyne.KeyJ: 0x6a,
|
|
||||||
// fyne.KeyK: 0x6b,
|
|
||||||
// fyne.KeyL: 0x6c,
|
|
||||||
// fyne.KeyM: 0x6d,
|
|
||||||
// fyne.KeyN: 0x6e,
|
|
||||||
// fyne.KeyO: 0x6f,
|
|
||||||
// fyne.KeyP: 0x70,
|
|
||||||
// fyne.KeyQ: 0x71,
|
|
||||||
// fyne.KeyR: 0x72,
|
|
||||||
// fyne.KeyS: 0x73,
|
|
||||||
// fyne.KeyT: 0x74,
|
|
||||||
// fyne.KeyU: 0x75,
|
|
||||||
// fyne.KeyV: 0x76,
|
|
||||||
// fyne.KeyW: 0x77,
|
|
||||||
// fyne.KeyX: 0x78,
|
|
||||||
// fyne.KeyY: 0x79,
|
|
||||||
// fyne.KeyZ: 0x7A,
|
|
||||||
// fyne.KeySpace: 0x20,
|
|
||||||
// fyne.KeyApostrophe: 0x27,
|
|
||||||
// fyne.KeyComma: 0x2c,
|
|
||||||
// fyne.KeyMinus: 0x2d,
|
|
||||||
// fyne.KeyPeriod: 0x2E,
|
|
||||||
// fyne.KeySlash: 0x2F,
|
|
||||||
// fyne.KeyBackslash: 0x5C,
|
|
||||||
// fyne.KeyLeftBracket: 0x5B,
|
|
||||||
// fyne.KeyRightBracket: 0x5D,
|
|
||||||
// fyne.KeySemicolon: 0x3B,
|
|
||||||
// fyne.KeyEqual: 0x3D,
|
|
||||||
// fyne.KeyAsterisk: 0x2A,
|
|
||||||
// fyne.KeyPlus: 0x2B,
|
|
||||||
// fyne.KeyBackTick: 0x60,
|
|
||||||
// fyne.KeyUnknown: 0x00,
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//var RemapKeyShift = map[fyne.KeyName]byte{
|
|
||||||
// fyne.KeyEscape: 0x1B,
|
|
||||||
// fyne.KeyReturn: 0x0A,
|
|
||||||
// fyne.KeyTab: 0x09,
|
|
||||||
// fyne.KeyBackspace: 0x08,
|
|
||||||
// fyne.KeyInsert: 0x00,
|
|
||||||
// fyne.KeyDelete: 0x08,
|
|
||||||
// fyne.KeyRight: 0x18,
|
|
||||||
// fyne.KeyLeft: 0x08,
|
|
||||||
// fyne.KeyDown: 0x0A,
|
|
||||||
// fyne.KeyUp: 0x19,
|
|
||||||
// fyne.KeyPageUp: 0x00,
|
|
||||||
// fyne.KeyPageDown: 0x00,
|
|
||||||
// fyne.KeyHome: 0x0C,
|
|
||||||
// fyne.KeyEnd: 0x1A,
|
|
||||||
// fyne.KeyF1: 0x00,
|
|
||||||
// fyne.KeyF2: 0x00,
|
|
||||||
// fyne.KeyF3: 0x00,
|
|
||||||
// fyne.KeyF4: 0x00,
|
|
||||||
// fyne.KeyF5: 0x00,
|
|
||||||
// fyne.KeyF6: 0x00,
|
|
||||||
// fyne.KeyF7: 0x00,
|
|
||||||
// fyne.KeyF8: 0x00,
|
|
||||||
// fyne.KeyF9: 0x00,
|
|
||||||
// fyne.KeyF10: 0x00,
|
|
||||||
// fyne.KeyF11: 0x00,
|
|
||||||
// fyne.KeyF12: 0x00,
|
|
||||||
// fyne.KeyEnter: 0x0D,
|
|
||||||
//
|
|
||||||
// fyne.Key0: 0x29,
|
|
||||||
// fyne.Key1: 0x21,
|
|
||||||
// fyne.Key2: 0x40,
|
|
||||||
// fyne.Key3: 0x23,
|
|
||||||
// fyne.Key4: 0x24,
|
|
||||||
// fyne.Key5: 0x25,
|
|
||||||
// fyne.Key6: 0x5E,
|
|
||||||
// fyne.Key7: 0x26,
|
|
||||||
// fyne.Key8: 0x2A,
|
|
||||||
// fyne.Key9: 0x28,
|
|
||||||
// fyne.KeyA: 0x41,
|
|
||||||
// fyne.KeyB: 0x42,
|
|
||||||
// fyne.KeyC: 0x43,
|
|
||||||
// fyne.KeyD: 0x44,
|
|
||||||
// fyne.KeyE: 0x45,
|
|
||||||
// fyne.KeyF: 0x46,
|
|
||||||
// fyne.KeyG: 0x47,
|
|
||||||
// fyne.KeyH: 0x48,
|
|
||||||
// fyne.KeyI: 0x49,
|
|
||||||
// fyne.KeyJ: 0x4a,
|
|
||||||
// fyne.KeyK: 0x4b,
|
|
||||||
// fyne.KeyL: 0x4c,
|
|
||||||
// fyne.KeyM: 0x4d,
|
|
||||||
// fyne.KeyN: 0x4e,
|
|
||||||
// fyne.KeyO: 0x4f,
|
|
||||||
// fyne.KeyP: 0x50,
|
|
||||||
// fyne.KeyQ: 0x51,
|
|
||||||
// fyne.KeyR: 0x52,
|
|
||||||
// fyne.KeyS: 0x53,
|
|
||||||
// fyne.KeyT: 0x54,
|
|
||||||
// fyne.KeyU: 0x55,
|
|
||||||
// fyne.KeyV: 0x56,
|
|
||||||
// fyne.KeyW: 0x57,
|
|
||||||
// fyne.KeyX: 0x58,
|
|
||||||
// fyne.KeyY: 0x59,
|
|
||||||
// fyne.KeyZ: 0x5A,
|
|
||||||
// fyne.KeySpace: 0x20,
|
|
||||||
// fyne.KeyApostrophe: 0x22,
|
|
||||||
// fyne.KeyComma: 0x3C,
|
|
||||||
// fyne.KeyMinus: 0x5F,
|
|
||||||
// fyne.KeyPeriod: 0x3E,
|
|
||||||
// fyne.KeySlash: 0x3F,
|
|
||||||
// fyne.KeyBackslash: 0x7C,
|
|
||||||
// fyne.KeyLeftBracket: 0x7B,
|
|
||||||
// fyne.KeyRightBracket: 0x7D,
|
|
||||||
// fyne.KeySemicolon: 0x3A,
|
|
||||||
// fyne.KeyEqual: 0x2B,
|
|
||||||
// fyne.KeyAsterisk: 0x7E,
|
|
||||||
// fyne.KeyPlus: 0x7E,
|
|
||||||
// fyne.KeyBackTick: 0x60,
|
|
||||||
//}
|
|
||||||
@ -1,153 +0,0 @@
|
|||||||
// Package usart
|
|
||||||
// Universal Serial Asynchronous Receiver/Transmitter
|
|
||||||
// i8051, MSM82C51, КР580ВВ51
|
|
||||||
// By Romych, 2025.03.04
|
|
||||||
package usart
|
|
||||||
|
|
||||||
import log "github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
//const I8251DSRFlag = 0x80
|
|
||||||
//const I8251SynDetFlag = 0x40
|
|
||||||
//const I8251FrameErrorFlag = 0x20
|
|
||||||
//const I8251OverrunErrorFlag = 0x10
|
|
||||||
//const I8251ParityErrorFlag = 0x08
|
|
||||||
|
|
||||||
const I8251TxEnableFlag = 0x04
|
|
||||||
const I8251RxReadyFlag = 0x02
|
|
||||||
const I8251TxReadyFlag = 0x01
|
|
||||||
const I8251TxBuffMaxLen = 16
|
|
||||||
|
|
||||||
const (
|
|
||||||
Sio8251Reset = iota
|
|
||||||
Sio8251LoadSyncChar1
|
|
||||||
Sio8251LoadSyncChar2
|
|
||||||
Sio8251LoadCommand
|
|
||||||
)
|
|
||||||
|
|
||||||
type I8251 struct {
|
|
||||||
counter uint64
|
|
||||||
mode byte
|
|
||||||
initState byte
|
|
||||||
syncChar1 byte
|
|
||||||
syncChar2 byte
|
|
||||||
bufferRx []byte
|
|
||||||
bufferTx []byte
|
|
||||||
rxe bool
|
|
||||||
txe bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type I8251Interface interface {
|
|
||||||
Tick()
|
|
||||||
Status() byte
|
|
||||||
Reset()
|
|
||||||
Command(value byte)
|
|
||||||
Send(value byte)
|
|
||||||
Receive() byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func New() *I8251 {
|
|
||||||
return &I8251{
|
|
||||||
counter: 0,
|
|
||||||
mode: 0,
|
|
||||||
initState: 0,
|
|
||||||
rxe: false,
|
|
||||||
txe: false,
|
|
||||||
bufferRx: []byte{},
|
|
||||||
bufferTx: []byte{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *I8251) Tick() {
|
|
||||||
s.counter++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status i8251 status [RST,RQ_RX,RST_ERR,PAUSE,RX_EN,RX_RDY,TX_RDY]
|
|
||||||
func (s *I8251) Status() byte {
|
|
||||||
var status byte = 0
|
|
||||||
if len(s.bufferRx) > 0 {
|
|
||||||
status |= I8251RxReadyFlag
|
|
||||||
}
|
|
||||||
if len(s.bufferTx) < I8251TxBuffMaxLen {
|
|
||||||
status |= I8251TxReadyFlag
|
|
||||||
}
|
|
||||||
if s.txe {
|
|
||||||
status |= I8251TxEnableFlag
|
|
||||||
}
|
|
||||||
return status
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *I8251) Reset() {
|
|
||||||
s.counter = 0
|
|
||||||
s.mode = 0
|
|
||||||
s.initState = 0
|
|
||||||
s.bufferRx = make([]byte, 8)
|
|
||||||
s.bufferTx = make([]byte, I8251TxBuffMaxLen)
|
|
||||||
s.rxe = false
|
|
||||||
s.txe = false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *I8251) Command(value byte) {
|
|
||||||
switch s.initState {
|
|
||||||
case Sio8251Reset:
|
|
||||||
s.mode = value
|
|
||||||
if s.mode&0x03 > 0 {
|
|
||||||
// SYNC
|
|
||||||
s.initState = Sio8251LoadSyncChar1
|
|
||||||
}
|
|
||||||
// ASYNC
|
|
||||||
s.initState = Sio8251LoadCommand
|
|
||||||
case Sio8251LoadSyncChar1:
|
|
||||||
s.mode = value
|
|
||||||
if s.mode&0x80 == 0 { // SYNC DOUBLE
|
|
||||||
s.initState = Sio8251LoadSyncChar2
|
|
||||||
}
|
|
||||||
case Sio8251LoadSyncChar2:
|
|
||||||
s.mode = value
|
|
||||||
s.initState = Sio8251LoadCommand
|
|
||||||
case Sio8251LoadCommand:
|
|
||||||
// value = command
|
|
||||||
if value&0x40 != 0 {
|
|
||||||
// RESET CMD
|
|
||||||
s.Reset()
|
|
||||||
} else {
|
|
||||||
// Set RXE, TXE
|
|
||||||
if value&0x04 != 0 {
|
|
||||||
s.rxe = true
|
|
||||||
} else {
|
|
||||||
s.rxe = false
|
|
||||||
}
|
|
||||||
if value&0x01 != 0 {
|
|
||||||
s.txe = true
|
|
||||||
} else {
|
|
||||||
s.txe = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *I8251) Send(value byte) {
|
|
||||||
if s.txe {
|
|
||||||
s.bufferTx = append(s.bufferTx, value)
|
|
||||||
log.Debugf("Send byte: %x", value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *I8251) Receive() byte {
|
|
||||||
if s.rxe {
|
|
||||||
if len(s.bufferRx) > 0 {
|
|
||||||
res := s.bufferRx[0]
|
|
||||||
s.bufferRx = s.bufferRx[1:]
|
|
||||||
log.Debugf("Receive: %x", res)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
log.Debugf("Receive: empty buffer")
|
|
||||||
} else {
|
|
||||||
log.Debugf("Receive: receiver disabled")
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *I8251) SetRxBytes(bytes []byte) {
|
|
||||||
s.bufferRx = append(s.bufferRx, bytes...)
|
|
||||||
}
|
|
||||||
101
z80em/constants.go
Normal file
101
z80em/constants.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package z80em
|
||||||
|
|
||||||
|
const OpHalt = 0x76
|
||||||
|
const OpLdBB = 0x40
|
||||||
|
const OpAddAB = 0x80
|
||||||
|
const OpRetNz = 0xc0
|
||||||
|
|
||||||
|
var CycleCounts = []byte{
|
||||||
|
4, 10, 7, 6, 4, 4, 7, 4, 4, 11, 7, 6, 4, 4, 7, 4,
|
||||||
|
8, 10, 7, 6, 4, 4, 7, 4, 12, 11, 7, 6, 4, 4, 7, 4,
|
||||||
|
7, 10, 16, 6, 4, 4, 7, 4, 7, 11, 16, 6, 4, 4, 7, 4,
|
||||||
|
7, 10, 13, 6, 11, 11, 10, 4, 7, 11, 13, 6, 4, 4, 7, 4,
|
||||||
|
4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4,
|
||||||
|
4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4,
|
||||||
|
4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4,
|
||||||
|
7, 7, 7, 7, 7, 7, 4, 7, 4, 4, 4, 4, 4, 4, 7, 4,
|
||||||
|
4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4,
|
||||||
|
4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4,
|
||||||
|
4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4,
|
||||||
|
4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4,
|
||||||
|
5, 10, 10, 10, 10, 11, 7, 11, 5, 10, 10, 0, 10, 17, 7, 11,
|
||||||
|
5, 10, 10, 11, 10, 11, 7, 11, 5, 4, 10, 11, 10, 0, 7, 11,
|
||||||
|
5, 10, 10, 19, 10, 11, 7, 11, 5, 4, 10, 4, 10, 0, 7, 11,
|
||||||
|
5, 10, 10, 4, 10, 11, 7, 11, 5, 6, 10, 4, 10, 0, 7, 11,
|
||||||
|
}
|
||||||
|
|
||||||
|
var CycleCountsCb = []byte{
|
||||||
|
8, 8, 8, 8, 8, 8, 15, 8, 8, 8, 8, 8, 8, 8, 15, 8,
|
||||||
|
8, 8, 8, 8, 8, 8, 15, 8, 8, 8, 8, 8, 8, 8, 15, 8,
|
||||||
|
8, 8, 8, 8, 8, 8, 15, 8, 8, 8, 8, 8, 8, 8, 15, 8,
|
||||||
|
8, 8, 8, 8, 8, 8, 15, 8, 8, 8, 8, 8, 8, 8, 15, 8,
|
||||||
|
8, 8, 8, 8, 8, 8, 12, 8, 8, 8, 8, 8, 8, 8, 12, 8,
|
||||||
|
8, 8, 8, 8, 8, 8, 12, 8, 8, 8, 8, 8, 8, 8, 12, 8,
|
||||||
|
8, 8, 8, 8, 8, 8, 12, 8, 8, 8, 8, 8, 8, 8, 12, 8,
|
||||||
|
8, 8, 8, 8, 8, 8, 12, 8, 8, 8, 8, 8, 8, 8, 12, 8,
|
||||||
|
8, 8, 8, 8, 8, 8, 15, 8, 8, 8, 8, 8, 8, 8, 15, 8,
|
||||||
|
8, 8, 8, 8, 8, 8, 15, 8, 8, 8, 8, 8, 8, 8, 15, 8,
|
||||||
|
8, 8, 8, 8, 8, 8, 15, 8, 8, 8, 8, 8, 8, 8, 15, 8,
|
||||||
|
8, 8, 8, 8, 8, 8, 15, 8, 8, 8, 8, 8, 8, 8, 15, 8,
|
||||||
|
8, 8, 8, 8, 8, 8, 15, 8, 8, 8, 8, 8, 8, 8, 15, 8,
|
||||||
|
8, 8, 8, 8, 8, 8, 15, 8, 8, 8, 8, 8, 8, 8, 15, 8,
|
||||||
|
8, 8, 8, 8, 8, 8, 15, 8, 8, 8, 8, 8, 8, 8, 15, 8,
|
||||||
|
8, 8, 8, 8, 8, 8, 15, 8, 8, 8, 8, 8, 8, 8, 15, 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
var CycleCountsDd = []byte{
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 14, 20, 10, 8, 8, 11, 0, 0, 15, 20, 10, 8, 8, 11, 0,
|
||||||
|
0, 0, 0, 0, 23, 23, 19, 0, 0, 15, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 8, 8, 19, 0, 0, 0, 0, 0, 8, 8, 19, 0,
|
||||||
|
0, 0, 0, 0, 8, 8, 19, 0, 0, 0, 0, 0, 8, 8, 19, 0,
|
||||||
|
8, 8, 8, 8, 8, 8, 19, 8, 8, 8, 8, 8, 8, 8, 19, 8,
|
||||||
|
19, 19, 19, 19, 19, 19, 0, 19, 0, 0, 0, 0, 8, 8, 19, 0,
|
||||||
|
0, 0, 0, 0, 8, 8, 19, 0, 0, 0, 0, 0, 8, 8, 19, 0,
|
||||||
|
0, 0, 0, 0, 8, 8, 19, 0, 0, 0, 0, 0, 8, 8, 19, 0,
|
||||||
|
0, 0, 0, 0, 8, 8, 19, 0, 0, 0, 0, 0, 8, 8, 19, 0,
|
||||||
|
0, 0, 0, 0, 8, 8, 19, 0, 0, 0, 0, 0, 8, 8, 19, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 14, 0, 23, 0, 15, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
var CycleCountsEd = []byte{
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
12, 12, 15, 20, 8, 14, 8, 9, 12, 12, 15, 20, 8, 14, 8, 9,
|
||||||
|
12, 12, 15, 20, 8, 14, 8, 9, 12, 12, 15, 20, 8, 14, 8, 9,
|
||||||
|
12, 12, 15, 20, 8, 14, 8, 18, 12, 12, 15, 20, 8, 14, 8, 18,
|
||||||
|
12, 12, 15, 20, 8, 14, 8, 0, 12, 12, 15, 20, 8, 14, 8, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
16, 16, 16, 16, 0, 0, 0, 0, 16, 16, 16, 16, 0, 0, 0, 0,
|
||||||
|
16, 16, 16, 16, 0, 0, 0, 0, 16, 16, 16, 16, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
var ParityBits = []bool{
|
||||||
|
true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true,
|
||||||
|
false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false,
|
||||||
|
false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false,
|
||||||
|
true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true,
|
||||||
|
false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false,
|
||||||
|
true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true,
|
||||||
|
true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true,
|
||||||
|
false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false,
|
||||||
|
false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false,
|
||||||
|
true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true,
|
||||||
|
true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true,
|
||||||
|
false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false,
|
||||||
|
true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true,
|
||||||
|
false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false,
|
||||||
|
false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false,
|
||||||
|
true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true,
|
||||||
|
}
|
||||||
657
z80em/opcode.go
Normal file
657
z80em/opcode.go
Normal file
@ -0,0 +1,657 @@
|
|||||||
|
package z80em
|
||||||
|
|
||||||
|
var instructions = []func(s *Z80Type){
|
||||||
|
// NOP
|
||||||
|
0x00: func(s *Z80Type) {
|
||||||
|
// NOP
|
||||||
|
},
|
||||||
|
// LD BC, nn
|
||||||
|
0x01: func(s *Z80Type) {
|
||||||
|
s.PC++
|
||||||
|
s.C = s.core.MemRead(s.PC)
|
||||||
|
s.PC++
|
||||||
|
s.B = s.core.MemRead(s.PC)
|
||||||
|
},
|
||||||
|
// LD (BC), A
|
||||||
|
0x02: func(s *Z80Type) {
|
||||||
|
s.core.MemWrite(s.bc(), s.A)
|
||||||
|
},
|
||||||
|
// 0x03 : INC BC
|
||||||
|
0x03: func(s *Z80Type) {
|
||||||
|
s.incBc()
|
||||||
|
},
|
||||||
|
// 0x04 : INC B
|
||||||
|
0x04: func(s *Z80Type) {
|
||||||
|
s.B = s.doInc(s.B)
|
||||||
|
},
|
||||||
|
// 0x05 : DEC B
|
||||||
|
0x05: func(s *Z80Type) {
|
||||||
|
s.B = s.doDec(s.B)
|
||||||
|
},
|
||||||
|
// 0x06 : LD B, n
|
||||||
|
0x06: func(s *Z80Type) {
|
||||||
|
s.PC++
|
||||||
|
s.B = s.core.MemRead(s.PC)
|
||||||
|
},
|
||||||
|
// 0x07 : RLCA
|
||||||
|
0x07: func(s *Z80Type) {
|
||||||
|
// This instruction is implemented as a special case of the
|
||||||
|
// more general Z80-specific RLC instruction.
|
||||||
|
// Specifially, RLCA is a version of RLC A that affects fewer flags.
|
||||||
|
// The same applies to RRCA, RLA, and RRA.
|
||||||
|
tempS := s.Flags.S
|
||||||
|
tempZ := s.Flags.Z
|
||||||
|
tempP := s.Flags.P
|
||||||
|
s.A = s.doRlc(s.A)
|
||||||
|
s.Flags.S = tempS
|
||||||
|
s.Flags.Z = tempZ
|
||||||
|
s.Flags.P = tempP
|
||||||
|
},
|
||||||
|
// 0x08 : EX AF, AF'
|
||||||
|
0x08: func(s *Z80Type) {
|
||||||
|
s.A, s.APrime = s.APrime, s.A
|
||||||
|
temp := s.getFlagsRegister()
|
||||||
|
s.setFlagsRegister(s.getFlagsPrimeRegister())
|
||||||
|
s.setFlagsPrimeRegister(temp)
|
||||||
|
},
|
||||||
|
// 0x09 : ADD HL, BC
|
||||||
|
0x09: func(s *Z80Type) {
|
||||||
|
s.doHlAdd(s.bc())
|
||||||
|
},
|
||||||
|
// 0x0a : LD A, (BC)
|
||||||
|
0x0A: func(s *Z80Type) {
|
||||||
|
s.A = s.core.MemRead(s.bc())
|
||||||
|
},
|
||||||
|
// 0x0b : DEC BC
|
||||||
|
0x0B: func(s *Z80Type) {
|
||||||
|
s.decBc()
|
||||||
|
},
|
||||||
|
// 0x0c : INC C
|
||||||
|
0x0C: func(s *Z80Type) {
|
||||||
|
s.C = s.doInc(s.C)
|
||||||
|
},
|
||||||
|
// 0x0d : DEC C
|
||||||
|
0x0D: func(s *Z80Type) {
|
||||||
|
s.C = s.doDec(s.C)
|
||||||
|
},
|
||||||
|
// 0x0e : LD C, n
|
||||||
|
0x0E: func(s *Z80Type) {
|
||||||
|
s.PC++
|
||||||
|
s.C = s.core.MemRead(s.PC)
|
||||||
|
},
|
||||||
|
// 0x0f : RRCA
|
||||||
|
0x0F: func(s *Z80Type) {
|
||||||
|
tempS := s.Flags.S
|
||||||
|
tempZ := s.Flags.Z
|
||||||
|
tempP := s.Flags.P
|
||||||
|
s.A = s.doRrc(s.A)
|
||||||
|
s.Flags.S = tempS
|
||||||
|
s.Flags.Z = tempZ
|
||||||
|
s.Flags.P = tempP
|
||||||
|
},
|
||||||
|
// 0x10 : DJNZ nn
|
||||||
|
0x10: func(s *Z80Type) {
|
||||||
|
s.B--
|
||||||
|
s.doConditionalRelativeJump(s.B != 0)
|
||||||
|
},
|
||||||
|
// 0x11 : LD DE, nn
|
||||||
|
0x11: func(s *Z80Type) {
|
||||||
|
s.PC++
|
||||||
|
s.E = s.core.MemRead(s.PC)
|
||||||
|
s.PC++
|
||||||
|
s.D = s.core.MemRead(s.PC)
|
||||||
|
},
|
||||||
|
// 0x12 : LD (DE), A
|
||||||
|
0x12: func(s *Z80Type) {
|
||||||
|
s.core.MemWrite(s.de(), s.A)
|
||||||
|
},
|
||||||
|
// 0x13 : INC DE
|
||||||
|
0x13: func(s *Z80Type) {
|
||||||
|
s.incDe()
|
||||||
|
},
|
||||||
|
// 0x14 : INC D
|
||||||
|
0x14: func(s *Z80Type) {
|
||||||
|
s.D = s.doInc(s.D)
|
||||||
|
},
|
||||||
|
// 0x15 : DEC D
|
||||||
|
0x15: func(s *Z80Type) {
|
||||||
|
s.D = s.doDec(s.D)
|
||||||
|
},
|
||||||
|
// 0x16 : LD D, n
|
||||||
|
0x16: func(s *Z80Type) {
|
||||||
|
s.PC++
|
||||||
|
s.D = s.core.MemRead(s.PC)
|
||||||
|
},
|
||||||
|
// 0x17 : RLA
|
||||||
|
0x17: func(s *Z80Type) {
|
||||||
|
tempS := s.Flags.S
|
||||||
|
tempZ := s.Flags.Z
|
||||||
|
tempP := s.Flags.P
|
||||||
|
s.A = s.doRl(s.A)
|
||||||
|
s.Flags.S = tempS
|
||||||
|
s.Flags.Z = tempZ
|
||||||
|
s.Flags.P = tempP
|
||||||
|
},
|
||||||
|
// 0x18 : JR n
|
||||||
|
0x18: func(s *Z80Type) {
|
||||||
|
var o = int8(s.core.MemRead(s.PC + 1))
|
||||||
|
if o > 0 {
|
||||||
|
s.PC += uint16(o)
|
||||||
|
} else {
|
||||||
|
s.PC -= uint16(-o)
|
||||||
|
}
|
||||||
|
s.PC++
|
||||||
|
},
|
||||||
|
// 0x19 : ADD HL, DE
|
||||||
|
0x19: func(s *Z80Type) {
|
||||||
|
s.doHlAdd(s.de())
|
||||||
|
},
|
||||||
|
// 0x1a : LD A, (DE)
|
||||||
|
0x1A: func(s *Z80Type) {
|
||||||
|
s.A = s.core.MemRead(s.de())
|
||||||
|
},
|
||||||
|
// 0x1b : DEC DE
|
||||||
|
0x1B: func(s *Z80Type) {
|
||||||
|
s.decDe()
|
||||||
|
},
|
||||||
|
// 0x1c : INC E
|
||||||
|
0x1C: func(s *Z80Type) {
|
||||||
|
s.E = s.doInc(s.E)
|
||||||
|
},
|
||||||
|
// 0x1d : DEC E
|
||||||
|
0x1D: func(s *Z80Type) {
|
||||||
|
s.E = s.doDec(s.E)
|
||||||
|
},
|
||||||
|
// 0x1e : LD E, n
|
||||||
|
0x1E: func(s *Z80Type) {
|
||||||
|
s.PC++
|
||||||
|
s.E = s.core.MemRead(s.PC)
|
||||||
|
},
|
||||||
|
// 0x1f : RRA
|
||||||
|
0x1F: func(s *Z80Type) {
|
||||||
|
tempS := s.Flags.S
|
||||||
|
tempZ := s.Flags.Z
|
||||||
|
tempP := s.Flags.P
|
||||||
|
s.A = s.doRr(s.A)
|
||||||
|
s.Flags.S = tempS
|
||||||
|
s.Flags.Z = tempZ
|
||||||
|
s.Flags.P = tempP
|
||||||
|
},
|
||||||
|
// 0x20 : JR NZ, n
|
||||||
|
0x20: func(s *Z80Type) {
|
||||||
|
s.doConditionalRelativeJump(!s.Flags.Z)
|
||||||
|
},
|
||||||
|
// 0x21 : LD HL, nn
|
||||||
|
0x21: func(s *Z80Type) {
|
||||||
|
s.PC++
|
||||||
|
s.L = s.core.MemRead(s.PC)
|
||||||
|
s.PC++
|
||||||
|
s.H = s.core.MemRead(s.PC)
|
||||||
|
},
|
||||||
|
// 0x22 : LD (nn), HL
|
||||||
|
0x22: func(s *Z80Type) {
|
||||||
|
addr := s.getAddr()
|
||||||
|
s.core.MemWrite(addr, s.L)
|
||||||
|
s.core.MemWrite(addr+1, s.H)
|
||||||
|
},
|
||||||
|
// 0x23 : INC HL
|
||||||
|
0x23: func(s *Z80Type) {
|
||||||
|
s.incHl()
|
||||||
|
},
|
||||||
|
// 0x24 : INC H
|
||||||
|
0x24: func(s *Z80Type) {
|
||||||
|
s.H = s.doInc(s.H)
|
||||||
|
},
|
||||||
|
// 0x25 : DEC H
|
||||||
|
0x25: func(s *Z80Type) {
|
||||||
|
s.H = s.doDec(s.H)
|
||||||
|
},
|
||||||
|
// 0x26 : LD H, n
|
||||||
|
0x26: func(s *Z80Type) {
|
||||||
|
s.PC++
|
||||||
|
s.H = s.core.MemRead(s.PC)
|
||||||
|
},
|
||||||
|
// 0x27 : DAA
|
||||||
|
0x27: func(s *Z80Type) {
|
||||||
|
temp := s.A
|
||||||
|
if !s.Flags.N {
|
||||||
|
if s.Flags.H || ((s.A & 0x0f) > 9) {
|
||||||
|
temp += 0x06
|
||||||
|
}
|
||||||
|
if s.Flags.C || (s.A > 0x99) {
|
||||||
|
temp += 0x60
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if s.Flags.H || ((s.A & 0x0f) > 9) {
|
||||||
|
temp -= 0x06
|
||||||
|
}
|
||||||
|
if s.Flags.C || (s.A > 0x99) {
|
||||||
|
temp -= 0x60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Flags.S = (temp & 0x80) != 0
|
||||||
|
s.Flags.Z = temp == 0
|
||||||
|
s.Flags.H = ((s.A & 0x10) ^ (temp & 0x10)) != 0
|
||||||
|
s.Flags.P = ParityBits[temp]
|
||||||
|
// DAA never clears the carry flag if it was already set,
|
||||||
|
// but it is able to set the carry flag if it was clear.
|
||||||
|
// Don't ask me, I don't know.
|
||||||
|
// Note also that we check for a BCD carry, instead of the usual.
|
||||||
|
s.Flags.C = s.Flags.C || (s.A > 0x99)
|
||||||
|
s.A = temp
|
||||||
|
s.updateXYFlags(s.A)
|
||||||
|
},
|
||||||
|
// 0x28 : JR Z, n
|
||||||
|
0x28: func(s *Z80Type) {
|
||||||
|
s.doConditionalRelativeJump(s.Flags.Z)
|
||||||
|
},
|
||||||
|
// 0x29 : ADD HL, HL
|
||||||
|
0x29: func(s *Z80Type) {
|
||||||
|
s.doHlAdd(s.hl())
|
||||||
|
},
|
||||||
|
// 0x2a : LD HL, (nn)
|
||||||
|
0x2A: func(s *Z80Type) {
|
||||||
|
addr := s.getAddr()
|
||||||
|
s.L = s.core.MemRead(addr)
|
||||||
|
s.H = s.core.MemRead(addr + 1)
|
||||||
|
},
|
||||||
|
// 0x2b : DEC HL
|
||||||
|
0x2B: func(s *Z80Type) {
|
||||||
|
s.decHl()
|
||||||
|
},
|
||||||
|
// 0x2c : INC L
|
||||||
|
0x2C: func(s *Z80Type) {
|
||||||
|
s.L = s.doInc(s.L)
|
||||||
|
},
|
||||||
|
// 0x2d : DEC L
|
||||||
|
0x2D: func(s *Z80Type) {
|
||||||
|
s.L = s.doDec(s.L)
|
||||||
|
},
|
||||||
|
// 0x2e : LD L, n
|
||||||
|
0x2E: func(s *Z80Type) {
|
||||||
|
s.PC++
|
||||||
|
s.L = s.core.MemRead(s.PC)
|
||||||
|
},
|
||||||
|
// 0x2f : CPL
|
||||||
|
0x2F: func(s *Z80Type) {
|
||||||
|
s.A = ^s.A
|
||||||
|
s.Flags.N = true
|
||||||
|
s.Flags.H = true
|
||||||
|
s.updateXYFlags(s.A)
|
||||||
|
},
|
||||||
|
// 0x30 : JR NC, n
|
||||||
|
0x30: func(s *Z80Type) {
|
||||||
|
s.doConditionalRelativeJump(!s.Flags.C)
|
||||||
|
},
|
||||||
|
// 0x31 : LD SP, nn
|
||||||
|
0x31: func(s *Z80Type) {
|
||||||
|
s.PC++
|
||||||
|
lo := s.core.MemRead(s.PC)
|
||||||
|
s.PC++
|
||||||
|
s.SP = (uint16(s.core.MemRead(s.PC)) << 8) | uint16(lo)
|
||||||
|
},
|
||||||
|
// 0x32 : LD (nn), A
|
||||||
|
0x32: func(s *Z80Type) {
|
||||||
|
s.core.MemWrite(s.getAddr(), s.A)
|
||||||
|
},
|
||||||
|
// 0x33 : INC SP
|
||||||
|
0x33: func(s *Z80Type) {
|
||||||
|
s.SP++
|
||||||
|
},
|
||||||
|
// 0x34 : INC (HL)
|
||||||
|
0x34: func(s *Z80Type) {
|
||||||
|
s.core.MemWrite(s.hl(), s.doInc(s.core.MemRead(s.hl())))
|
||||||
|
},
|
||||||
|
// 0x35 : DEC (HL)
|
||||||
|
0x35: func(s *Z80Type) {
|
||||||
|
s.core.MemWrite(s.hl(), s.doDec(s.core.MemRead(s.hl())))
|
||||||
|
},
|
||||||
|
// 0x36 : LD (HL), n
|
||||||
|
0x36: func(s *Z80Type) {
|
||||||
|
s.PC++
|
||||||
|
s.core.MemWrite(s.hl(), s.core.MemRead(s.PC))
|
||||||
|
},
|
||||||
|
// 0x37 : SCF
|
||||||
|
0x37: func(s *Z80Type) {
|
||||||
|
s.Flags.N = false
|
||||||
|
s.Flags.H = false
|
||||||
|
s.Flags.C = true
|
||||||
|
s.updateXYFlags(s.A)
|
||||||
|
},
|
||||||
|
// 0x38 : JR C, n
|
||||||
|
0x38: func(s *Z80Type) {
|
||||||
|
s.doConditionalRelativeJump(s.Flags.C)
|
||||||
|
},
|
||||||
|
// 0x39 : ADD HL, SP
|
||||||
|
0x39: func(s *Z80Type) {
|
||||||
|
s.doHlAdd(s.SP)
|
||||||
|
},
|
||||||
|
// 0x3a : LD A, (nn)
|
||||||
|
0x3A: func(s *Z80Type) {
|
||||||
|
s.A = s.core.MemRead(s.getAddr())
|
||||||
|
},
|
||||||
|
// 0x3b : DEC SP
|
||||||
|
0x3B: func(s *Z80Type) {
|
||||||
|
s.SP--
|
||||||
|
},
|
||||||
|
// 0x3c : INC A
|
||||||
|
0x3C: func(s *Z80Type) {
|
||||||
|
s.A = s.doInc(s.A)
|
||||||
|
},
|
||||||
|
// 0x3d : DEC A
|
||||||
|
0x3D: func(s *Z80Type) {
|
||||||
|
s.A = s.doDec(s.A)
|
||||||
|
},
|
||||||
|
// 0x3e : LD A, n
|
||||||
|
0x3E: func(s *Z80Type) {
|
||||||
|
s.PC++
|
||||||
|
s.A = s.core.MemRead(s.PC)
|
||||||
|
},
|
||||||
|
// 0x3f : CCF
|
||||||
|
0x3F: func(s *Z80Type) {
|
||||||
|
s.Flags.N = false
|
||||||
|
s.Flags.H = s.Flags.C
|
||||||
|
s.Flags.C = !s.Flags.C
|
||||||
|
s.updateXYFlags(s.A)
|
||||||
|
},
|
||||||
|
// 0xc0 : RET NZ
|
||||||
|
0xC0: func(s *Z80Type) {
|
||||||
|
s.doConditionalReturn(!s.Flags.Z)
|
||||||
|
},
|
||||||
|
// 0xc1 : POP BC
|
||||||
|
0xC1: func(s *Z80Type) {
|
||||||
|
result := s.PopWord()
|
||||||
|
s.C = byte(result & 0xff)
|
||||||
|
s.B = byte((result & 0xff00) >> 8)
|
||||||
|
},
|
||||||
|
// 0xc2 : JP NZ, nn
|
||||||
|
0xC2: func(s *Z80Type) {
|
||||||
|
s.doConditionalAbsoluteJump(!s.Flags.Z)
|
||||||
|
},
|
||||||
|
// 0xc3 : JP nn
|
||||||
|
0xC3: func(s *Z80Type) {
|
||||||
|
s.PC = uint16(s.core.MemRead(s.PC+1)) | (uint16(s.core.MemRead(s.PC+2)) << 8)
|
||||||
|
s.PC--
|
||||||
|
},
|
||||||
|
// 0xc4 : CALL NZ, nn
|
||||||
|
0xC4: func(s *Z80Type) {
|
||||||
|
s.doConditionalCall(!s.Flags.Z)
|
||||||
|
},
|
||||||
|
// 0xc5 : PUSH BC
|
||||||
|
0xC5: func(s *Z80Type) {
|
||||||
|
s.pushWord((uint16(s.B) << 8) | uint16(s.C))
|
||||||
|
},
|
||||||
|
// 0xc6 : ADD A, n
|
||||||
|
0xC6: func(s *Z80Type) {
|
||||||
|
s.PC++
|
||||||
|
s.doAdd(s.core.MemRead(s.PC))
|
||||||
|
},
|
||||||
|
// 0xc7 : RST 00h
|
||||||
|
0xC7: func(s *Z80Type) {
|
||||||
|
s.doReset(0x0000)
|
||||||
|
},
|
||||||
|
// 0xc8 : RET Z
|
||||||
|
0xC8: func(s *Z80Type) {
|
||||||
|
s.doConditionalReturn(s.Flags.Z)
|
||||||
|
},
|
||||||
|
// 0xc9 : RET
|
||||||
|
0xC9: func(s *Z80Type) {
|
||||||
|
s.PC = s.PopWord() - 1
|
||||||
|
},
|
||||||
|
// 0xca : JP Z, nn
|
||||||
|
0xCA: func(s *Z80Type) {
|
||||||
|
s.doConditionalAbsoluteJump(s.Flags.Z)
|
||||||
|
},
|
||||||
|
// 0xcb : CB Prefix
|
||||||
|
0xCB: func(s *Z80Type) {
|
||||||
|
s.opcodeCB()
|
||||||
|
},
|
||||||
|
// 0xcc : CALL Z, nn
|
||||||
|
0xCC: func(s *Z80Type) {
|
||||||
|
s.doConditionalCall(s.Flags.Z)
|
||||||
|
},
|
||||||
|
// 0xcd : CALL nn
|
||||||
|
0xCD: func(s *Z80Type) {
|
||||||
|
s.pushWord(s.PC + 3)
|
||||||
|
s.PC = uint16(s.core.MemRead(s.PC+1)) | (uint16(s.core.MemRead(s.PC+2)) << 8)
|
||||||
|
s.PC--
|
||||||
|
},
|
||||||
|
// 0xce : ADC A, n
|
||||||
|
0xCE: func(s *Z80Type) {
|
||||||
|
s.PC++
|
||||||
|
s.doAdc(s.core.MemRead(s.PC))
|
||||||
|
},
|
||||||
|
// 0xcf : RST 08h
|
||||||
|
0xCF: func(s *Z80Type) {
|
||||||
|
s.doReset(0x0008)
|
||||||
|
},
|
||||||
|
// 0xd0 : RET NC
|
||||||
|
0xD0: func(s *Z80Type) {
|
||||||
|
s.doConditionalReturn(!s.Flags.C)
|
||||||
|
},
|
||||||
|
// 0xd1 : POP DE
|
||||||
|
0xD1: func(s *Z80Type) {
|
||||||
|
result := s.PopWord()
|
||||||
|
s.E = byte(result & 0xff)
|
||||||
|
s.D = byte((result & 0xff00) >> 8)
|
||||||
|
},
|
||||||
|
// 0xd2 : JP NC, nn
|
||||||
|
0xD2: func(s *Z80Type) {
|
||||||
|
s.doConditionalAbsoluteJump(!s.Flags.C)
|
||||||
|
},
|
||||||
|
// 0xd3 : OUT (n), A
|
||||||
|
0xD3: func(s *Z80Type) {
|
||||||
|
s.PC++
|
||||||
|
s.core.IOWrite((uint16(s.A)<<8)|uint16(s.core.MemRead(s.PC)), s.A)
|
||||||
|
},
|
||||||
|
// 0xd4 : CALL NC, nn
|
||||||
|
0xD4: func(s *Z80Type) {
|
||||||
|
s.doConditionalCall(!s.Flags.C)
|
||||||
|
},
|
||||||
|
// 0xd5 : PUSH DE
|
||||||
|
0xD5: func(s *Z80Type) {
|
||||||
|
s.pushWord((uint16(s.D) << 8) | uint16(s.E))
|
||||||
|
},
|
||||||
|
// 0xd6 : SUB n
|
||||||
|
0xD6: func(s *Z80Type) {
|
||||||
|
s.PC++
|
||||||
|
s.doSub(s.core.MemRead(s.PC))
|
||||||
|
},
|
||||||
|
// 0xd7 : RST 10h
|
||||||
|
0xD7: func(s *Z80Type) {
|
||||||
|
s.doReset(0x0010)
|
||||||
|
},
|
||||||
|
// 0xd8 : RET C
|
||||||
|
0xD8: func(s *Z80Type) {
|
||||||
|
s.doConditionalReturn(s.Flags.C)
|
||||||
|
},
|
||||||
|
// 0xd9 : EXX
|
||||||
|
0xD9: func(s *Z80Type) {
|
||||||
|
s.B, s.BPrime = s.BPrime, s.B
|
||||||
|
s.C, s.CPrime = s.CPrime, s.C
|
||||||
|
s.D, s.DPrime = s.DPrime, s.D
|
||||||
|
s.E, s.EPrime = s.EPrime, s.E
|
||||||
|
s.H, s.HPrime = s.HPrime, s.H
|
||||||
|
s.L, s.LPrime = s.LPrime, s.L
|
||||||
|
},
|
||||||
|
// 0xda : JP C, nn
|
||||||
|
0xDA: func(s *Z80Type) {
|
||||||
|
s.doConditionalAbsoluteJump(s.Flags.C)
|
||||||
|
},
|
||||||
|
// 0xdb : IN A, (n)
|
||||||
|
0xDB: func(s *Z80Type) {
|
||||||
|
s.PC++
|
||||||
|
s.A = s.core.IORead((uint16(s.A) << 8) | uint16(s.core.MemRead(s.PC)))
|
||||||
|
},
|
||||||
|
// 0xdc : CALL C, nn
|
||||||
|
0xDC: func(s *Z80Type) {
|
||||||
|
s.doConditionalCall(s.Flags.C)
|
||||||
|
},
|
||||||
|
// 0xdd : DD Prefix (IX instructions)
|
||||||
|
0xDD: func(s *Z80Type) {
|
||||||
|
s.opcodeDD()
|
||||||
|
},
|
||||||
|
// 0xde : SBC n
|
||||||
|
0xDE: func(s *Z80Type) {
|
||||||
|
s.PC++
|
||||||
|
s.doSbc(s.core.MemRead(s.PC))
|
||||||
|
},
|
||||||
|
// 0xdf : RST 18h
|
||||||
|
0xDF: func(s *Z80Type) {
|
||||||
|
s.doReset(0x0018)
|
||||||
|
},
|
||||||
|
// 0xe0 : RET PO
|
||||||
|
0xE0: func(s *Z80Type) {
|
||||||
|
s.doConditionalReturn(!s.Flags.P)
|
||||||
|
},
|
||||||
|
// 0xe1 : POP HL
|
||||||
|
0xE1: func(s *Z80Type) {
|
||||||
|
result := s.PopWord()
|
||||||
|
s.L = byte(result & 0xff)
|
||||||
|
s.H = byte((result & 0xff00) >> 8)
|
||||||
|
},
|
||||||
|
// 0xe2 : JP PO, (nn)
|
||||||
|
0xE2: func(s *Z80Type) {
|
||||||
|
s.doConditionalAbsoluteJump(!s.Flags.P)
|
||||||
|
},
|
||||||
|
// 0xe3 : EX (SP), HL
|
||||||
|
0xE3: func(s *Z80Type) {
|
||||||
|
temp := s.core.MemRead(s.SP)
|
||||||
|
s.core.MemWrite(s.SP, s.L)
|
||||||
|
s.L = temp
|
||||||
|
temp = s.core.MemRead(s.SP + 1)
|
||||||
|
s.core.MemWrite(s.SP+1, s.H)
|
||||||
|
s.H = temp
|
||||||
|
},
|
||||||
|
// 0xe4 : CALL PO, nn
|
||||||
|
0xE4: func(s *Z80Type) {
|
||||||
|
s.doConditionalCall(!s.Flags.P)
|
||||||
|
},
|
||||||
|
// 0xe5 : PUSH HL
|
||||||
|
0xE5: func(s *Z80Type) {
|
||||||
|
s.pushWord((uint16(s.H) << 8) | uint16(s.L))
|
||||||
|
},
|
||||||
|
// 0xe6 : AND n
|
||||||
|
0xE6: func(s *Z80Type) {
|
||||||
|
s.PC++
|
||||||
|
s.doAnd(s.core.MemRead(s.PC))
|
||||||
|
},
|
||||||
|
// 0xe7 : RST 20h
|
||||||
|
0xE7: func(s *Z80Type) {
|
||||||
|
s.doReset(0x0020)
|
||||||
|
},
|
||||||
|
// 0xe8 : RET PE
|
||||||
|
0xE8: func(s *Z80Type) {
|
||||||
|
s.doConditionalReturn(s.Flags.P)
|
||||||
|
},
|
||||||
|
// 0xe9 : JP (HL)
|
||||||
|
0xE9: func(s *Z80Type) {
|
||||||
|
s.PC = uint16(s.H)<<8 | uint16(s.L)
|
||||||
|
s.PC--
|
||||||
|
},
|
||||||
|
// 0xea : JP PE, nn
|
||||||
|
0xEA: func(s *Z80Type) {
|
||||||
|
s.doConditionalAbsoluteJump(s.Flags.P)
|
||||||
|
},
|
||||||
|
// 0xeb : EX DE, HL
|
||||||
|
0xEB: func(s *Z80Type) {
|
||||||
|
s.D, s.H = s.H, s.D
|
||||||
|
s.E, s.L = s.L, s.E
|
||||||
|
},
|
||||||
|
// 0xec : CALL PE, nn
|
||||||
|
0xEC: func(s *Z80Type) {
|
||||||
|
s.doConditionalCall(s.Flags.P)
|
||||||
|
},
|
||||||
|
// 0xed : ED Prefix
|
||||||
|
0xED: func(s *Z80Type) {
|
||||||
|
s.opcodeED()
|
||||||
|
},
|
||||||
|
// 0xee : XOR n
|
||||||
|
0xEE: func(s *Z80Type) {
|
||||||
|
s.PC++
|
||||||
|
s.doXor(s.core.MemRead(s.PC))
|
||||||
|
},
|
||||||
|
// 0xef : RST 28h
|
||||||
|
0xEF: func(s *Z80Type) {
|
||||||
|
s.doReset(0x0028)
|
||||||
|
},
|
||||||
|
// 0xf0 : RET P
|
||||||
|
0xF0: func(s *Z80Type) {
|
||||||
|
s.doConditionalReturn(!s.Flags.S)
|
||||||
|
},
|
||||||
|
// 0xf1 : POP AF
|
||||||
|
0xF1: func(s *Z80Type) {
|
||||||
|
var result = s.PopWord()
|
||||||
|
s.setFlagsRegister(byte(result & 0xff))
|
||||||
|
s.A = byte((result & 0xff00) >> 8)
|
||||||
|
},
|
||||||
|
// 0xf2 : JP P, nn
|
||||||
|
0xF2: func(s *Z80Type) {
|
||||||
|
s.doConditionalAbsoluteJump(!s.Flags.S)
|
||||||
|
},
|
||||||
|
// 0xf3 : DI
|
||||||
|
0xF3: func(s *Z80Type) {
|
||||||
|
// DI doesn't actually take effect until after the next instruction.
|
||||||
|
s.DoDelayedDI = true
|
||||||
|
},
|
||||||
|
// 0xf4 : CALL P, nn
|
||||||
|
0xF4: func(s *Z80Type) {
|
||||||
|
s.doConditionalCall(!s.Flags.S)
|
||||||
|
},
|
||||||
|
// 0xf5 : PUSH AF
|
||||||
|
0xF5: func(s *Z80Type) {
|
||||||
|
s.pushWord(uint16(s.getFlagsRegister()) | (uint16(s.A) << 8))
|
||||||
|
},
|
||||||
|
// 0xf6 : OR n
|
||||||
|
0xF6: func(s *Z80Type) {
|
||||||
|
s.PC++
|
||||||
|
s.doOr(s.core.MemRead(s.PC))
|
||||||
|
},
|
||||||
|
// 0xf7 : RST 30h
|
||||||
|
0xF7: func(s *Z80Type) {
|
||||||
|
s.doReset(0x0030)
|
||||||
|
},
|
||||||
|
// 0xf8 : RET M
|
||||||
|
0xF8: func(s *Z80Type) {
|
||||||
|
s.doConditionalReturn(s.Flags.S)
|
||||||
|
},
|
||||||
|
// 0xf9 : LD SP, HL
|
||||||
|
0xF9: func(s *Z80Type) {
|
||||||
|
s.SP = uint16(s.H)<<8 | uint16(s.L)
|
||||||
|
},
|
||||||
|
// 0xfa : JP M, nn
|
||||||
|
0xFA: func(s *Z80Type) {
|
||||||
|
s.doConditionalAbsoluteJump(s.Flags.S)
|
||||||
|
},
|
||||||
|
// 0xfb : EI
|
||||||
|
0xFB: func(s *Z80Type) {
|
||||||
|
// EI doesn't actually take effect until after the next instruction.
|
||||||
|
s.DoDelayedEI = true
|
||||||
|
},
|
||||||
|
// 0xfc : CALL M, nn
|
||||||
|
0xFC: func(s *Z80Type) {
|
||||||
|
s.doConditionalCall(s.Flags.S)
|
||||||
|
},
|
||||||
|
// 0xfd : FD Prefix (IY instructions)
|
||||||
|
0xFD: func(s *Z80Type) {
|
||||||
|
s.opcodeFD()
|
||||||
|
},
|
||||||
|
// 0xfe : CP n
|
||||||
|
0xFE: func(s *Z80Type) {
|
||||||
|
s.PC++
|
||||||
|
s.doCp(s.core.MemRead(s.PC))
|
||||||
|
},
|
||||||
|
// 0xff : RST 38h
|
||||||
|
0xFF: func(s *Z80Type) {
|
||||||
|
s.doReset(0x0038)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *Z80Type) getAddr() uint16 {
|
||||||
|
z.PC++
|
||||||
|
addr := uint16(z.core.MemRead(z.PC))
|
||||||
|
z.PC++
|
||||||
|
addr |= uint16(z.core.MemRead(z.PC)) << 8
|
||||||
|
return addr
|
||||||
|
}
|
||||||
106
z80em/opcodeCB.go
Normal file
106
z80em/opcodeCB.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package z80em
|
||||||
|
|
||||||
|
func (z *Z80Type) opcodeCB() {
|
||||||
|
z.R = (z.R & 0x80) | (((z.R & 0x7f) + 1) & 0x7f)
|
||||||
|
z.PC++
|
||||||
|
opcode := z.core.M1MemRead(z.PC)
|
||||||
|
bitNumber := (opcode & 0x38) >> 3
|
||||||
|
regCode := opcode & 0x07
|
||||||
|
if opcode < 0x40 {
|
||||||
|
// Shift/rotate instructions
|
||||||
|
opArray := []OpShift{z.doRlc, z.doRrc, z.doRl, z.doRr, z.doSla, z.doSra, z.doSll, z.doSrl}
|
||||||
|
switch regCode {
|
||||||
|
case 0:
|
||||||
|
z.B = opArray[bitNumber](z.B)
|
||||||
|
case 1:
|
||||||
|
z.C = opArray[bitNumber](z.C)
|
||||||
|
case 2:
|
||||||
|
z.D = opArray[bitNumber](z.D)
|
||||||
|
case 3:
|
||||||
|
z.E = opArray[bitNumber](z.E)
|
||||||
|
case 4:
|
||||||
|
z.H = opArray[bitNumber](z.H)
|
||||||
|
case 5:
|
||||||
|
z.L = opArray[bitNumber](z.L)
|
||||||
|
case 6:
|
||||||
|
z.core.MemWrite(z.hl(), opArray[bitNumber](z.core.MemRead(z.hl())))
|
||||||
|
default:
|
||||||
|
z.A = opArray[bitNumber](z.A)
|
||||||
|
}
|
||||||
|
} else if opcode < 0x80 {
|
||||||
|
// BIT instructions
|
||||||
|
switch regCode {
|
||||||
|
case 0:
|
||||||
|
z.Flags.Z = z.B&(1<<bitNumber) == 0
|
||||||
|
case 1:
|
||||||
|
z.Flags.Z = z.C&(1<<bitNumber) == 0
|
||||||
|
case 2:
|
||||||
|
z.Flags.Z = z.D&(1<<bitNumber) == 0
|
||||||
|
case 3:
|
||||||
|
z.Flags.Z = z.E&(1<<bitNumber) == 0
|
||||||
|
case 4:
|
||||||
|
z.Flags.Z = z.H&(1<<bitNumber) == 0
|
||||||
|
case 5:
|
||||||
|
z.Flags.Z = z.L&(1<<bitNumber) == 0
|
||||||
|
case 6:
|
||||||
|
z.Flags.Z = z.core.MemRead(z.hl())&(1<<bitNumber) == 0
|
||||||
|
default:
|
||||||
|
z.Flags.Z = z.A&(1<<bitNumber) == 0
|
||||||
|
}
|
||||||
|
z.Flags.N = false
|
||||||
|
z.Flags.H = true
|
||||||
|
z.Flags.P = z.Flags.Z
|
||||||
|
z.Flags.S = (bitNumber == 7) && !z.Flags.Z
|
||||||
|
// For the BIT n, (HL) instruction, the X and Y flags are obtained
|
||||||
|
// from what is apparently an internal temporary register used for
|
||||||
|
// some of the 16-bit arithmetic instructions.
|
||||||
|
// I haven't implemented that register here,
|
||||||
|
// so for now we'll set X and Y the same way for every BIT opcode,
|
||||||
|
// which means that they will usually be wrong for BIT n, (HL).
|
||||||
|
z.Flags.Y = (bitNumber == 5) && !z.Flags.Z
|
||||||
|
z.Flags.X = (bitNumber == 3) && !z.Flags.Z
|
||||||
|
} else if opcode < 0xC0 {
|
||||||
|
// RES instructions
|
||||||
|
negMask := byte(^(1 << bitNumber))
|
||||||
|
switch regCode {
|
||||||
|
case 0:
|
||||||
|
z.B &= negMask
|
||||||
|
case 1:
|
||||||
|
z.C &= negMask
|
||||||
|
case 2:
|
||||||
|
z.D &= negMask
|
||||||
|
case 3:
|
||||||
|
z.E &= negMask
|
||||||
|
case 4:
|
||||||
|
z.H &= negMask
|
||||||
|
case 5:
|
||||||
|
z.L &= negMask
|
||||||
|
case 6:
|
||||||
|
z.core.MemWrite(z.hl(), z.core.MemRead(z.hl())&negMask)
|
||||||
|
default:
|
||||||
|
z.A &= negMask
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// SET instructions
|
||||||
|
mask := byte(1 << bitNumber)
|
||||||
|
switch regCode {
|
||||||
|
case 0:
|
||||||
|
z.B |= mask
|
||||||
|
case 1:
|
||||||
|
z.C |= mask
|
||||||
|
case 2:
|
||||||
|
z.D |= mask
|
||||||
|
case 3:
|
||||||
|
z.E |= mask
|
||||||
|
case 4:
|
||||||
|
z.H |= mask
|
||||||
|
case 5:
|
||||||
|
z.L |= mask
|
||||||
|
case 6:
|
||||||
|
z.core.MemWrite(z.hl(), z.core.MemRead(z.hl())|mask)
|
||||||
|
default:
|
||||||
|
z.A |= mask
|
||||||
|
}
|
||||||
|
}
|
||||||
|
z.CycleCounter += CycleCountsCb[opcode]
|
||||||
|
}
|
||||||
483
z80em/opcodeDD.go
Normal file
483
z80em/opcodeDD.go
Normal file
@ -0,0 +1,483 @@
|
|||||||
|
package z80em
|
||||||
|
|
||||||
|
var ddInstructions = []func(s *Z80Type){
|
||||||
|
// 0x09 : ADD IX, BC
|
||||||
|
0x09: func(s *Z80Type) {
|
||||||
|
s.doIxAdd(s.bc())
|
||||||
|
},
|
||||||
|
// 0x19 : ADD IX, DE
|
||||||
|
0x19: func(s *Z80Type) {
|
||||||
|
s.doIxAdd(s.de())
|
||||||
|
},
|
||||||
|
// 0x21 : LD IX, nn
|
||||||
|
0x21: func(s *Z80Type) {
|
||||||
|
s.IX = s.getAddr()
|
||||||
|
},
|
||||||
|
// 0x22 : LD (nn), IX
|
||||||
|
0x22: func(s *Z80Type) {
|
||||||
|
addr := s.getAddr()
|
||||||
|
s.core.MemWrite(addr, byte(s.IX&0x00ff))
|
||||||
|
s.core.MemWrite(addr+1, byte(s.IX>>8))
|
||||||
|
},
|
||||||
|
// 0x23 : INC IX
|
||||||
|
0x23: func(s *Z80Type) {
|
||||||
|
s.IX++
|
||||||
|
},
|
||||||
|
// 0x24 : INC IXH (Undocumented)
|
||||||
|
0x24: func(s *Z80Type) {
|
||||||
|
s.IX = (uint16(s.doInc(byte(s.IX>>8))) << 8) | (s.IX & 0x00ff)
|
||||||
|
},
|
||||||
|
// 0x25 : DEC IXH (Undocumented)
|
||||||
|
0x25: func(s *Z80Type) {
|
||||||
|
s.IX = (uint16(s.doDec(byte(s.IX>>8))) << 8) | (s.IX & 0x00ff)
|
||||||
|
},
|
||||||
|
// 0x26 : LD IXH, n (Undocumented)
|
||||||
|
0x26: func(s *Z80Type) {
|
||||||
|
s.PC++
|
||||||
|
s.IX = (uint16(s.core.MemRead(s.PC)) << 8) | (s.IX & 0x00ff)
|
||||||
|
},
|
||||||
|
// 0x29 : ADD IX, IX
|
||||||
|
0x29: func(s *Z80Type) {
|
||||||
|
s.doIxAdd(s.IX)
|
||||||
|
},
|
||||||
|
// 0x2a : LD IX, (nn)
|
||||||
|
0x2A: func(s *Z80Type) {
|
||||||
|
addr := s.getAddr()
|
||||||
|
s.IX = (uint16(s.core.MemRead(addr)) << 8) | uint16(s.core.MemRead(addr+1))
|
||||||
|
},
|
||||||
|
// 0x2b : DEC IX
|
||||||
|
0x2B: func(s *Z80Type) {
|
||||||
|
s.IX--
|
||||||
|
},
|
||||||
|
// 0x2c : INC IXL (Undocumented)
|
||||||
|
0x2C: func(s *Z80Type) {
|
||||||
|
s.IX = (uint16(s.doInc(byte(s.IX & 0x00ff)))) | (s.IX & 0xff00)
|
||||||
|
},
|
||||||
|
// 0x2d : DEC IXL (Undocumented)
|
||||||
|
0x2D: func(s *Z80Type) {
|
||||||
|
s.IX = (uint16(s.doDec(byte(s.IX & 0x00ff)))) | (s.IX & 0xff00)
|
||||||
|
},
|
||||||
|
// 0x2e : LD IXL, n (Undocumented)
|
||||||
|
0x2E: func(s *Z80Type) {
|
||||||
|
s.PC++
|
||||||
|
s.IX = (uint16(s.core.MemRead(s.PC))) | (s.IX & 0xff00)
|
||||||
|
},
|
||||||
|
// 0x34 : INC (IX+n)
|
||||||
|
0x34: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
value := s.core.MemRead(offset)
|
||||||
|
s.core.MemWrite(offset, s.doInc(value))
|
||||||
|
},
|
||||||
|
// 0x35 : DEC (IX+n)
|
||||||
|
0x35: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
value := s.core.MemRead(offset)
|
||||||
|
s.core.MemWrite(offset, s.doDec(value))
|
||||||
|
},
|
||||||
|
// 0x36 : LD (IX+n), n
|
||||||
|
0x36: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
s.PC++
|
||||||
|
s.core.MemWrite(offset, s.core.MemRead(s.PC))
|
||||||
|
},
|
||||||
|
// 0x39 : ADD IX, SP
|
||||||
|
0x39: func(s *Z80Type) {
|
||||||
|
s.doIxAdd(s.SP)
|
||||||
|
},
|
||||||
|
// 0x44 : LD B, IXH (Undocumented)
|
||||||
|
0x44: func(s *Z80Type) {
|
||||||
|
s.B = byte(s.IX >> 8)
|
||||||
|
},
|
||||||
|
// 0x45 : LD B, IXL (Undocumented)
|
||||||
|
0x45: func(s *Z80Type) {
|
||||||
|
s.B = byte(s.IX & 0x00ff)
|
||||||
|
},
|
||||||
|
// 0x46 : LD B, (IX+n)
|
||||||
|
0x46: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
s.B = s.core.MemRead(offset)
|
||||||
|
},
|
||||||
|
// 0x4c : LD C, IXH (Undocumented)
|
||||||
|
0x4C: func(s *Z80Type) {
|
||||||
|
s.C = byte(s.IX >> 8)
|
||||||
|
},
|
||||||
|
// 0x4d : LD C, IXL (Undocumented)
|
||||||
|
0x4D: func(s *Z80Type) {
|
||||||
|
s.C = byte(s.IX & 0x00ff)
|
||||||
|
},
|
||||||
|
// 0x4e : LD C, (IX+n)
|
||||||
|
0x4E: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
s.C = s.core.MemRead(offset)
|
||||||
|
},
|
||||||
|
// 0x54 : LD D, IXH (Undocumented)
|
||||||
|
0x54: func(s *Z80Type) {
|
||||||
|
s.D = byte(s.IX >> 8)
|
||||||
|
},
|
||||||
|
// 0x55 : LD D, IXL (Undocumented)
|
||||||
|
0x55: func(s *Z80Type) {
|
||||||
|
s.D = byte(s.IX & 0x00ff)
|
||||||
|
},
|
||||||
|
// 0x56 : LD D, (IX+n)
|
||||||
|
0x56: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
s.D = s.core.MemRead(offset)
|
||||||
|
},
|
||||||
|
// 0x5d : LD E, IXL (Undocumented)
|
||||||
|
0x5D: func(s *Z80Type) {
|
||||||
|
s.E = byte(s.IX & 0x00ff)
|
||||||
|
},
|
||||||
|
// 0x5e : LD E, (IX+n)
|
||||||
|
0x5E: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
s.E = s.core.MemRead(offset)
|
||||||
|
},
|
||||||
|
// 0x60 : LD IXH, B (Undocumented)
|
||||||
|
0x60: func(s *Z80Type) {
|
||||||
|
s.IX = uint16(s.B)<<8 | s.IX&0x00ff
|
||||||
|
},
|
||||||
|
// 0x61 : LD IXH, C (Undocumented)
|
||||||
|
0x61: func(s *Z80Type) {
|
||||||
|
s.IX = uint16(s.C)<<8 | s.IX&0x00ff
|
||||||
|
},
|
||||||
|
// 0x62 : LD IXH, D (Undocumented)
|
||||||
|
0x62: func(s *Z80Type) {
|
||||||
|
s.IX = uint16(s.D)<<8 | s.IX&0x00ff
|
||||||
|
},
|
||||||
|
// 0x63 : LD IXH, E (Undocumented)
|
||||||
|
0x63: func(s *Z80Type) {
|
||||||
|
s.IX = uint16(s.E)<<8 | s.IX&0x00ff
|
||||||
|
},
|
||||||
|
// 0x64 : LD IXH, IXH (Undocumented)
|
||||||
|
0x64: func(s *Z80Type) {
|
||||||
|
// NOP
|
||||||
|
},
|
||||||
|
// 0x65 : LD IXH, IXL (Undocumented)
|
||||||
|
0x65: func(s *Z80Type) {
|
||||||
|
s.IX = (s.IX << 8) | (s.IX & 0x00ff)
|
||||||
|
},
|
||||||
|
// 0x66 : LD H, (IX+n)
|
||||||
|
0x66: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
s.H = s.core.MemRead(offset)
|
||||||
|
},
|
||||||
|
// 0x67 : LD IXH, A (Undocumented)
|
||||||
|
0x67: func(s *Z80Type) {
|
||||||
|
s.IX = (uint16(s.A) << 8) | (s.IX & 0x00ff)
|
||||||
|
},
|
||||||
|
// 0x68 : LD IXL, B (Undocumented)
|
||||||
|
0x68: func(s *Z80Type) {
|
||||||
|
s.IX = (s.IX & 0xff00) | uint16(s.B)
|
||||||
|
},
|
||||||
|
// 0x69 : LD IXL, C (Undocumented)
|
||||||
|
0x69: func(s *Z80Type) {
|
||||||
|
s.IX = (s.IX & 0xff00) | uint16(s.C)
|
||||||
|
},
|
||||||
|
// 0x6a : LD IXL, D (Undocumented)
|
||||||
|
0x6a: func(s *Z80Type) {
|
||||||
|
s.IX = (s.IX & 0xff00) | uint16(s.D)
|
||||||
|
},
|
||||||
|
// 0x6b : LD IXL, E (Undocumented)
|
||||||
|
0x6b: func(s *Z80Type) {
|
||||||
|
s.IX = (s.IX & 0xff00) | uint16(s.E)
|
||||||
|
},
|
||||||
|
// 0x6c : LD IXL, IXH (Undocumented)
|
||||||
|
0x6c: func(s *Z80Type) {
|
||||||
|
s.IX = (s.IX >> 8) | (s.IX & 0xff00)
|
||||||
|
},
|
||||||
|
// 0x6d : LD IXL, IXL (Undocumented)
|
||||||
|
0x6d: func(s *Z80Type) {
|
||||||
|
// NOP
|
||||||
|
},
|
||||||
|
// 0x6e : LD L, (IX+n)
|
||||||
|
0x6e: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
s.L = s.core.MemRead(offset)
|
||||||
|
},
|
||||||
|
// 0x6f : LD IXL, A (Undocumented)
|
||||||
|
0x6f: func(s *Z80Type) {
|
||||||
|
s.IX = uint16(s.A) | (s.IX & 0xff00)
|
||||||
|
},
|
||||||
|
// 0x70 : LD (IX+n), B
|
||||||
|
0x70: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
s.core.MemWrite(offset, s.B)
|
||||||
|
},
|
||||||
|
// 0x71 : LD (IX+n), C
|
||||||
|
0x71: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
s.core.MemWrite(offset, s.C)
|
||||||
|
},
|
||||||
|
// 0x72 : LD (IX+n), D
|
||||||
|
0x72: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
s.core.MemWrite(offset, s.D)
|
||||||
|
},
|
||||||
|
// 0x73 : LD (IX+n), E
|
||||||
|
0x73: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
s.core.MemWrite(offset, s.E)
|
||||||
|
},
|
||||||
|
// 0x74 : LD (IX+n), H
|
||||||
|
0x74: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
s.core.MemWrite(offset, s.H)
|
||||||
|
},
|
||||||
|
// 0x75 : LD (IX+n), L
|
||||||
|
0x75: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
s.core.MemWrite(offset, s.L)
|
||||||
|
},
|
||||||
|
// 0x77 : LD (IX+n), A
|
||||||
|
0x77: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
s.core.MemWrite(offset, s.A)
|
||||||
|
},
|
||||||
|
// 0x7c : LD A, IXH (Undocumented)
|
||||||
|
0x7C: func(s *Z80Type) {
|
||||||
|
s.A = byte(s.IX >> 8)
|
||||||
|
},
|
||||||
|
// 0x7d : LD A, IXL (Undocumented)
|
||||||
|
0x7D: func(s *Z80Type) {
|
||||||
|
s.A = byte(s.IX & 0x00ff)
|
||||||
|
},
|
||||||
|
// 0x7e : LD A, (IX+n)
|
||||||
|
0x7E: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
s.A = s.core.MemRead(offset)
|
||||||
|
},
|
||||||
|
// 0x84 : ADD A, IXH (Undocumented)
|
||||||
|
0x84: func(s *Z80Type) {
|
||||||
|
s.doAdd(byte(s.IX >> 8))
|
||||||
|
},
|
||||||
|
// 0x85 : ADD A, IXL (Undocumented)
|
||||||
|
0x85: func(s *Z80Type) {
|
||||||
|
s.doAdd(byte(s.IX & 0x00ff))
|
||||||
|
},
|
||||||
|
// 0x86 : ADD A, (IX+n)
|
||||||
|
0x86: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
s.doAdd(s.core.MemRead(offset))
|
||||||
|
},
|
||||||
|
// 0x8c : ADC A, IXH (Undocumented)
|
||||||
|
0x8C: func(s *Z80Type) {
|
||||||
|
s.doAdc(byte(s.IX >> 8))
|
||||||
|
},
|
||||||
|
// 0x8d : ADC A, IXL (Undocumented)
|
||||||
|
0x8D: func(s *Z80Type) {
|
||||||
|
s.doAdc(byte(s.IX & 0x00ff))
|
||||||
|
},
|
||||||
|
// 0x8e : ADC A, (IX+n)
|
||||||
|
0x8E: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
s.doAdc(s.core.MemRead(offset))
|
||||||
|
},
|
||||||
|
// 0x94 : SUB IXH (Undocumented)
|
||||||
|
0x94: func(s *Z80Type) {
|
||||||
|
s.doSub(byte(s.IX >> 8))
|
||||||
|
},
|
||||||
|
// 0x95 : SUB IXL (Undocumented)
|
||||||
|
0x95: func(s *Z80Type) {
|
||||||
|
s.doSub(byte(s.IX & 0x00ff))
|
||||||
|
},
|
||||||
|
// 0x96 : SUB A, (IX+n)
|
||||||
|
0x96: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
s.doSub(s.core.MemRead(offset))
|
||||||
|
},
|
||||||
|
// 0x9c : SBC IXH (Undocumented)
|
||||||
|
0x9C: func(s *Z80Type) {
|
||||||
|
s.doSbc(byte(s.IX >> 8))
|
||||||
|
},
|
||||||
|
// 0x9d : SBC IXL (Undocumented)
|
||||||
|
0x9D: func(s *Z80Type) {
|
||||||
|
s.doSbc(byte(s.IX & 0x00ff))
|
||||||
|
},
|
||||||
|
// 0x9e : SBC A, (IX+n)
|
||||||
|
0x9E: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
s.doSbc(s.core.MemRead(offset))
|
||||||
|
},
|
||||||
|
// 0xa4 : AND IXH (Undocumented)
|
||||||
|
0xA4: func(s *Z80Type) {
|
||||||
|
s.doAnd(byte(s.IX >> 8))
|
||||||
|
},
|
||||||
|
// 0xa5 : AND IXL (Undocumented)
|
||||||
|
0xA5: func(s *Z80Type) {
|
||||||
|
s.doAnd(byte(s.IX & 0x00ff))
|
||||||
|
},
|
||||||
|
// 0xa6 : AND A, (IX+n)
|
||||||
|
0xA6: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
s.doAnd(s.core.MemRead(offset))
|
||||||
|
},
|
||||||
|
// 0xac : XOR IXH (Undocumented)
|
||||||
|
0xAC: func(s *Z80Type) {
|
||||||
|
s.doXor(byte(s.IX >> 8))
|
||||||
|
},
|
||||||
|
// 0xad : XOR IXL (Undocumented)
|
||||||
|
0xAD: func(s *Z80Type) {
|
||||||
|
s.doXor(byte(s.IX & 0x00ff))
|
||||||
|
},
|
||||||
|
// 0xae : XOR A, (IX+n)
|
||||||
|
0xAE: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
s.doXor(s.core.MemRead(offset))
|
||||||
|
},
|
||||||
|
// 0xb4 : OR IXH (Undocumented)
|
||||||
|
0xB4: func(s *Z80Type) {
|
||||||
|
s.doOr(byte(s.IX >> 8))
|
||||||
|
},
|
||||||
|
// 0xb5 : OR IXL (Undocumented)
|
||||||
|
0xB5: func(s *Z80Type) {
|
||||||
|
s.doOr(byte(s.IX & 0x00ff))
|
||||||
|
},
|
||||||
|
// 0xb6 : OR A, (IX+n)
|
||||||
|
0xB6: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
s.doOr(s.core.MemRead(offset))
|
||||||
|
},
|
||||||
|
// 0xbc : CP IXH (Undocumented)
|
||||||
|
0xBC: func(s *Z80Type) {
|
||||||
|
s.doCp(byte(s.IX >> 8))
|
||||||
|
},
|
||||||
|
// 0xbd : CP IXL (Undocumented)
|
||||||
|
0xBD: func(s *Z80Type) {
|
||||||
|
s.doCp(byte(s.IX & 0x00ff))
|
||||||
|
},
|
||||||
|
// 0xbe : CP A, (IX+n)
|
||||||
|
0xBE: func(s *Z80Type) {
|
||||||
|
offset := s.getOffset(s.IX)
|
||||||
|
s.doCp(s.core.MemRead(offset))
|
||||||
|
},
|
||||||
|
// 0xcb : CB Prefix (IX bit instructions)
|
||||||
|
0xCB: func(s *Z80Type) {
|
||||||
|
s.opcodeDDCB()
|
||||||
|
},
|
||||||
|
// 0xe1 : POP IX
|
||||||
|
0xE1: func(s *Z80Type) {
|
||||||
|
s.IX = s.PopWord()
|
||||||
|
},
|
||||||
|
// 0xe3 : EX (SP), IX
|
||||||
|
0xE3: func(s *Z80Type) {
|
||||||
|
temp := s.IX
|
||||||
|
s.IX = uint16(s.core.MemRead(s.SP))
|
||||||
|
s.IX |= uint16(s.core.MemRead(s.SP+1)) << 8
|
||||||
|
s.core.MemWrite(s.SP, byte(temp&0x00ff))
|
||||||
|
s.core.MemWrite(s.SP+1, byte(temp>>8))
|
||||||
|
},
|
||||||
|
// 0xe5 : PUSH IX
|
||||||
|
0xE5: func(s *Z80Type) {
|
||||||
|
s.pushWord(s.IX)
|
||||||
|
},
|
||||||
|
// 0xe9 : JP (IX)
|
||||||
|
0xE9: func(s *Z80Type) {
|
||||||
|
s.PC = s.IX - 1
|
||||||
|
},
|
||||||
|
// 0xf9 : LD SP, IX
|
||||||
|
0xf9: func(s *Z80Type) {
|
||||||
|
s.SP = s.IX
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================
|
||||||
|
func (z *Z80Type) getOffset(reg uint16) uint16 {
|
||||||
|
z.PC++
|
||||||
|
offset := z.core.MemRead(z.PC)
|
||||||
|
if offset < 0 {
|
||||||
|
reg -= uint16(-offset)
|
||||||
|
} else {
|
||||||
|
reg += uint16(offset)
|
||||||
|
}
|
||||||
|
return reg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *Z80Type) opcodeDD() {
|
||||||
|
z.R = (z.R & 0x80) | (((z.R & 0x7f) + 1) & 0x7f)
|
||||||
|
z.PC++
|
||||||
|
opcode := z.core.M1MemRead(z.PC)
|
||||||
|
|
||||||
|
fun := ddInstructions[opcode]
|
||||||
|
if fun != nil {
|
||||||
|
//func = func.bind(this);
|
||||||
|
fun(z)
|
||||||
|
z.CycleCounter += CycleCountsDd[opcode]
|
||||||
|
} else {
|
||||||
|
// Apparently if a DD opcode doesn't exist,
|
||||||
|
// it gets treated as an unprefixed opcode.
|
||||||
|
// What we'll do to handle that is just back up the
|
||||||
|
// program counter, so that this byte gets decoded
|
||||||
|
// as a normal instruction.
|
||||||
|
z.PC--
|
||||||
|
// And we'll add in the cycle count for a NOP.
|
||||||
|
z.CycleCounter += CycleCounts[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *Z80Type) opcodeDDCB() {
|
||||||
|
offset := z.getOffset(z.IX)
|
||||||
|
z.PC++
|
||||||
|
|
||||||
|
opcode := z.core.M1MemRead(z.PC)
|
||||||
|
value := byte(0)
|
||||||
|
bitTestOp := false
|
||||||
|
// As with the "normal" CB prefix, we implement the DDCB prefix
|
||||||
|
// by decoding the opcode directly, rather than using a table.
|
||||||
|
if opcode < 0x40 {
|
||||||
|
// Shift and rotate instructions.
|
||||||
|
|
||||||
|
ddcbFunctions := []OpShift{z.doRlc, z.doRrc, z.doRl, z.doRr, z.doSla, z.doSra, z.doSll, z.doSrl}
|
||||||
|
|
||||||
|
// Most of the opcodes in this range are not valid,
|
||||||
|
// so we map this opcode onto one of the ones that is.
|
||||||
|
|
||||||
|
fun := ddcbFunctions[(opcode&0x38)>>3]
|
||||||
|
value = fun(z.core.MemRead(offset))
|
||||||
|
z.core.MemWrite(offset, value)
|
||||||
|
} else {
|
||||||
|
bitNumber := (opcode & 0x38) >> 3
|
||||||
|
|
||||||
|
if opcode < 0x80 {
|
||||||
|
// BIT
|
||||||
|
bitTestOp = true
|
||||||
|
z.Flags.N = false
|
||||||
|
z.Flags.H = true
|
||||||
|
z.Flags.Z = z.core.MemRead(offset)&(1<<bitNumber) == 0
|
||||||
|
z.Flags.P = z.Flags.Z
|
||||||
|
z.Flags.S = (bitNumber == 7) && !z.Flags.Z
|
||||||
|
} else if opcode < 0xc0 {
|
||||||
|
// RES
|
||||||
|
value = z.core.MemRead(offset) & ^(1 << bitNumber)
|
||||||
|
z.core.MemWrite(offset, value)
|
||||||
|
} else {
|
||||||
|
// SET
|
||||||
|
value = z.core.MemRead(offset | (1 << bitNumber))
|
||||||
|
z.core.MemWrite(offset, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This implements the undocumented shift, RES, and SET opcodes,
|
||||||
|
// which write their result to memory and also to an 8080 register.
|
||||||
|
if !bitTestOp {
|
||||||
|
value := byte(1)
|
||||||
|
switch opcode & 0x07 {
|
||||||
|
case 0:
|
||||||
|
z.B = value
|
||||||
|
case 1:
|
||||||
|
z.C = value
|
||||||
|
case 2:
|
||||||
|
z.D = value
|
||||||
|
case 3:
|
||||||
|
z.E = value
|
||||||
|
case 4:
|
||||||
|
z.H = value
|
||||||
|
case 5:
|
||||||
|
z.L = value
|
||||||
|
// 6 is the documented opcode, which doesn't set a register.
|
||||||
|
case 7:
|
||||||
|
z.A = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
z.CycleCounter += CycleCountsCb[opcode] + 8
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user