Skip to content

Commit d8ae63c

Browse files
committed
Updates from technical review of chapter 2 and minor updates based on development review of other chapters
1 parent 149e255 commit d8ae63c

File tree

11 files changed

+87
-58
lines changed

11 files changed

+87
-58
lines changed

KNN/knn.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def classify(self, k: int, data_point: DP) -> str:
5555
neighbors = self.nearest(k, data_point)
5656
return Counter(neighbor.kind for neighbor in neighbors).most_common(1)[0][0]
5757

58-
# Predict a numeric property of a data point based on the k nearest neighbors
58+
# Predict a numeric property of a data point based on the k nearest neighbors
5959
# Find the average of that property from the neighbors and return it
6060
def predict(self, k: int, data_point: DP, property_name: str) -> float:
6161
neighbors = self.nearest(k, data_point)

NESEmulator/ppu.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def __init__(self, rom: ROM):
4141
# PPU Memory
4242
self.spr = array('B', [0] * SPR_RAM_SIZE) # sprite ram
4343
self.nametables = array('B', [0] * NAMETABLE_SIZE) # nametable ram
44-
self.palette = array('B', [0] * PALETTE_SIZE) # pallete ram
44+
self.palette = array('B', [0] * PALETTE_SIZE) # palette ram
4545
# Registers
4646
self.addr = 0 # main PPU address register
4747
self.addr_write_latch = False

NESEmulator/rom.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def __init__(self, filename: str):
3636
print("Invalid ROM Header Signature")
3737
else:
3838
print("Valid ROM Header Signature")
39-
# Untangle Mapper - one nybble in flags6 and one nybble in flags7
39+
# Untangle Mapper - one nibble in flags6 and one nibble in flags7
4040
self.mapper = (self.header.flags7 & 0xF0) | (
4141
(self.header.flags6 & 0xF0) >> 4)
4242
print(f"Mapper {self.mapper}")

NanoBASIC/errors.py

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# NanoBASIC/errors.py
2+
# From Fun Computer Science Projects in Python
3+
# Copyright 2024 David Kopec
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
# DESCRIPTION
18+
# The errors module contains the custom exceptions used in the NanoBASIC
19+
# interpreter including ParserError and InterpreterError.
20+
from NanoBASIC.tokenizer import Token
21+
from NanoBASIC.nodes import Node
22+
23+
24+
class NanoBASICError(Exception):
25+
def __init__(self, message: str, line_num: int, column: int):
26+
super().__init__(message)
27+
self.message = message
28+
self.line_num = line_num
29+
self.column = column
30+
31+
def __str__(self):
32+
return (f"{self.message} Occurred at line {self.line_num} "
33+
f"and column {self.column}")
34+
35+
36+
class ParserError(NanoBASICError):
37+
def __init__(self, message: str, token: Token):
38+
super().__init__(message, token.line_num, token.col_start)
39+
40+
41+
class InterpreterError(NanoBASICError):
42+
def __init__(self, message: str, node: Node):
43+
super().__init__(message, node.line_num, node.col_start)

NanoBASIC/interpreter.py

+16-25
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,11 @@
2121
# just an array of statement nodes; each node is something of a tree in itself. The interpreter
2222
# translates the meaning of each statement node into Python code that can be run "live."
2323
from NanoBASIC.nodes import *
24+
from NanoBASIC.errors import InterpreterError
2425
from collections import deque
2526

2627

