Implement FOR and NEXT
authorNick Downing <nick@ndcode.org>
Sun, 15 May 2022 15:12:10 +0000 (01:12 +1000)
committerNick Downing <nick@ndcode.org>
Sun, 15 May 2022 15:12:23 +0000 (01:12 +1000)
applesoft_basic.t
applesoft_basic.y
test.bas

index 8d17e2a..84c8db1 100644 (file)
@@ -57,6 +57,8 @@ class NodeStatementIf: NodeStatement;
 class NodeStatementEnd: NodeStatement;
 class NodeStatementGosub: NodeStatement;
 class NodeStatementReturn: NodeStatement;
+class NodeStatementFor: NodeStatement;
+class NodeStatementNext: NodeStatement;
 class NodeExpression: Node;
 class NodeExpressionOr: NodeExpression;
 class NodeExpressionAnd: NodeExpression;
@@ -104,6 +106,9 @@ class EndException(Exception):
 class ReturnException(Exception):
   pass
 
+class NextException(Exception):
+  pass
+
 class Context:
   def __init__(self, program = None, variables = None, i = 0, j = 1):
     self.program = program if program is not None else NodeProgram()
@@ -120,6 +125,10 @@ class Context:
       raise Exception(
         f'?RETURN WITHOUT GOSUB ERROR IN {self.line_number():d}'
       )
+    except NextException:
+      raise Exception(
+        f'?NEXT WITHOUT FOR ERROR IN {self.line_number():d}'
+      )
 
   def line_number(self):
     assert self.i < len(self.program.children)
@@ -144,7 +153,9 @@ class Context:
         self.j = 1
         break
     else:
-      raise Exception(f'?UNDEF\'D STATEMENT ERROR IN {self.line_number():d}')
+      raise Exception(
+        f'?UNDEF\'D STATEMENT ERROR IN {self.line_number():d}'
+      )
 
 def factory(tag, *args, **kwargs):
   return tag_to_class[tag](*args, **kwargs)
@@ -186,14 +197,20 @@ def execute(self, context):
   value = self.children[1].evaluate(context)
   if name[-1] == '$':
     if not isinstance(value, str):
-      raise Exception(f'?TYPE MISMATCH ERROR IN {context.line_number():d}')
+      raise Exception(
+        f'?TYPE MISMATCH ERROR IN {context.line_number():d}'
+      )
   elif name[-1] == '%':
     if not isinstance(value, float):
-      raise Exception(f'?TYPE MISMATCH ERROR IN {context.line_number():d}')
+      raise Exception(
+        f'?TYPE MISMATCH ERROR IN {context.line_number():d}'
+      )
     value = ((int(math.floor(value)) + 0x8000) & 0xffff) - 0x8000
   else:
     if not isinstance(value, float):
-      raise Exception(f'?TYPE MISMATCH ERROR IN {context.line_number():d}')
+      raise Exception(
+        f'?TYPE MISMATCH ERROR IN {context.line_number():d}'
+      )
   context.variables[name] = value
 @method(NodeStatementPrint)
 def execute(self, context):
@@ -215,7 +232,9 @@ def execute(self, context):
           exponent = ''
         else:
           if exponent >= 100:
-            raise Exception(f'?OVERFLOW ERROR IN {context.line_number():d}')
+            raise Exception(
+              f'?OVERFLOW ERROR IN {context.line_number():d}'
+            )
           value *= 10. ** (8 - exponent)
 
           int_value = int(round(value))
@@ -224,7 +243,9 @@ def execute(self, context):
             value /= 10.
             exponent += 1
             if exponent >= 100:
-              raise Exception(f'?OVERFLOW ERROR IN {context.line_number():d}')
+              raise Exception(
+                f'?OVERFLOW ERROR IN {context.line_number():d}'
+              )
             int_value = int(round(value))
             assert int_value >= 100000000 and int_value < 1000000000
 
@@ -274,11 +295,67 @@ def execute(self, context):
     context.execute()
   except ReturnException:
     pass
+  except NextException:
+    raise Exception(
+      f'?NEXT WITHOUT FOR ERROR IN {context.line_number():d}'
+    )
   context.i = i
   context.j = j
 @method(NodeStatementReturn)
 def execute(self, context):
   raise ReturnException()
