Skip to content

Commit

Permalink
Add support for Custom Dict, comma, emacs major mode, usage examples
Browse files Browse the repository at this point in the history
It allows finer grained control for setting individual actions eg:
- perform side-effects when 'M' register is updated or accessed.
- Normalizing register values to 02x and address to 04x etc

Add comma support in instructions for compatibility with standard
Add emacs major mode workflow.
Add extensive command usage examples.
  • Loading branch information
hemanta212 committed Feb 8, 2022
1 parent b63fae0 commit f9922ed
Show file tree
Hide file tree
Showing 6 changed files with 763 additions and 56 deletions.
82 changes: 64 additions & 18 deletions README.org
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
* 8085-Python
A simple exploratory interpreter with REPL experience.
A simple exploratory interpreter with REPL experience. (Beginning learning purpose only)

- For windows users, you can download and run the =.exe= file from [[https://github.com/hemanta212/releases/latest][releases page]].
- For windows users, you can download and run the =.exe= file from [[https://github.com/hemanta212/8085-simulator/releases/latest][releases page]].
- Clone and run this repository,
#+begin_src shell :eval never
git clone https://github.com/hemanta212/8085-simulator
Expand All @@ -12,6 +12,8 @@ A simple exploratory interpreter with REPL experience.

Type =help= and run the commands listed.

For detailed examples on available commands, see file [[file:usage_examples.org]].

** Table of contents
:PROPERTIES:
:TOC: :include siblings :depth 2
Expand All @@ -28,7 +30,9 @@ Type =help= and run the commands listed.
- [[#the-plainindirect-mode-option--i][The plain/indirect mode option (-i)]]
- [[#the-verbosity-logging-option--v][The verbosity logging option (-v)]]
- [[#using-from-terminal-vim-and-emacs][Using From Terminal, Vim and Emacs]]
- [[#example-emacs-config][Example Emacs config]]
- [[#example-emacs-org-babel-config][Example Emacs Org babel config]]
- [[#emacs-8085-major-mode][Emacs 8085 major mode]]
- [[#extensive-usage-examples][Extensive Usage Examples]]
:END:

** Features
Expand All @@ -47,8 +51,8 @@ Type =help= and run the commands listed.
- [ ] - Implement proper 8-bit 16-bit type system and validation
- [ ] - Implement a stacktrace in case of errors
- [ ] - Implement integration test and actions CI
- [ ] - A pypi package and pyinstaller executable
- [ ] - Write a major mode for emacs.
- [ ] - A pypi package
- [-] - Write a major mode for emacs.

** REPL Mode
#+begin_src shell :exports both :results output
Expand Down Expand Up @@ -135,6 +139,7 @@ In case of using multiple options, they need to be specified in order,
Providing options otherwise will result in an error.

** Example Repl Workflow
*NOTE* Fore more extensive examples for each commands, see file [[file:usage_examples.org]].

#+begin_src shell :eval never
>>> inspect
Expand All @@ -156,10 +161,9 @@ Registers:
M: 0x00

Memory:
0x0000: 0x33
0x0001: 0x9A
0x000A: 0x2B
0x000B: 0x34
0x1000: 0x2b
0x1001: 0x34
0x0000: 0x00

Flags:
carry: 0
Expand Down Expand Up @@ -238,10 +242,9 @@ Registers:
M: 0x00

Memory:
0x0000: 0x33
0x0001: 0x9A
0x000A: 0x2B
0x000B: 0x34
0x1000: 0x2b
0x1001: 0x34
0x0000: 0x00
0x3322: 0x0a

Flags:
Expand Down Expand Up @@ -322,10 +325,9 @@ Registers:
M: 0x00

Memory:
0x0000: 0x33
0x0001: 0x9A
0x000A: 0x2B
0x000B: 0x34
0x1000: 0x2b
0x1001: 0x34
0x0000: 0x00
0x5555: 0x05

Flags:
Expand Down Expand Up @@ -374,7 +376,7 @@ Either you can:
- Use combination of =-c= and =-db= option to emulate a repl session.
- Use combnation of =-i= and =-db= option to emulate a repl session.

*** Example Emacs config
*** Example Emacs Org babel config
With some configuration, the interpreter can be made to work with Emacs' Org Mode using the =org-babel-eval= function.
This uses =-i= command option to write to the interpreter.

Expand All @@ -396,12 +398,54 @@ Put this in your =init.el= file,
org-babel-8085-command
(if args (concat " -i " args) " -i " ))
body)))

;; Placeholder major mode, look below for more featured major mode
(define-derived-mode 8085-mode prog-mode "8085"
"Major mode for 8085."
(setq-local comment-start ";")
(setq-local comment-start-skip ";+[\t ]*"))
#+end_src

- The =path-to-8085= should be folder where you cloned this project.
- The =org-babel-8085-command= should be the command to run the interpreter (eg python main.py),
- You could use =(concat path-to-8085 "/.venv/bin/python")= in place of "=python=" if you use in-project virtual environments.

*** Emacs 8085 major mode
#+begin_src emacs-lisp :eval never
(require 'rx)
(defvar 8085-mode-map
(let ((map (make-sparse-keymap)))
map))

(defconst 8085--font-lock-defaults
(let (
(instructions '("MVI" "MOV" "ADD" "SUB" "ADI"
"SUI" "JNZ" "JNC" "JZ" "JC" "LXI"
"LXAD" "INR" "DCR" "INX" "DCX" "OUT"
"HLT" "CPI" "CMP" "STA" "LDA")))
`(((,(rx-to-string `(: (or ,@instructions))) 0 font-lock-keyword-face)
("\\([[:word:]]+\\):" 1 font-lock-function-name-face)))))

(defvar 8085-mode-syntax-table
(let ((st (make-syntax-table)))
;; - and _ are word constituents
(modify-syntax-entry ?_ "w" st)
(modify-syntax-entry ?- "w" st)

;; add comments. lua-mode does something similar, so it shouldn't
;; bee *too* wrong.
(modify-syntax-entry ?\; "<" st)
(modify-syntax-entry ?\n ">" st)
st))

(define-derived-mode 8085-mode prog-mode "8085"
"Major mode for 8085."
(setq font-lock-defaults 8085--font-lock-defaults)
(setq-local comment-start ";")
(setq-local comment-start-skip ";+[\t ]*")
(setq-local case-fold-search nil))
#+end_src

Save and restart your emacs (or execute each block with =C-x C-e=).
Then you can use org mode to write block like:

Expand All @@ -425,3 +469,5 @@ MVI B 80H
MVI B 80H
,#+end_src
#+end_example

** [[file:usage_examples.org][Extensive Usage Examples]]
32 changes: 20 additions & 12 deletions command_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def load_accumulator(self, args: tuple):
f"Address {address} is not in memory:",
f"Creating and init to 0",
)
self.state.memory[address] = hex(0x00)
self.state.memory[address] = f"0x{0:02x}"
self.state.accumulator = self.state.memory[address]
logger.debug(f"LOADED ACCUMULATOR: {self.state.accumulator}")
print(
Expand Down Expand Up @@ -224,9 +224,10 @@ def increment_register(self, args: tuple) -> None:
logger.debug(f"INR: {args}")
register = args[0]
register_value = self.state.registers[register]
increment_by = hex(0x1)
incremented_value = hex(int(register_value, 16) + int(increment_by, 16))
self.state.registers[register] = incremented_value
increment_by = f"0x{1:02x}"
incremented_int_value = int(register_value, 16) + int(increment_by, 16)
self.state.registers[register] = hex(abs(incremented_int_value))
incremented_value = self.state.registers[register]
logger.debug(f"Incremented: {register} to {incremented_value}")
print(
f"{register} -> {hex_to_simple(register_value)} + {hex_to_simple(increment_by)} -> {hex_to_simple(incremented_value)}"
Expand All @@ -239,15 +240,16 @@ def decrement_register(self, args: tuple) -> None:
logger.debug(f"DCR: {args}")
register = args[0]
register_value = self.state.registers[register]
decrement_by = hex(0x1)
decremented_value = hex(int(register_value, 16) - int(decrement_by, 16))
self.state.registers[register] = decremented_value
if int(decremented_value, 16) < 0:
decrement_by = f"0x{1:02x}" # eq to hex(0x01)
decremented_int_value = int(register_value, 16) - int(decrement_by, 16)
if decremented_int_value < 0:
self.change_state_flags(carry=True, sign=True, zero=False)
elif int(decremented_value, 16) > 0:
elif decremented_int_value > 0:
self.change_state_flags(carry=False, sign=False, zero=False)
elif int(decremented_value, 16) == 0:
elif decremented_int_value == 0:
self.change_state_flags(carry=False, sign=False, zero=True)
self.state.registers[register] = hex(abs(decremented_int_value))
decremented_value = self.state.registers[register]
logger.debug(f"Decremented: {register} to {decremented_value}")
print(
f"{register} -> {hex_to_simple(register_value)} - {hex_to_simple(decrement_by)} -> {hex_to_simple(decremented_value)}"
Expand All @@ -264,7 +266,7 @@ def increment_extended_register(self, args: tuple):
logger.debug(
f"Got mem addr stored by register pair {REG1}{REG2}: {register_addr}"
)
increment_by = hex(0x1)
increment_by = f"0x{1:02x}"
incremented_int_value = int(register_addr, 16) + int(increment_by, 16)
incremented_value = f"0x{incremented_int_value:04x}"
self.state.set_register_pair_value(incremented_value, register)
Expand All @@ -283,8 +285,13 @@ def decrement_extended_register(self, args: tuple) -> None:
logger.debug(
f"Got mem addr stored by register pair {REG1}{REG2}: {register_addr}"
)
decrement_by = hex(0x1)
decrement_by = f"0x{1:02x}"
decremented_int_value = int(register_addr, 16) - int(decrement_by, 16)
if decremented_int_value < 0:
logger.error(
f"Memory address '{register_addr}' gets negative when decremented"
)
return
decremented_value = f"0x{decremented_int_value:04x}"
self.state.set_register_pair_value(decremented_value, register)
hex1, hex2 = self.state.registers[REG1], self.state.registers[REG2]
Expand All @@ -297,6 +304,7 @@ def load_register_pair_immediate(self, args: tuple) -> None:
"""
logger.debug(f"LXI: {args}")
register, value = args[0], args[1]
value = f"0x{int(value, 16):04x}"
self.state.set_register_pair_value(value, register)
REG1, REG2 = REGISTER_PAIRS[register]
hex1, hex2 = self.state.registers[REG1], self.state.registers[REG2]
Expand Down
48 changes: 48 additions & 0 deletions custom_dictionaries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from collections import UserDict


class RegisterDict(UserDict):
def __init__(self, state, *args, **kwargs):
super().__init__(*args, **kwargs)
self.update(*args, **kwargs)
self.state = state

def __setitem__(self, key: str, value: str):
value = f"0x{int(value, 16):02x}"
if key == "M":
mem_addr = self.state.get_mem_addr_register_pair("H")
self.state.memory[mem_addr] = value
super().__setitem__(key, value)

def __getitem__(self, key: str):
if key == "M":
return self.state.get_register_pair_value("H")
return super().__getitem__(key)

def update(self, *args, **kwargs):
if len(args) > 1:
raise TypeError("update expected at most 1 arguments, got %d" % len(args))
other = dict(*args, **kwargs)
for key in other:
self[key] = other[key]


class MemoryDict(UserDict):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.update(*args, **kwargs)

def __setitem__(self, key: str, value: str):
key = f"0x{int(key, 16):04x}"
value = f"0x{int(value, 16):02x}"
super().__setitem__(key, value)

def __getitem__(self, key: str):
return super().__getitem__(key)

def update(self, *args, **kwargs):
if len(args) > 1:
raise TypeError("update expected at most 1 arguments, got %d" % len(args))
other = dict(*args, **kwargs)
for key in other:
self[key] = other[key]
3 changes: 3 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ def cmd_preprocessor(cmd: str, state: State) -> Optional[Command]:
"""
Convert tokens to types, Return a Command object.
"""
# Preproces commas to space
cmd = cmd.replace(",", " ")

cmd_list = tuple([item.strip() for item in cmd.split(" ") if item])
logger.debug(f"Splitted commands: {cmd_list}")

Expand Down
58 changes: 32 additions & 26 deletions state_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from loguru import logger

from data import REGISTER_PAIRS
from custom_dictionaries import RegisterDict, MemoryDict


class State:
Expand All @@ -16,24 +17,28 @@ class State:
"""

def __init__(self):
# Initialize state of registers with 0 hex values
self.registers: Dict[str, str] = {
"A": "0x00",
"B": "0x00",
"C": "0x00",
"D": "0x00",
"E": "0x00",
"H": "0x00",
"L": "0x00",
"M": "0x00",
}
# Initialize few memory locations to garbage values
self.memory: Dict[str, str] = {
"0x0000": "0x33",
"0x0001": "0x9A",
"0x000A": "0x2B",
"0x000B": "0x34",
}
self.memory: MemoryDict = MemoryDict()
self.memory.update(
{
"0x1000": "0x2B",
"0x1001": "0x34",
}
)
# Initialize state of registers with 0 hex values
self.registers: RegisterDict = RegisterDict(self)
self.registers.update(
{
"A": "0x00",
"B": "0x00",
"C": "0x00",
"D": "0x00",
"E": "0x00",
"H": "0x00",
"L": "0x00",
"M": "0x00",
}
)
# Initialize the flags
self.flags: Dict[str, bool] = {
"carry": False,
Expand Down Expand Up @@ -69,6 +74,9 @@ def get_register_pair_value(self, register: str) -> str:
return value_at_mem_addr

def set_register_pair_value(self, value: str, register: str) -> None:
"""
Takes a 16 bit value and splits it between register pairs
"""
REG1, REG2 = REGISTER_PAIRS[register]
# Distribute the 16-bit hex in value literally half half to H and L
# value -> 5533H --> '0x5533' -> [-2:] -> '0x55', '33' -> '0x' + '33'
Expand All @@ -82,12 +90,6 @@ def set_register_pair_value(self, value: str, register: str) -> None:
self.registers["M"] = self.get_register_pair_value("H")
logger.debug(f"Pair {REG1}{REG2} Loaded: {first_hex}, {second_hex}")

def set_M(self, value: str) -> None:
self.set_register_pair_value(value, register="H")

def get_M(self) -> str:
return self.get_register_pair_value("H")

@property
def accumulator(self) -> str:
"""
Expand Down Expand Up @@ -134,8 +136,10 @@ def restore(self, file_db: str) -> None:
with open(file_db, "r") as rf:
state_data = json.load(rf)

self.registers = state_data["registers"]
self.memory = state_data["memory"]
self.memory = MemoryDict()
self.memory.update(state_data["memory"])
self.registers = RegisterDict(self)
self.registers.update(state_data["registers"])

def save(self, file_db: str) -> None:
if not file_db:
Expand All @@ -144,6 +148,8 @@ def save(self, file_db: str) -> None:
with open(file_db, "w"):
pass

state_data = {"registers": self.registers, "memory": self.memory}
reg_data = {k: v for k, v in self.registers.items()}
mem_data = {k: v for k, v in self.memory.items()}
state_data = {"registers": reg_data, "memory": mem_data}
with open(file_db, "w") as wf:
json.dump(state_data, wf)
Loading

0 comments on commit f9922ed

Please sign in to comment.