GithubHelp home page GithubHelp logo

skx / z80-cpm-scripting-interpreter Goto Github PK

View Code? Open in Web Editor NEW
1.0 2.0 0.0 83 KB

A trivial I/O language, with repl, written in z80 assembler to run under CP/M.

Makefile 100.00%
cpm io repl retro scripting scripting-language z80

z80-cpm-scripting-interpreter's Introduction

scripting

This is a trivial REPL-based "scripting thing", designed to run on Z80-based CP/M systems.

The 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.

All-told the interpreter, when compiled as REPL, requires approximately 800 bytes.

Overview

This 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.

There are three registers which are used, internally:

  • Any time a number is encountered it is moved into a scratch-area.
    • This is notionally known as the accumulator register.
  • It is possible to move the accumulator value into the IO-register, U.
    • This register contains the port-number that any I/O read, or write, operation is applied to.
    • Not a great mnemonic, but close to both I&O on the keyboard.
  • It is possible to move the accumulator value into the memory-register, M.
    • This register specifies the RAM address of any memory read/write operations.
  • Finally you may move the contents of the accumulator into the K-register, which is used for operating loops.

TLDR:

  • Numbers go to A-Register.
  • Port I/O is controlled by the U-register.
  • RAM read/write is controlled by the M-register.
  • Loops are controlled by the K-register.

Instructions

The following instructions are available:

  • [0-9]
    • Build up a number, which is stored in the accumulator / A-register.
  • [ \t\n]
    • Ignored.
  • {..}
    • Looping construct, see below for details.
  • u
    • Set the I/O-port to be the value of the accumulator.
  • U
    • Set the accumulator to be the value of the I/O-port.
  • _
    • Print the string wrapped by by "_" characters.
  • c
    • Clear the number in the accumulator. (i.e. Set to zero).
  • g
    • Perform a CALL instruction to the currently selected RAM address in the M-register.
  • h
    • HALT for the number of times specified in the accumulator.
    • This is used for running delay operations.
  • i
    • Read a byte of input, from the currently selected I/O port (U-register), and place it in the accumulator.
  • k
    • Copy the contents of the accumulator (lower half) into the K-register which is used for loops.
  • K
    • Copy the contents of the K-register to the accumulator.
  • m
    • Write the contents of the accumulator to the M-register, which is used to specify the address to (r)ead and (w)rite from.
  • M
    • Write the contents of the M-register to the accumulator.
  • n
    • Write a newline character.
  • o
    • Write the contents of the accumulator to the currently selected I/O port (held in the U-register).
  • p
    • Print the value of the accumulator, as a four-digit hex number.
  • P
    • Print the value of the lower half of the accumulator, as a two-digit hex number.
  • r
    • Read the contents of the currently selected RAM address, held in the M-register), and save in the accumulator.
    • Then increment the RAM address held in the M-register (so that repeats will read from incrementing addresses).
  • q
    • Quit, if we're in REPL-mode.
  • w
    • Write the contents of the accumulator (lower byte only) to the currently selected RAM address held in the M-register.
    • Then increment the RAM address held in the M-register (so that repeats will write to incrementing addresses).
  • x
    • Print the character whos ASCII code is stored in the accumulator.

Looping

The special K-register can be used to control how many times a loop will be carried out.

Loops consist of code between { and } pairs. For example the following program will show a countdown:

[00]>10k{Kp}
0009
0008
0007
0006
0005
0004
0003
0002
0001
0000

First of all the value 10 is loaded into the accumulator, then copied into the K-register. The body of the loop is then:

Kp

K copies the contents of the loop-register back to the accumulator, which is then printed by the p command.

Another example would be to dump the first 16 bytes of RAM:

> 0m 16k{rP _ _}
C3 03 EA 00 00 C3 06 DC 00 00 00 00 00 00 00 00
  • 0m: Stores the address zero in the M-register
  • 16k: Sets the K-register, our loop counter, to 16.
  • {: loop-start
    • r: Read a byte into the accumulator from the address in the M-register, incrementing that register in the process.
    • P: Print the contents of the accumulator as a 2-digit HEX number.
    • _ _: Output a space, to split the output nicely.
  • }: loop-end

Sample "Programs"

Also 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.

Show a greeting:

_Hello, world!_

Store the value 42 in the accumulator, and print it as a four-digit hex number:

42p

To print the value as a byte, not a word use P rather than p:

42P

Store the value "201" (opcode for RET) at address 20000, and CALL it, this will execute RET which will return to our REPL:

20000m 201w 20000mg

Jump to address 0x0000, which will exit back to the CP/M prompt:

0mg

Store the value 42 in the accumulator, then print that as if it were the ASCII code of a character (output "*"):

42x

Configure the I/O port to be port 0x01, read a byte from it to the accumulator, then print that value:

1u i p

Write the byte 32, then the byte 77, to the I/O port 3.

3u 32o 77o

To be more explicit that last example could have been written as:

  • 3u32o77o
    • 3 - Write 3 to the accumulator.
    • 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.
    • 32- Write 32 to the accumulator.
    • o - Output the byte in the accumulator (32) to the currently selected I/O port (3)
    • 77- Write 77 to the accumulator.
    • o - Output the byte in the accumulator (77) to the currently selected I/O port (3)

Porting

We 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.

All the system-integration is contained within the file bios.z80 so you could replace the functions appropriately:

  • Add your UART initialization to bios_init.
  • Update the code to read/write to the serial-console, or whatever, in the other I/O functions.

Inspiration

https://blog.steve.fi/simple_toy_languages.html

z80-cpm-scripting-interpreter's People

Contributors

skx avatar

Stargazers

 avatar

Watchers

 avatar  avatar

z80-cpm-scripting-interpreter's Issues

Allow RAM read/write

Suggest we use "M" to set the memory-address, "R"/"W" to allow reading/writing and use J to jump to new code.

  • NOTE: This requires 16-bit temporary storage - #1

This would look like this:

3000M ; set the memory address to be 3000
c30W   ; write 30 to 3000
c40W   ; write 40 to 3001

c3000MJ  ; Jump to the code at address 3000

NOTE: We auto-increment the memory-address when (r)eading or (w)riting.

Save bytes via the IX register

Something like this:

  ld IX, a_register
  ld h, (IX+0)
  ld l, (IX+1)

Is shorter than the indirect use we use right now.

It might also be worth thinking about register usage more generally; currently we go through a dance to preserve the contents of the string to interpret - which we keep in hl - but we don't do anything for the other registers which get juggled back and forth..

Allow easy porting

We use the CP/M bios for:

  • Reading input into a buffer
  • Writing a single character to the console.
  • Writing a string to the console.

We should ensure we make it easy to swap those out, to ease porting to single-board computers that run standalone, without CP/M.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.