Skip to content
Specy edited this page Dec 24, 2022 · 8 revisions

Updated for version v2.3.2

For the ts library on npm visit here.

Interfaces & Types

CompilationResult

If the compilation was successful, it will return an Interpreter, otherwise it will return a list of SemanticErrors

type CompilationResult = 
    { ok: false, errors: SemanticError[] } | 
    { ok: true, interpreter: Interpreter }

Interrupt handler

The callback that will be called whenever an interrupt is triggered, you will have to return the valid result for the interrupt to be handled correctly

type InterruptResult = //this is the value that you will have to return, the type has to be the same as the current interrupt
    { type: "DisplayStringWithCRLF" } |
    { type: "DisplayStringWithoutCRLF" } |
    { type: "ReadKeyboardString", value: string } |
    { type: "DisplayNumber" } |
    { type: "ReadNumber", value: number } |
    { type: "ReadChar", value: string } |
    { type: "GetTime", value: number } |
    { type: "DisplayChar" } | 
    { type: "Terminate" }

type Interrupt = //this is the interrupt from the interpreter
    { type: "DisplayStringWithCRLF", value: string } |
    { type: "DisplayStringWithoutCRLF", value: string } |
    { type: "ReadKeyboardString" } |
    { type: "DisplayNumber", value: number } |
    { type: "ReadNumber" } |
    { type: "ReadChar" } |
    { type: "GetTime" } |
    { type: "Terminate" } | 
    { type: "DisplayChar", value: string }

//handler
type InterruptHandler = (interrupt: Interrupt) => Promise<InterruptResult> | void

InterpreterOptions

type InterpreterOptions = {
    keep_history: boolean //if true, the interpreter will keep a history of the executed instructions to execute the undo
    history_size: number   //the size of the history, if the history is full, the oldest change will be removed
}

Parsing

Those are the types for the compilation result and execution.

type ParsedLine = {
    line: string, //the original line
    line_index: number, //the index of the line in the file
    parsed: LexedLine //the parsed line
}

type RegisterOperand = //A single register
    { type: "Address", value: number } |
    {type: "Data", value: number}

type LexedLine = //The lexed instruction line
    {
        type: "Instruction"
        value: {
            name: string,
            operands: LexedOperand[],
            size: "Byte" | "Word" | "Long"
        }
    } | {
        type: "Label",
        value: {
            name: string
        }
    } | {
        type: "Directive",
        value: {
            args: string[]
        }
    } | {
        type: "Empty"
    } | {
        type: "Comment",
        value: {
            content: string
        }
    } | {
        type: "Unknown",
        value: {
            content: string
        }
    }

type LexedOperand = //The lexed operand
    {
        type: "Register",
        value: [type: LexedRegisterType, name: string]
    } | {
        type: "PreIndirect",
        value: LexedOperand
    } | {
        type: "Immediate"
        value: string
    } | {
        type: "PostIndirect",
        value: LexedOperand
    } | {
        type: "Absolute",
        value: string
    } | {
        type: "Label",
        value: string
    } | {
        type: "Other",
        value: string
    } | {
        type: "IndirectOrDisplacement",
        value: {
            offset: String,
            operand: LexedOperand
        }
    } | {
        type: "IndirectBaseDisplacement",
        value: {
            offset: String,
            operands: LexedOperand[]
        }
    }

enum Size { //Generic size used throughout the library
  Byte,
  Word,
  Long,
}

enum LexedRegisterType { //The type of the register
    LexedData = "Data",
    LexedAddress = "Address",
}

S68k

This class is the main interface to the library, it has some utility methods to compile/lex/semantic check etc.

Compilation

Given the m68k code and the size of the memory, it will return the CompilationResult, which, if successful, will contain the Interpreter. Optionally you can pass an InterpreterOptions object to configure the interpreter

static compile(code: string, memorySize: number, options?: InterpreterOptions): CompilationResult 

Semantic check

It will execute lex and semantic checking to verify if there are any errors in the code, returns a list of SemanticErrors

static semanticCheck(code: string): SemanticError[]

Lexing

It will execute the lexing on the given code, this does not check for semantic errors, it will return a list of tokens

static lex(code: string): ParsedLine[]

Alternatively you can lex a single line

static lexOne(line: string): ParsedLine

Instead of using the static methods, you can use the constructor to create a new S68k wrapper that will encapsulate the code and provide similar utility methods as the static ones.

CompiledProgram

The WASM object that wraps the compiled code, it doesn't expose functionalities but is used to create the Interpreter

Semantic Error

The error returned by the semantic check

class SemanticError {
    getMessage(): string //the error message
    getLineIndex(): number //the index of the line in the file
    getMessageWithLine(): string //the formatted error message with the line too
    getLine(): ParsedLine //the parsed line which caused the error
    getError(): string //the error message
}

Register

The register object, it has methods to get the value of the register in different sizes

class Register {
    getLong(): number //32 bits value of the register
    getWord(): number //16 bits value of the register
    getByte(): number //8 bits value of the register
}

Interpreter runtime classes

InterpreterStatus

