-
Notifications
You must be signed in to change notification settings - Fork 1
Home
For the ts library on npm visit here.
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 }
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
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
}
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",
}
This class is the main interface to the library, it has some utility methods to compile/lex/semantic check etc.
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
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[]
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.
The WASM object that wraps the compiled code, it doesn't expose functionalities but is used to create the Interpreter
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
}
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
}
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,
}
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
}
The last executed instruction, plus the current status of the interpreter
type Step = [instruction: InstructionLine, status: InterpreterStatus]
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[]
}
}
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,
}
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
}
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>
}