Skip to content

Commit

Permalink
Updates from technical review of chapter 2 and minor updates based on…
Browse files Browse the repository at this point in the history
… development review of other chapters
  • Loading branch information
davecom committed Nov 4, 2024
1 parent 149e255 commit d8ae63c
Show file tree
Hide file tree
Showing 11 changed files with 87 additions and 58 deletions.
2 changes: 1 addition & 1 deletion KNN/knn.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def classify(self, k: int, data_point: DP) -> str:
neighbors = self.nearest(k, data_point)
return Counter(neighbor.kind for neighbor in neighbors).most_common(1)[0][0]

# Predict a numeric property of a data point based on the k nearest neighbors
# Predict a numeric property of a data point based on the k nearest neighbors
# Find the average of that property from the neighbors and return it
def predict(self, k: int, data_point: DP, property_name: str) -> float:
neighbors = self.nearest(k, data_point)
Expand Down
2 changes: 1 addition & 1 deletion NESEmulator/ppu.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def __init__(self, rom: ROM):
# PPU Memory
self.spr = array('B', [0] * SPR_RAM_SIZE) # sprite ram
self.nametables = array('B', [0] * NAMETABLE_SIZE) # nametable ram
self.palette = array('B', [0] * PALETTE_SIZE) # pallete ram
self.palette = array('B', [0] * PALETTE_SIZE) # palette ram
# Registers
self.addr = 0 # main PPU address register
self.addr_write_latch = False
Expand Down
2 changes: 1 addition & 1 deletion NESEmulator/rom.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def __init__(self, filename: str):
print("Invalid ROM Header Signature")
else:
print("Valid ROM Header Signature")
# Untangle Mapper - one nybble in flags6 and one nybble in flags7
# Untangle Mapper - one nibble in flags6 and one nibble in flags7
self.mapper = (self.header.flags7 & 0xF0) | (
(self.header.flags6 & 0xF0) >> 4)
print(f"Mapper {self.mapper}")
Expand Down
43 changes: 43 additions & 0 deletions NanoBASIC/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# NanoBASIC/errors.py
# From Fun Computer Science Projects in Python
# Copyright 2024 David Kopec
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# DESCRIPTION
# The errors module contains the custom exceptions used in the NanoBASIC
# interpreter including ParserError and InterpreterError.
from NanoBASIC.tokenizer import Token
from NanoBASIC.nodes import Node


class NanoBASICError(Exception):
def __init__(self, message: str, line_num: int, column: int):
super().__init__(message)
self.message = message
self.line_num = line_num
self.column = column

def __str__(self):
return (f"{self.message} Occurred at line {self.line_num} "
f"and column {self.column}")


class ParserError(NanoBASICError):
def __init__(self, message: str, token: Token):
super().__init__(message, token.line_num, token.col_start)


class InterpreterError(NanoBASICError):
def __init__(self, message: str, node: Node):
super().__init__(message, node.line_num, node.col_start)
41 changes: 16 additions & 25 deletions NanoBASIC/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,11 @@
# just an array of statement nodes; each node is something of a tree in itself. The interpreter
# translates the meaning of each statement node into Python code that can be run "live."
from NanoBASIC.nodes import *
from NanoBASIC.errors import InterpreterError
from collections import deque


class Interpreter:
class InterpreterError(Exception):
def __init__(self, message: str, node: Node):
self.message = message
self.line_num = node.line_num
self.column = node.col_start

def __str__(self):
return (f"{self.message} Occurred at line {self.line_num} "
f"and column {self.column}")

def __init__(self, statements: list[Statement]):
self.statements = statements
self.variable_table: dict[str, int] = {}
Expand Down Expand Up @@ -75,17 +66,17 @@ def interpret(self, statement: Statement):
if (line_index := self.find_line_index(go_to_line_id)) is not None:
self.statement_index = line_index
else:
raise Interpreter.InterpreterError("No GOTO line id.", self.current)
raise InterpreterError("No GOTO line id.", self.current)
case GoSubStatement(line_expr=line_expr):
go_sub_line_id = self.evaluate_numeric(line_expr)
if (line_index := self.find_line_index(go_sub_line_id)) is not None:
self.subroutine_stack.append(self.statement_index + 1) # Setup for RETURN
self.statement_index = line_index
else:
raise Interpreter.InterpreterError("No GOSUB line id.", self.current)
raise InterpreterError("No GOSUB line id.", self.current)
case ReturnStatement():
if not self.subroutine_stack: # Check if the stack is empty
raise Interpreter.InterpreterError("RETURN without GOSUB.", self.current)
raise InterpreterError("RETURN without GOSUB.", self.current)
self.statement_index = self.subroutine_stack.pop()
case PrintStatement(printables=printables):
accumulated_string: str = ""
Expand All @@ -104,8 +95,8 @@ def interpret(self, statement: Statement):
else:
self.statement_index += 1
case _:
raise Interpreter.InterpreterError(f"Unexpected item {self.current} "
f"in statement list.", self.current)
raise InterpreterError(f"Unexpected item {self.current} "
f"in statement list.", self.current)