Current status of the interpreter, if running it can continue execution, if interrupted it will wait for an answer, if terminated, either normally or with exception, it will not continue execution and throw an exception if you try to step or run

enum InterpreterStatus {
  Running,
  Interrupt,
  Terminated,
  TerminatedWithException,
}

InstructionLine

The compiled instruction line, contains info about the original line too.

type InstructionLine = {
    instruction: any //the compiled instruction
    address: number //the address of the instruction in memory
    parsed_line: ParsedLine //the original parsed line
}

Step

The last executed instruction, plus the current status of the interpreter

type Step = [instruction: InstructionLine, status: InterpreterStatus]

MutationOperation

The mutation operation that was caused by the last instruction

type MutationOperation = 
    {
        type: "WriteRegister",
        value: {
            register: RegisterOperand,
            old: number,
            size: Size
        }
    } | {
        type: "WriteMemory",
        value: {
            address: number,
            old: number,
            size: Size
        }
    } | {
        type: "WriteMemoryBytes",
        value: {
            address: number,
            old: number[]
        }
    }

Condition

An enum representing the condition codes of the ccr

enum Condition {
  True,
  False,
  High,
  LowOrSame,
  CarryClear,
  CarrySet,
  NotEqual,
  Equal,
  OverflowClear,
  OverflowSet,
  Plus,
  Minus,
  GreaterThanOrEqual,
  LessThan,
  GreaterThan,
  LessThanOrEqual,
}

Cpu

A snapshot of the cpu status, it contains the Registers and the ccr

class Cpu {
    //returns a list of all the values of the registers
    //first 8 are the data registers, next 8 are the address registers
    getRegistersValues(): number[] 

    //given the number of the register and the type, returns the register object
    getRegister(register: number, type: RegisterType): Register 

    //given the number of the register and the type, returns the value of the register
    getRegisterValue(register: number, type: RegisterType): number
}

Interpreter

The actual interpreter objecet that will execute the code, it holds the memory and Cpu state, handles Interrupts.

class Interpreter {
    //Use this to answer to the interrupt from the interpreter, 
    //it will not continue execution
    answerInterrupt(interruptResult: InterruptResult) 

    //Steps one instruction, returns the step object containing the 
    //last executed instruction and the current status
    //WARNING: if you are going to execute this function many times and ignore the result
    //it will run approximately 10x slower as it needs to create the js object for the 
    //return value, if you only need to step, use stepGetStatus()
    step(): Step 

    //steps and returns the status of the interpreter, faster than step()
    stepGetStatus(): InterpreterStatus
    
    //returns the last executed instruction 
    getLastInstruction(): InstructionLine

    //Runs the program until it is interrupted or terminated
    run(): InterpreterStatus

    //If the interpreter was set to allow for history, and there 
    //are operations to undo, it will undo the last instruction
    undo(): ExecutionStep 

    //Returns all the mutations that were caused by the last instruction
    getPreviousMutations(): MutationOperation[]

    //Given a condition, it tests whether the ccr flags satisfy it
    getConditionValue(condition: Condition): boolean 

    //Returns the current cpu snapshot
    getCpuSnapshot(): Cpu 

    //Returns the current interrupt, if present
    getCurrentInterrupt(): Interrupt | null 

    //Gets the program counter
    getPc(): number

    //Gets the stack pointer
    getSp(): number 

    //Returns an array of the ccr flags, where in order:
    //[X: Extend, N: Negative, Z: Zero, V: Overflow, C: Carry]
    getFlagsAsArray(): boolean[]

    //Returns the ccr flags as a bitfield number
    getFlagsAsBitfield(): number

    //Given a flag, it returns whether it is set or not
    getFlag(flag: Flags): boolean

    //Reads the memory at a certain address and length.
    //Will throw if the address is out of bounds
    readMemoryBytes(address: number, length: number): Uint8Array 

    //Returns the index of the last instruction that was executed
    getCurrentLineIndex(): number

    //Returns whether the interpreter can undo the last instruction
    canUndo(): boolean

    //Fetches the instruction at a certain address
    getInstructionAt(address: number): InstructionLine | null

    //Returns the current status of the interpreter
    getStatus(): InterpreterStatus

    //Given a register, and a size, it returns the value of the register
    getRegisterValue(register: RegisterOperand, size = Size.Long): number
    //Given a register, and a size, it sets the value of the register, this is not tracked as a mutation
    setRegisterValue(register: RegisterOperand, value: number, size = Size.Long)

    //Returns the next instruction to be executed
    getNextInstruction(): InstructionLine | null

    //Returns whether the interpreter has terminated execution, either normally or with exception
    hasTerminated(): boolean

    //Returns whether the interpreter has reached the end of the program
    hasReachedBottom(): boolean

    //Runs the program with an interrupt handler, it will call the handler when an interrupt
    //occurs, and will wait for the answer, once the code ends, the promise will resolve
    async runWithInterruptHandler(onInterrupt: InterruptHandler): Promise<InterpreterStatus>

    //Same as runWithInterruptHandler, but it will step one instruction at a time
    async stepWithInterruptHandler(onInterrupt: InterruptHandler): Promise<Step> 
}