2728
class Interpreter:
28-
class InterpreterError(Exception):
29-
def __init__(self, message: str, node: Node):
30-
self.message = message
31-
self.line_num = node.line_num
32-
self.column = node.col_start
33-
34-
def __str__(self):
35-
return (f"{self.message} Occurred at line {self.line_num} "
36-
f"and column {self.column}")
37-
3829
def __init__(self, statements: list[Statement]):
3930
self.statements = statements
4031
self.variable_table: dict[str, int] = {}
@@ -75,17 +66,17 @@ def interpret(self, statement: Statement):
7566
if (line_index := self.find_line_index(go_to_line_id)) is not None:
7667
self.statement_index = line_index
7768
else:
78-
raise Interpreter.InterpreterError("No GOTO line id.", self.current)
69+
raise InterpreterError("No GOTO line id.", self.current)
7970
case GoSubStatement(line_expr=line_expr):
8071
go_sub_line_id = self.evaluate_numeric(line_expr)
8172
if (line_index := self.find_line_index(go_sub_line_id)) is not None:
8273
self.subroutine_stack.append(self.statement_index + 1) # Setup for RETURN
8374
self.statement_index = line_index
8475
else:
85-
raise Interpreter.InterpreterError("No GOSUB line id.", self.current)
76+
raise InterpreterError("No GOSUB line id.", self.current)
8677
case ReturnStatement():
8778
if not self.subroutine_stack: # Check if the stack is empty
88-
raise Interpreter.InterpreterError("RETURN without GOSUB.", self.current)
79+
raise InterpreterError("RETURN without GOSUB.", self.current)
8980
self.statement_index = self.subroutine_stack.pop()
9081
case PrintStatement(printables=printables):
9182
accumulated_string: str = ""
@@ -104,8 +95,8 @@ def interpret(self, statement: Statement):
10495
else:
10596
self.statement_index += 1
10697
case _:
107-
raise Interpreter.InterpreterError(f"Unexpected item {self.current} "
108-
f"in statement list.", self.current)
98+
raise InterpreterError(f"Unexpected item {self.current} "
99+
f"in statement list.", self.current)
109100

110101
def evaluate_numeric(self, numeric_expression: NumericExpression) -> int:
111102
match numeric_expression:
@@ -115,14 +106,14 @@ def evaluate_numeric(self, numeric_expression: NumericExpression) -> int:
115106
if name in self.variable_table:
116107
return self.variable_table[name]
117108
else:
118-
raise Interpreter.InterpreterError(f"Var {name} used "
119-
f"before initialized.", numeric_expression)
109+
raise InterpreterError(f"Var {name} used "
110+
f"before initialized.", numeric_expression)
120111
case UnaryOperation(operator=operator, expr=expr):
121112
if operator is TokenType.MINUS:
122113
return -self.evaluate_numeric(expr)
123114
else:
124-
raise Interpreter.InterpreterError(f"Expected - "
125-
f"but got {operator}.", numeric_expression)
115+
raise InterpreterError(f"Expected - "
116+
f"but got {operator}.", numeric_expression)
126117
case BinaryOperation(operator=operator, left_expr=left, right_expr=right):
127118
if operator is TokenType.PLUS:
128119
return self.evaluate_numeric(left) + self.evaluate_numeric(right)
@@ -133,11 +124,11 @@ def evaluate_numeric(self, numeric_expression: NumericExpression) -> int:
133124
elif operator is TokenType.DIVIDE:
134125
return self.evaluate_numeric(left) // self.evaluate_numeric(right)
135126
else:
136-
raise Interpreter.InterpreterError(f"Unexpected binary operator "
137-
f"{operator}.", numeric_expression)
127+
raise InterpreterError(f"Unexpected binary operator "
128+
f"{operator}.", numeric_expression)
138129
case _:
139-
raise Interpreter.InterpreterError("Expected numeric expression.",
140-
numeric_expression)
130+
raise InterpreterError("Expected numeric expression.",
131+
numeric_expression)
141132

142133
def evaluate_boolean(self, boolean_expression: BooleanExpression) -> bool:
143134
left = self.evaluate_numeric(boolean_expression.left_expr)
@@ -156,5 +147,5 @@ def evaluate_boolean(self, boolean_expression: BooleanExpression) -> bool:
156147
case TokenType.NOT_EQUAL:
157148
return left != right
158149
case _:
159-
raise Interpreter.InterpreterError(f"Unexpected boolean operator "
160-
f"{boolean_expression.operator}.", boolean_expression)
150+
raise InterpreterError(f"Unexpected boolean operator "
151+
f"{boolean_expression.operator}.", boolean_expression)