def evaluate_numeric(self, numeric_expression: NumericExpression) -> int:
match numeric_expression:
Expand All @@ -115,14 +106,14 @@ def evaluate_numeric(self, numeric_expression: NumericExpression) -> int:
if name in self.variable_table:
return self.variable_table[name]
else:
raise Interpreter.InterpreterError(f"Var {name} used "
f"before initialized.", numeric_expression)
raise InterpreterError(f"Var {name} used "
f"before initialized.", numeric_expression)
case UnaryOperation(operator=operator, expr=expr):
if operator is TokenType.MINUS:
return -self.evaluate_numeric(expr)
else:
raise Interpreter.InterpreterError(f"Expected - "
f"but got {operator}.", numeric_expression)
raise InterpreterError(f"Expected - "
f"but got {operator}.", numeric_expression)
case BinaryOperation(operator=operator, left_expr=left, right_expr=right):
if operator is TokenType.PLUS:
return self.evaluate_numeric(left) + self.evaluate_numeric(right)
Expand All @@ -133,11 +124,11 @@ def evaluate_numeric(self, numeric_expression: NumericExpression) -> int:
elif operator is TokenType.DIVIDE:
return self.evaluate_numeric(left) // self.evaluate_numeric(right)
else:
raise Interpreter.InterpreterError(f"Unexpected binary operator "
f"{operator}.", numeric_expression)
raise InterpreterError(f"Unexpected binary operator "
f"{operator}.", numeric_expression)
case _:
raise Interpreter.InterpreterError("Expected numeric expression.",
numeric_expression)
raise InterpreterError("Expected numeric expression.",
numeric_expression)

def evaluate_boolean(self, boolean_expression: BooleanExpression) -> bool:
left = self.evaluate_numeric(boolean_expression.left_expr)
Expand All @@ -156,5 +147,5 @@ def evaluate_boolean(self, boolean_expression: BooleanExpression) -> bool:
case TokenType.NOT_EQUAL:
return left != right
case _:
raise Interpreter.InterpreterError(f"Unexpected boolean operator "
f"{boolean_expression.operator}.", boolean_expression)
raise InterpreterError(f"Unexpected boolean operator "
f"{boolean_expression.operator}.", boolean_expression)
4 changes: 2 additions & 2 deletions NanoBASIC/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class NumericExpression(Node):
pass


# A numeric expression with two operands like +, -, *, and /
# A numeric expression with two operands like 2 + 2 or 8 / 4
@dataclass(frozen=True)
class BinaryOperation(NumericExpression):
operator: TokenType
Expand All @@ -58,7 +58,7 @@ def __repr__(self) -> str:
return f"{self.left_expr} {self.operator} {self.right_expr}"


# A numeric expression with one operand like -
# A numeric expression with one operand like -4
@dataclass(frozen=True)
class UnaryOperation(NumericExpression):
operator: TokenType
Expand Down
39 changes: 15 additions & 24 deletions NanoBASIC/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,10 @@
from NanoBASIC.tokenizer import Token
from typing import cast
from NanoBASIC.nodes import *
from NanoBASIC.errors import ParserError


class Parser:
class ParserError(Exception):
def __init__(self, message: str, token: Token):
self.message = message
self.line_num = token.line_num
self.column = token.col_start

def __str__(self):
return (f"{self.message} Occurred at line {self.line_num} "
f"and column {self.column}")

def __init__(self, tokens: list[Token]):
self.tokens = tokens
self.token_index: int = 0
Expand All @@ -53,8 +44,8 @@ def out_of_tokens(self) -> bool:
@property
def current(self) -> Token:
if self.out_of_tokens:
raise (Parser.ParserError(f"No tokens after "
f"{self.previous.kind}", self.previous))
raise (ParserError(f"No tokens after "
f"{self.previous.kind}", self.previous))
return self.tokens[self.token_index]

@property
Expand All @@ -65,8 +56,8 @@ def consume(self, kind: TokenType) -> Token:
if self.current.kind is kind:
self.token_index += 1
return self.previous
raise Parser.ParserError(f"Expected {kind} after {self.previous}"
f"but got {self.current}.", self.current)
raise ParserError(f"Expected {kind} after {self.previous}"
f"but got {self.current}.", self.current)

def parse(self) -> list[Statement]:
statements: list[Statement] = []
Expand All @@ -93,8 +84,8 @@ def parse_statement(self, line_id: int) -> Statement:
return self.parse_gosub(line_id)
case TokenType.RETURN_T:
return self.parse_return(line_id)
raise Parser.ParserError("Expected to find start of statement.",
self.current)
raise ParserError("Expected to find start of statement.",
self.current)