+@method(NodeStatementFor)
+def execute(self, context):
+  name = self.children[0].str_value
+  start = self.children[1].evaluate(context)
+  end = self.children[2].evaluate(context)
+  step = (
+    self.children[3].evaluate(context)
+  if len(self.children) >= 4 else
+    1.
+  )
+  if (
+    name[-1] in '$%' or
+      not isinstance(start, float) or
+      not isinstance(end, float) or
+      not isinstance(step, float)
+  ):
+    # interestingly, real applesoft treats % (but not $) as syntax error
+    raise Exception(
+      f'?TYPE MISMATCH ERROR IN {context.line_number():d}'
+    )
+  context.variables[name] = start
+
+  i = context.i
+  j = context.j
+  while True:
+    try:
+      context.execute()
+    except NextException:
+      pass
+  
+    assert context.i < len(context.program.children)
+    line = context.program.children[context.i]
+    assert context.j > 1 and context.j <= len(line.children)
+    statement = line.children[context.j - 1]
+    assert isinstance(statement, NodeStatementNext)
+    if (
+      len(statement.children) >= 1 and
+        statement.children[0].str_value != name
+    ):
+      raise Exception(
+        f'?NEXT WITHOUT FOR ERROR IN {context.line_number():d}'
+      )
+  
+    context.variables[name] += step
+    if context.variables[name] > end:
+      break
+
+    context.i = i
+    context.j = j
+@method(NodeStatementNext)
+def execute(self, context):
+  raise NextException()
 del execute
 
 @method(NodeExpression)
@@ -289,14 +366,18 @@ def evaluate(self, context):
   value0 = self.children[0].evaluate(context)
   value1 = self.children[1].evaluate(context)
   if not isinstance(value0, float) or not isinstance(value1, float):
-    raise Exception(f'?TYPE MISMATCH ERROR IN {context.line_number():d}')
+    raise Exception(
+      f'?TYPE MISMATCH ERROR IN {context.line_number():d}'
+    )
   return float(value0 != 0. or value1 != 0.)
 @method(NodeExpressionAnd)
 def evaluate(self, context):
   value0 = self.children[0].evaluate(context)
   value1 = self.children[1].evaluate(context)
   if not isinstance(value0, float) or not isinstance(value1, float):
-    raise Exception(f'?TYPE MISMATCH ERROR IN {context.line_number():d}')
+    raise Exception(
+      f'?TYPE MISMATCH ERROR IN {context.line_number():d}'
+    )
   return float(value0 != 0. and value1 != 0.)
 @method(NodeExpressionLT)
 def evaluate(self, context):
@@ -306,7 +387,9 @@ def evaluate(self, context):
     (not isinstance(value0, str) or not isinstance(value1, str)) and
       (not isinstance(value0, float) or not isinstance(value1, float))
   ):
-    raise Exception(f'?TYPE MISMATCH ERROR IN {context.line_number():d}')
+    raise Exception(
+      f'?TYPE MISMATCH ERROR IN {context.line_number():d}'
+    )
   return float(value0 < value1)
 @method(NodeExpressionEqual)
 def evaluate(self, context):
@@ -316,7 +399,9 @@ def evaluate(self, context):
     (not isinstance(value0, str) or not isinstance(value1, str)) and
       (not isinstance(value0, float) or not isinstance(value1, float))
   ):
-    raise Exception(f'?TYPE MISMATCH ERROR IN {context.line_number():d}')
+    raise Exception(
+      f'?TYPE MISMATCH ERROR IN {context.line_number():d}'
+    )
   return float(value0 == value1)
 @method(NodeExpressionGT)
 def evaluate(self, context):
@@ -326,7 +411,9 @@ def evaluate(self, context):
     (not isinstance(value0, str) or not isinstance(value1, str)) and
       (not isinstance(value0, float) or not isinstance(value1, float))
   ):
-    raise Exception(f'?TYPE MISMATCH ERROR IN {context.line_number():d}')
+    raise Exception(
+      f'?TYPE MISMATCH ERROR IN {context.line_number():d}'
+    )
   return float(value0 > value1)
 @method(NodeExpressionGE)
 def evaluate(self, context):
@@ -336,7 +423,9 @@ def evaluate(self, context):
     (not isinstance(value0, str) or not isinstance(value1, str)) and
       (not isinstance(value0, float) or not isinstance(value1, float))
   ):
-    raise Exception(f'?TYPE MISMATCH ERROR IN {context.line_number():d}')
+    raise Exception(
+      f'?TYPE MISMATCH ERROR IN {context.line_number():d}'
+    )
   return float(value0 >= value1)
 @method(NodeExpressionLE)
 def evaluate(self, context):
@@ -346,7 +435,9 @@ def evaluate(self, context):
     (not isinstance(value0, str) or not isinstance(value1, str)) and
       (not isinstance(value0, float) or not isinstance(value1, float))
   ):
-    raise Exception(f'?TYPE MISMATCH ERROR IN {context.line_number():d}')
+    raise Exception(
+      f'?TYPE MISMATCH ERROR IN {context.line_number():d}'
+    )
   return float(value0 <= value1)
 @method(NodeExpressionNE)
 def evaluate(self, context):
@@ -356,7 +447,9 @@ def evaluate(self, context):
     (not isinstance(value0, str) or not isinstance(value1, str)) and
       (not isinstance(value0, float) or not isinstance(value1, float))
   ):