NanoBASIC/nodes.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class NumericExpression(Node):
4747
pass
4848

4949

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

6060

61-
# A numeric expression with one operand like -
61+
# A numeric expression with one operand like -4
6262
@dataclass(frozen=True)
6363
class UnaryOperation(NumericExpression):
6464
operator: TokenType

NanoBASIC/parser.py

+15-24
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,10 @@
2929
from NanoBASIC.tokenizer import Token
3030
from typing import cast
3131
from NanoBASIC.nodes import *
32+
from NanoBASIC.errors import ParserError
3233

3334

3435
class Parser:
35-
class ParserError(Exception):
36-
def __init__(self, message: str, token: Token):
37-
self.message = message
38-
self.line_num = token.line_num
39-
self.column = token.col_start
40-
41-
def __str__(self):
42-
return (f"{self.message} Occurred at line {self.line_num} "
43-
f"and column {self.column}")
44-
4536
def __init__(self, tokens: list[Token]):
4637
self.tokens = tokens
4738
self.token_index: int = 0
@@ -53,8 +44,8 @@ def out_of_tokens(self) -> bool:
5344
@property
5445
def current(self) -> Token:
5546
if self.out_of_tokens:
56-
raise (Parser.ParserError(f"No tokens after "
57-
f"{self.previous.kind}", self.previous))
47+
raise (ParserError(f"No tokens after "
48+
f"{self.previous.kind}", self.previous))
5849
return self.tokens[self.token_index]
5950

6051
@property
@@ -65,8 +56,8 @@ def consume(self, kind: TokenType) -> Token:
6556
if self.current.kind is kind:
6657
self.token_index += 1
6758
return self.previous
68-
raise Parser.ParserError(f"Expected {kind} after {self.previous}"
69-
f"but got {self.current}.", self.current)
59+
raise ParserError(f"Expected {kind} after {self.previous}"
60+
f"but got {self.current}.", self.current)
7061

7162
def parse(self) -> list[Statement]:
7263
statements: list[Statement] = []
@@ -93,8 +84,8 @@ def parse_statement(self, line_id: int) -> Statement:
9384
return self.parse_gosub(line_id)
9485
case TokenType.RETURN_T:
9586
return self.parse_return(line_id)
96-
raise Parser.ParserError("Expected to find start of statement.",
97-
self.current)
87+
raise ParserError("Expected to find start of statement.",
88+
self.current)
9889

9990
# PRINT "COMMA",SEPARATED,7154
10091
def parse_print(self, line_id: int) -> PrintStatement:
@@ -110,8 +101,8 @@ def parse_print(self, line_id: int) -> PrintStatement:
110101
printables.append(expression)
111102
last_col = expression.col_end
112103
else:
113-
raise Parser.ParserError("Only strings and numeric expressions "
114-
"allowed in print list.", self.current)
104+
raise ParserError("Only strings and numeric expressions "
105+
"allowed in print list.", self.current)
115106
# Comma means there's more to print
116107
if not self.out_of_tokens and self.current.kind is TokenType.COMMA:
117108
self.consume(TokenType.COMMA)
@@ -166,15 +157,15 @@ def parse_return(self, line_id: int) -> ReturnStatement:
166157
# NUMERIC_EXPRESSION BOOLEAN_OPERATOR NUMERIC_EXPRESSION
167158
def parse_boolean_expression(self) -> BooleanExpression:
168159
left = self.parse_numeric_expression()
169-
if self.current.kind in [TokenType.GREATER, TokenType.GREATER_EQUAL, TokenType.EQUAL,
170-
TokenType.LESS, TokenType.LESS_EQUAL, TokenType.NOT_EQUAL]:
160+
if self.current.kind in {TokenType.GREATER, TokenType.GREATER_EQUAL, TokenType.EQUAL,
161+
TokenType.LESS, TokenType.LESS_EQUAL, TokenType.NOT_EQUAL}:
171162
operator = self.consume(self.current.kind)
172163
right = self.parse_numeric_expression()
173164
return BooleanExpression(line_num=left.line_num,
174165
col_start=left.col_start, col_end=right.col_end,
175166
operator=operator.kind, left_expr=left, right_expr=right)
176-
raise Parser.ParserError(f"Expected boolean operator but found "
177-
f"{self.current.kind}.", self.current)
167+
raise ParserError(f"Expected boolean operator but found "
168+
f"{self.current.kind}.", self.current)
178169