# PRINT "COMMA",SEPARATED,7154
def parse_print(self, line_id: int) -> PrintStatement:
Expand All @@ -110,8 +101,8 @@ def parse_print(self, line_id: int) -> PrintStatement:
printables.append(expression)
last_col = expression.col_end
else:
raise Parser.ParserError("Only strings and numeric expressions "
"allowed in print list.", self.current)
raise ParserError("Only strings and numeric expressions "
"allowed in print list.", self.current)
# Comma means there's more to print
if not self.out_of_tokens and self.current.kind is TokenType.COMMA:
self.consume(TokenType.COMMA)
Expand Down Expand Up @@ -166,15 +157,15 @@ def parse_return(self, line_id: int) -> ReturnStatement:
# NUMERIC_EXPRESSION BOOLEAN_OPERATOR NUMERIC_EXPRESSION
def parse_boolean_expression(self) -> BooleanExpression:
left = self.parse_numeric_expression()
if self.current.kind in [TokenType.GREATER, TokenType.GREATER_EQUAL, TokenType.EQUAL,
TokenType.LESS, TokenType.LESS_EQUAL, TokenType.NOT_EQUAL]:
if self.current.kind in {TokenType.GREATER, TokenType.GREATER_EQUAL, TokenType.EQUAL,
TokenType.LESS, TokenType.LESS_EQUAL, TokenType.NOT_EQUAL}:
operator = self.consume(self.current.kind)
right = self.parse_numeric_expression()
return BooleanExpression(line_num=left.line_num,
col_start=left.col_start, col_end=right.col_end,
operator=operator.kind, left_expr=left, right_expr=right)
raise Parser.ParserError(f"Expected boolean operator but found "
f"{self.current.kind}.", self.current)
raise ParserError(f"Expected boolean operator but found "
f"{self.current.kind}.", self.current)

def parse_numeric_expression(self) -> NumericExpression:
left = self.parse_term()
Expand Down Expand Up @@ -235,7 +226,7 @@ def parse_factor(self) -> NumericExpression:
self.consume(TokenType.OPEN_PAREN)
expression = self.parse_numeric_expression()
if self.current.kind is not TokenType.CLOSE_PAREN:
raise Parser.ParserError("Expected matching close parenthesis.", self.current)
raise ParserError("Expected matching close parenthesis.", self.current)
self.consume(TokenType.CLOSE_PAREN)
return expression
elif self.current.kind is TokenType.MINUS:
Expand All @@ -244,4 +235,4 @@ def parse_factor(self) -> NumericExpression:
return UnaryOperation(line_num=minus.line_num,
col_start=minus.col_start, col_end=expression.col_end,
operator=TokenType.MINUS, expr=expression)
raise Parser.ParserError("Unexpected token in numeric expression.", self.current)
raise ParserError("Unexpected token in numeric expression.", self.current)
3 changes: 2 additions & 1 deletion tests/test_brainfuck.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import unittest
import sys
import os
from pathlib import Path
from io import StringIO
from Brainfuck.brainfuck import Brainfuck

Expand All @@ -37,7 +38,7 @@ class BrainfuckTestCase(unittest.TestCase):
def setUp(self) -> None:
# Change working directory to this file so we can easily access
# the Examples directory where the test Brainfuck code resides
os.chdir(os.path.dirname(os.path.abspath(__file__)))
os.chdir(Path(__file__).resolve().parent)

def test_hello_world(self):
program_output = run("../Brainfuck/Examples/hello_world_verbose.bf")
Expand Down
3 changes: 2 additions & 1 deletion tests/test_knn.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import unittest
import os
import csv
from pathlib import Path
from KNN.knn import KNN
from KNN.fish import Fish
from KNN.digit import Digit
Expand All @@ -27,7 +28,7 @@
class FishTestCase(unittest.TestCase):
def setUp(self) -> None:
# Change working directory to this file to get datasets
os.chdir(os.path.dirname(os.path.abspath(__file__)))
os.chdir(Path(__file__).resolve().parent)

def test_nearest(self):
k: int = 3
Expand Down
3 changes: 2 additions & 1 deletion tests/test_nanobasic.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import unittest
import sys
import os
from pathlib import Path
from io import StringIO
from NanoBASIC.executioner import execute

Expand All @@ -37,7 +38,7 @@ class NanoBASICTestCase(unittest.TestCase):
def setUp(self) -> None:
# Change working directory to this file so we can easily access
# the Examples directory where the test NanoBASIC code resides
os.chdir(os.path.dirname(os.path.abspath(__file__)))
os.chdir(Path(__file__).resolve().parent)

def test_print1(self):
program_output = run("../NanoBASIC/Examples/print1.bas")
Expand Down
3 changes: 2 additions & 1 deletion tests/test_nesemulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# Tries running multiple different tests to verify the correctness of our emulator.
import unittest
import os
from pathlib import Path
from NESEmulator.cpu import CPU
from NESEmulator.ppu import PPU
from NESEmulator.rom import ROM
Expand All @@ -27,7 +28,7 @@ class CPUTestCase(unittest.TestCase):
def setUp(self) -> None:
# Change working directory to this file so we can easily access
# the Tests directory where the test ROMs and logs reside
os.chdir(os.path.dirname(os.path.abspath(__file__)))
os.chdir(Path(__file__).resolve().parent)

def test_nes_test(self):
# Create machinery that we are testing
Expand Down

0 comments on commit d8ae63c

Please sign in to comment.