-    raise Exception(f'?TYPE MISMATCH ERROR IN {context.line_number():d}')
+    raise Exception(
+      f'?TYPE MISMATCH ERROR IN {context.line_number():d}'
+    )
   return float(value0 != value1)
 @method(NodeExpressionAdd)
 def evaluate(self, context):
@@ -366,47 +459,61 @@ def evaluate(self, context):
     (not isinstance(value0, str) or not isinstance(value1, str)) and
       (not isinstance(value0, float) or not isinstance(value1, float))
   ):
-    raise Exception(f'?TYPE MISMATCH ERROR IN {context.line_number():d}')
+    raise Exception(
+      f'?TYPE MISMATCH ERROR IN {context.line_number():d}'
+    )
   return value0 + value1
 @method(NodeExpressionSubtract)
 def evaluate(self, context):
   value0 = self.children[0].evaluate(context)
   value1 = self.children[1].evaluate(context)
   if not isinstance(value0, float) or not isinstance(value1, float):
-    raise Exception(f'?TYPE MISMATCH ERROR IN {context.line_number():d}')
+    raise Exception(
+      f'?TYPE MISMATCH ERROR IN {context.line_number():d}'
+    )
   return value0 - value1
 @method(NodeExpressionMultiply)
 def evaluate(self, context):
   value0 = self.children[0].evaluate(context)
   value1 = self.children[1].evaluate(context)
   if not isinstance(value0, float) or not isinstance(value1, float):
-    raise Exception(f'?TYPE MISMATCH ERROR IN {context.line_number():d}')
+    raise Exception(
+      f'?TYPE MISMATCH ERROR IN {context.line_number():d}'
+    )
   return value0 * value1
 @method(NodeExpressionDivide)
 def evaluate(self, context):
   value0 = self.children[0].evaluate(context)
   value1 = self.children[1].evaluate(context)
   if not isinstance(value0, float) or not isinstance(value1, float):
-    raise Exception(f'?TYPE MISMATCH ERROR IN {context.line_number():d}')
+    raise Exception(
+      f'?TYPE MISMATCH ERROR IN {context.line_number():d}'
+    )
   return value0 / value1
 @method(NodeExpressionPower)
 def evaluate(self, context):
   value0 = self.children[0].evaluate(context)
   value1 = self.children[1].evaluate(context)
   if not isinstance(value0, float) or not isinstance(value1, float):
-    raise Exception(f'?TYPE MISMATCH ERROR IN {context.line_number():d}')
+    raise Exception(
+      f'?TYPE MISMATCH ERROR IN {context.line_number():d}'
+    )
   return value0 ** value1
 @method(NodeExpressionSign)
 def evaluate(self, context):
   value = self.children[0].evaluate(context)
   if not isinstance(value, float):
-    raise Exception(f'?TYPE MISMATCH ERROR IN {context.line_number():d}')
+    raise Exception(
+      f'?TYPE MISMATCH ERROR IN {context.line_number():d}'
+    )
   return self.sign * value
 @method(NodeExpressionNot)
 def evaluate(self, context):
   value = self.children[0].evaluate(context)
   if not isinstance(value, float):
-    raise Exception(f'?TYPE MISMATCH ERROR IN {context.line_number():d}')
+    raise Exception(
+      f'?TYPE MISMATCH ERROR IN {context.line_number():d}'
+    )
   return float(value == 0.)
 @method(NodeExpressionIntLiteral)
 def evaluate(self, context):
index 42c9039..e059706 100644 (file)
@@ -163,6 +163,10 @@ statement_opt
   | %space (?E{t_def.NodeStatementEnd}KEYWORD_END)
   | %space (?E{t_def.NodeStatementGosub}KEYWORD_GOSUB INT_LITERAL)
   | %space (?E{t_def.NodeStatementReturn}KEYWORD_RETURN)
+  | %space (?E{t_def.NodeStatementFor}KEYWORD_FOR VARIABLE '=' expression KEYWORD_TO expression)
+  | %space (?E{t_def.NodeStatementFor}KEYWORD_FOR VARIABLE '=' expression KEYWORD_TO expression KEYWORD_STEP expression)
+  | %space (?E{t_def.NodeStatementNext}KEYWORD_NEXT)
+  | %space (?E{t_def.NodeStatementNext}KEYWORD_NEXT VARIABLE)
   ;
 
 print_expression_list0
index 07ee00e..08d2ecc 100644 (file)
--- a/test.bas
+++ b/test.bas
@@ -1,7 +1,11 @@
 10 I$="PI":I%=-3.141:I=3.141
 20 PRINT I$" "I%" "I
 30 GOSUB 100
-40 IF I%>5 THEN END
+40 IF I%>5 THEN 60
 50 GOTO 20
+60 FOR J=1 TO 10
+70 PRINT "J"J
+80 NEXT J
+90 END
 100 I%=I%+1
 110 RETURN