179170
def parse_numeric_expression(self) -> NumericExpression:
180171
left = self.parse_term()
@@ -235,7 +226,7 @@ def parse_factor(self) -> NumericExpression:
235226
self.consume(TokenType.OPEN_PAREN)
236227
expression = self.parse_numeric_expression()
237228
if self.current.kind is not TokenType.CLOSE_PAREN:
238-
raise Parser.ParserError("Expected matching close parenthesis.", self.current)
229+
raise ParserError("Expected matching close parenthesis.", self.current)
239230
self.consume(TokenType.CLOSE_PAREN)
240231
return expression
241232
elif self.current.kind is TokenType.MINUS:
@@ -244,4 +235,4 @@ def parse_factor(self) -> NumericExpression:
244235
return UnaryOperation(line_num=minus.line_num,
245236
col_start=minus.col_start, col_end=expression.col_end,
246237
operator=TokenType.MINUS, expr=expression)
247-
raise Parser.ParserError("Unexpected token in numeric expression.", self.current)
238+
raise ParserError("Unexpected token in numeric expression.", self.current)

tests/test_brainfuck.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import unittest
2121
import sys
2222
import os
23+
from pathlib import Path
2324
from io import StringIO
2425
from Brainfuck.brainfuck import Brainfuck
2526

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

4243
def test_hello_world(self):
4344
program_output = run("../Brainfuck/Examples/hello_world_verbose.bf")

tests/test_knn.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import unittest
2020
import os
2121
import csv
22+
from pathlib import Path
2223
from KNN.knn import KNN
2324
from KNN.fish import Fish
2425
from KNN.digit import Digit
@@ -27,7 +28,7 @@
2728
class FishTestCase(unittest.TestCase):
2829
def setUp(self) -> None:
2930
# Change working directory to this file to get datasets
30-
os.chdir(os.path.dirname(os.path.abspath(__file__)))
31+
os.chdir(Path(__file__).resolve().parent)
3132

3233
def test_nearest(self):
3334
k: int = 3

tests/test_nanobasic.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import unittest
2121
import sys
2222
import os
23+
from pathlib import Path
2324
from io import StringIO
2425
from NanoBASIC.executioner import execute
2526

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

4243
def test_print1(self):
4344
program_output = run("../NanoBASIC/Examples/print1.bas")

tests/test_nesemulator.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
# Tries running multiple different tests to verify the correctness of our emulator.
1919
import unittest
2020
import os
21+
from pathlib import Path
2122
from NESEmulator.cpu import CPU
2223
from NESEmulator.ppu import PPU
2324
from NESEmulator.rom import ROM
@@ -27,7 +28,7 @@ class CPUTestCase(unittest.TestCase):
2728
def setUp(self) -> None:
2829
# Change working directory to this file so we can easily access
2930
# the Tests directory where the test ROMs and logs reside
30-
os.chdir(os.path.dirname(os.path.abspath(__file__)))
31+
os.chdir(Path(__file__).resolve().parent)
3132

3233
def test_nes_test(self):
3334
# Create machinery that we are testing

0 commit comments

Comments
 (0)