{"id":15517310,"url":"https://github.com/skx/z80-cpm-scripting-interpreter","last_synced_at":"2026-03-19T02:46:14.353Z","repository":{"id":200817038,"uuid":"657315690","full_name":"skx/z80-cpm-scripting-interpreter","owner":"skx","description":"A trivial I/O language, with repl, written in z80 assembler to run under  CP/M.","archived":false,"fork":false,"pushed_at":"2023-10-17T17:39:30.000Z","size":83,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-04T05:27:33.999Z","etag":null,"topics":["cpm","io","repl","retro","scripting","scripting-language","z80"],"latest_commit_sha":null,"homepage":"","language":"Makefile","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/skx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-06-22T19:46:59.000Z","updated_at":"2023-12-09T20:01:44.000Z","dependencies_parsed_at":null,"dependency_job_id":"60abdfac-9716-478e-885c-37353ab024be","html_url":"https://github.com/skx/z80-cpm-scripting-interpreter","commit_stats":null,"previous_names":["skx/z80-cpm-scripting-interpreter"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/skx/z80-cpm-scripting-interpreter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skx%2Fz80-cpm-scripting-interpreter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skx%2Fz80-cpm-scripting-interpreter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skx%2Fz80-cpm-scripting-interpreter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skx%2Fz80-cpm-scripting-interpreter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/skx","download_url":"https://codeload.github.com/skx/z80-cpm-scripting-interpreter/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skx%2Fz80-cpm-scripting-interpreter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28565556,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-19T08:53:44.001Z","status":"ssl_error","status_checked_at":"2026-01-19T08:52:40.245Z","response_time":67,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpm","io","repl","retro","scripting","scripting-language","z80"],"created_at":"2024-10-02T10:12:27.923Z","updated_at":"2026-01-19T10:33:05.360Z","avatar_url":"https://github.com/skx.png","language":"Makefile","funding_links":[],"categories":[],"sub_categories":[],"readme":"# scripting\n\nThis is a trivial REPL-based \"scripting thing\", designed to run on Z80-based CP/M systems.\n\nThe REPL allows arbitrary I/O to ports, and RAM, along with string output and looping-support, but there is little else - for usefulness the currently-selected I/O port is displayed in the REPL prompt.\n\nAll-told the interpreter, when compiled as REPL, requires approximately 800 bytes.\n\n\n\n## Overview\n\nThis repository contains a simple REPL-based interpreter which will run upon a CP/M host.  Although there is a FORTH-like flavour to the idea of using simple tokens to define actions this is actually not a stack-based language at all.\n\nThere are three registers which are used, internally:\n\n* Any time a number is encountered it is moved into a scratch-area.\n  * This is notionally known as the accumulator register.\n* It is possible to move the accumulator value into the IO-register, U.\n  * This register contains the port-number that any I/O read, or write, operation is applied to.\n  * Not a great mnemonic, but close to both I\u0026O on the keyboard.\n* It is possible to move the accumulator value into the memory-register, M.\n  * This register specifies the RAM address of any memory read/write operations.\n* Finally you may move the contents of the accumulator into the K-register, which is used for operating loops.\n\nTLDR:\n\n* Numbers go to A-Register.\n* Port I/O is controlled by the U-register.\n* RAM read/write is controlled by the M-register.\n* Loops are controlled by the K-register.\n\n\n### Instructions\n\nThe following instructions are available:\n\n* `[0-9]`\n  * Build up a number, which is stored in the accumulator / A-register.\n* `[ \\t\\n]`\n  * Ignored.\n* `{..}`\n  * Looping construct, see below for details.\n* `u`\n  * Set the I/O-port to be the value of the accumulator.\n* `U`\n  * Set the accumulator to be the value of the I/O-port.\n* `_`\n  * Print the string wrapped by by \"_\" characters.\n* `c`\n  * Clear the number in the accumulator.  (i.e. Set to zero).\n* `g`\n  * Perform a CALL instruction to the currently selected RAM address in the M-register.\n* `h`\n  * HALT for the number of times specified in the accumulator.\n  * This is used for running delay operations.\n* `i`\n  * Read a byte of input, from the currently selected I/O port (U-register), and place it in the accumulator.\n* `k`\n  * Copy the contents of the accumulator (lower half) into the K-register which is used for loops.\n* `K`\n  * Copy the contents of the K-register to the accumulator.\n* `m`\n  * Write the contents of the accumulator to the M-register, which is used to specify the address to (r)ead and (w)rite from.\n* `M`\n  * Write the contents of the M-register to the accumulator.\n* `n`\n  * Write a newline character.\n* `o`\n  * Write the contents of the accumulator to the currently selected I/O port (held in the U-register).\n* `p`\n  * Print the value of the accumulator, as a four-digit hex number.\n* `P`\n  * Print the value of the lower half of the accumulator, as a two-digit hex number.\n* `r`\n  * Read the contents of the currently selected RAM address, held in the M-register), and save in the accumulator.\n  * Then increment the RAM address held in the M-register (so that repeats will read from incrementing addresses).\n* `q`\n  * Quit, if we're in REPL-mode.\n* `w`\n  * Write the contents of the accumulator (lower byte only) to the currently selected RAM address held in the M-register.\n  * Then increment the RAM address held in the M-register (so that repeats will write to incrementing addresses).\n* `x`\n  * Print the character whos ASCII code is stored in the accumulator.\n\n\n\n## Looping\n\nThe special K-register can be used to control how many times a loop will be carried out.\n\nLoops consist of code between `{` and `}` pairs.  For example the following program will show a countdown:\n\n```\n[00]\u003e10k{Kp}\n0009\n0008\n0007\n0006\n0005\n0004\n0003\n0002\n0001\n0000\n```\n\nFirst of all the value 10 is loaded into the accumulator, then copied into the K-register.  The body of the loop is then:\n\n```\nKp\n```\n\nK copies the contents of the loop-register back to the accumulator, which is then printed by the `p` command.\n\nAnother example would be to dump the first 16 bytes of RAM:\n\n```\n\u003e 0m 16k{rP _ _}\nC3 03 EA 00 00 C3 06 DC 00 00 00 00 00 00 00 00\n```\n\n* `0m`: Stores the address zero in the M-register\n* `16k`: Sets the K-register, our loop counter, to 16.\n* `{`: loop-start\n  * `r`: Read a byte into the accumulator from the address in the M-register, incrementing that register in the process.\n  * `P`: Print the contents of the accumulator as a 2-digit HEX number.\n  * `_ _`: Output a space, to split the output nicely.\n* `}`: loop-end\n\n\n### Sample \"Programs\"\n\nAlso note that I broke up the \"programs\" with whitespace to aid readability.  This still works, spaces, TABs, and newlines are skipped over by the interpreter.\n\nShow a greeting:\n\n```\n_Hello, world!_\n```\n\nStore the value 42 in the accumulator, and print it as a four-digit hex number:\n\n```\n42p\n```\n\nTo print the value as a byte, not a word use `P` rather than `p`:\n\n```\n42P\n```\n\nStore the value \"201\" (opcode for RET) at address 20000, and CALL it, this will execute RET which will return to our REPL:\n\n```\n20000m 201w 20000mg\n```\n\nJump to address 0x0000, which will exit back to the CP/M prompt:\n\n```\n0mg\n```\n\nStore the value 42 in the accumulator, then print that as if it were the ASCII code of a character (output \"`*`\"):\n\n```\n42x\n```\n\nConfigure the I/O port to be port 0x01, read a byte from it to the accumulator, then print that value:\n\n```\n1u i p\n```\n\nWrite the byte 32, then the byte 77, to the I/O port 3.\n\n```\n3u 32o 77o\n```\n\nTo be more explicit that last example could have been written as:\n\n* `3u32o77o`\n  * `3` - Write 3 to the accumulator.\n  * `u` - Set the I/O port to be used for (i)nput and (o)utput to be the value in the accumulator, i.e. 3.\n  * `32`- Write 32 to the accumulator.\n  * `o` - Output the byte in the accumulator (32) to the currently selected I/O port (3)\n  * `77`- Write 77 to the accumulator.\n  * `o` - Output the byte in the accumulator (77) to the currently selected I/O port (3)\n\n\n\n## Porting\n\nWe use the CP/M BIOS calls for simplicity, if you wished to port this code to a single-board Z80-based system, without CP/M, that would not be hard.\n\nAll the system-integration is contained within the file [bios.z80](bios.z80) so you could replace the functions appropriately:\n\n* Add your UART initialization to `bios_init`.\n* Update the code to read/write to the serial-console, or whatever, in the other I/O functions.\n\n\n\n## Inspiration\n\nhttps://blog.steve.fi/simple_toy_languages.html\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskx%2Fz80-cpm-scripting-interpreter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fskx%2Fz80-cpm-scripting-interpreter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskx%2Fz80-cpm-scripting-interpreter/lists"}