Initial commit, can interpret LET, PRINT and GOTO, expression evaluation works
authorNick Downing <nick@ndcode.org>
Sun, 15 May 2022 07:03:46 +0000 (17:03 +1000)
committerNick Downing <nick@ndcode.org>
Sun, 15 May 2022 07:03:46 +0000 (17:03 +1000)
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
applesoft_basic.l [new file with mode: 0644]
applesoft_basic.py [new file with mode: 0755]
applesoft_basic.t [new file with mode: 0644]
applesoft_basic.y [new file with mode: 0644]
test.bas [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..8f4dc51
--- /dev/null
@@ -0,0 +1,5 @@
+__pycache__
+/element.py
+/lex_yy.py
+/t_def.py
+/y_tab.py
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..cd0d4b1
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,16 @@
+all: element.py lex_yy.py t_def.py y_tab.py
+
+element.py:
+       pitree --install-element
+
+lex_yy.py: applesoft_basic.l
+       pilex --element --groups --python $<
+
+t_def.py: applesoft_basic.t
+       pitree --python $<
+
+y_tab.py: applesoft_basic.y
+       piyacc --element --python $<
+
+clean:
+       rm -f element.py lex_yy.py t_def.py y_tab.py
diff --git a/applesoft_basic.l b/applesoft_basic.l
new file mode 100644 (file)
index 0000000..0ffea7c
--- /dev/null
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2018 Nick Downing <nick@ndcode.org>
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+%{
+  import element
+  import t_def
+  import y_tab
+%}
+
+%option nodefault
+
+%%
+
+[ ]
+X\ *P\ *L\ *O\ *T/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_XPLOT
+}
+X\ *D\ *R\ *A\ *W/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_XDRAW
+}
+W\ *A\ *I\ *T/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_WAIT
+}
+V\ *T\ *A\ *B/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_VTAB
+}
+V\ *L\ *I\ *N/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_VLIN
+}
+V\ *A\ *L/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_VAL
+}
+U\ *S\ *R/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_USR
+}
+T\ *R\ *A\ *C\ *E/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_TRACE
+}
+T\ *O/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_TO
+}
+T\ *H\ *E\ *N/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_THEN
+}
+T\ *E\ *X\ *T/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_TEXT
+}
+T\ *A\ *N/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_TAN
+}
+T\ *A\ *B\ *\( {
+  return y_tab.KEYWORD_TAB_LPAREN
+}
+S\ *T\ *R\ *\$ {
+  return y_tab.KEYWORD_STR_DOLLAR
+}
+S\ *T\ *O\ *R\ *E/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_STORE
+}
+S\ *T\ *O\ *P/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_STOP
+}
+S\ *T\ *E\ *P/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_STEP
+}
+S\ *Q\ *R/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_SQR
+}
+S\ *P\ *E\ *E\ *D\ *= {
+  return y_tab.KEYWORD_SPEED_EQUAL
+}
+S\ *P\ *C\ *\( {
+  return y_tab.KEYWORD_SPC_LPAREN
+}
+S\ *I\ *N/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_SIN
+}
+S\ *H\ *L\ *O\ *A\ *D/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_SHLOAD
+}
+S\ *G\ *N/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_SGN
+}
+S\ *C\ *R\ *N\ *\( {
+  return y_tab.KEYWORD_SCRN_LPAREN
+}
+S\ *C\ *A\ *L\ *E\ *= {
+  return y_tab.KEYWORD_SCALE_EQUAL
+}
+S\ *A\ *V\ *E/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_SAVE
+}
+R\ *I\ *G\ *H\ *T\ *\$ {
+  return y_tab.KEYWORD_RIGHT_DOLLAR
+}
+R\ *E\ *T\ *U\ *R\ *N/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_RETURN
+}
+R\ *E\ *S\ *U\ *M\ *E/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_RESUME
+}
+R\ *E\ *S\ *T\ *O\ *R\ *E/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_RESTORE
+}
+R\ *E\ *M/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_REM
+}
+R\ *E\ *C\ *A\ *L\ *L/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_RECALL
+}
+R\ *E\ *A\ *D/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_READ
+}
+P\ *R\ *I\ *N\ *T/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_PRINT
+}
+P\ *R\ *# {
+  return y_tab.KEYWORD_PR_FLOAT_LITERAL
+}
+P\ *O\ *S/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_POS
+}
+P\ *O\ *P/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_POP
+}
+P\ *O\ *K\ *E/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_POKE
+}
+P\ *L\ *O\ *T/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_PLOT
+}
+P\ *E\ *E\ *K/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_PEEK
+}
+P\ *D\ *L/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_PDL
+}
+O\ *R/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_OR
+}
+O\ *N\ *E\ *R\ *R/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_ONERR
+}
+O\ *N/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_ON
+}
+N\ *O\ *T\ *R\ *A\ *C\ *E/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_NOTRACE
+}
+N\ *O\ *T/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_NOT
+}
+N\ *O\ *R\ *M\ *A\ *L/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_NORMAL
+}
+N\ *E\ *X\ *T/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_NEXT
+}
+N\ *E\ *W/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_NEW
+}
+M\ *I\ *D\ *\$ {
+  return y_tab.KEYWORD_MID_DOLLAR
+}
+L\ *O\ *M\ *E\ *M\ *: {
+  return y_tab.KEYWORD_LOMEM_COLON
+}
+L\ *O\ *G/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_LOG
+}
+L\ *O\ *A\ *D/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_LOAD
+}
+L\ *I\ *S\ *T/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_LIST
+}
+L\ *E\ *T/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_LET
+}
+L\ *E\ *N/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_LEN
+}
+L\ *E\ *F\ *T\ *\$ {
+  return y_tab.KEYWORD_LEFT_DOLLAR
+}
+I\ *N\ *V\ *E\ *R\ *S\ *E/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_INVERSE
+}
+I\ *N\ *T/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_INT
+}
+I\ *N\ *P\ *U\ *T/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_INPUT
+}
+I\ *N\ *# {
+  return y_tab.KEYWORD_IN_FLOAT_LITERAL
+}
+I\ *F/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_IF
+}
+H\ *T\ *A\ *B/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_HTAB
+}
+H\ *P\ *L\ *O\ *T/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_HPLOT
+}
+H\ *O\ *M\ *E/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_HOME
+}
+H\ *L\ *I\ *N/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_HLIN
+}
+H\ *I\ *M\ *E\ *M\ *: {
+  return y_tab.KEYWORD_HIMEM_COLON
+}
+H\ *G\ *R\ *2/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_HGR2
+}
+H\ *G\ *R/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_HGR
+}
+H\ *C\ *O\ *L\ *O\ *R\ *= {
+  return y_tab.KEYWORD_HCOLOR_EQUAL
+}
+G\ *R/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_GR
+}
+G\ *O\ *T\ *O/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_GOTO
+}
+G\ *O/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_GO
+}
+G\ *E\ *T/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_GET
+}
+F\ *R\ *E/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_FRE
+}
+F\ *O\ *R/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_FOR
+}
+F\ *N/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_FN
+}
+F\ *L\ *A\ *S\ *H/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_FLASH
+}
+E\ *X\ *P/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_EXP
+}
+E\ *N\ *D/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_END
+}
+D\ *R\ *A\ *W/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_DRAW
+}
+D\ *I\ *M/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_DIM
+}
+D\ *E\ *L/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_DEL
+}
+D\ *E\ *F/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_DEF
+}
+D\ *A\ *T\ *A/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_DATA
+}
+C\ *O\ *S/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_COS
+}
+C\ *O\ *N\ *T/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_CONT
+}
+C\ *O\ *L\ *O\ *R\ *= {
+  return y_tab.KEYWORD_COLOR_EQUAL
+}
+C\ *L\ *E\ *A\ *R/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_CLEAR
+}
+C\ *H\ *R\ *\$ {
+  return y_tab.KEYWORD_CHR_DOLLAR
+}
+C\ *A\ *L\ *L/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_CALL
+}
+A\ *T\ *N/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_ATN
+}
+A\ *T/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_AT
+}
+A\ *S\ *C/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_ASC
+}
+A\ *N\ *D/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_AND
+}
+A\ *B\ *S/(\ *[A-Z0-9])*(\ *[$%])? {
+  return y_tab.KEYWORD_ABS
+}
+(?E{t_def.NodeTextIntLiteral}(?E{t_def.NodeText.Char}[0-9])(\ *(?E{t_def.NodeText.Char}[0-9]))*) {
+  return y_tab.INT_LITERAL
+}
+(?E{t_def.NodeTextVariable}(?E{t_def.NodeText.Char}[A-Z])(\ *(?E{t_def.NodeText.Char}[A-Z0-9])(\ *[A-Z0-9])*)?(\ *(?E{t_def.NodeText.Char}[$%]))?) {
+  return y_tab.VARIABLE
+}
+(?E{t_def.NodeTextIntLiteral}(?E{t_def.NodeText.Char}[0-9])(\ *(?E{t_def.NodeText.Char}[0-9]))*) {
+  return y_tab.INT_LITERAL
+}
+(?E{t_def.NodeFloatLiteral}((?E{t_def.NodeFloatLiteral.NodeTextInteger}(?E{t_def.NodeText.Char}[0-9])(\ *(?E{t_def.NodeText.Char}[0-9]))*)|(?E{t_def.NodeFloatLiteral.NodeTextInteger}((?E{t_def.NodeText.Char}[0-9])\ *)*)(?E{t_def.NodeFloatLiteral.NodeTextFraction}\.(\ *(?E{t_def.NodeText.Char}[0-9]))*))(?E{t_def.NodeFloatLiteral.Exponent}\ *E(?E{t_def.NodeFloatLiteral.NodeTextSign}(\ *(?E{t_def.NodeText.Char}[+-]))?)(?E{t_def.NodeFloatLiteral.NodeTextInteger}(\ *(?E{t_def.NodeText.Char}[0-9]))*))?) {
+  return y_tab.FLOAT_LITERAL
+}
+\"(?E{t_def.NodeStrLiteral}[^"\n]*)\" {
+  return y_tab.STR_LITERAL
+}
+\>\ *=|=\ *\> {
+  return y_tab.OPERATOR_GE
+}
+\<\ *=|=\ *\< {
+  return y_tab.OPERATOR_LE
+}
+\<\ *\>|\>\ *\< {
+  return y_tab.OPERATOR_NE
+}
+. {
+  return ord(yytext[0])
+}
+\n {
+  return ord('\n')
+}
diff --git a/applesoft_basic.py b/applesoft_basic.py
new file mode 100755 (executable)
index 0000000..2dda9b4
--- /dev/null
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2018 Nick Downing <nick@ndcode.org>
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; version 2.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 51
+# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+
+import t_def
+import element
+import sys
+import y_tab
+
+print('y_tab.yyparse()')
+program = y_tab.yyparse(t_def.NodeProgram)
+
+print('program.post_process()')
+program.post_process(program)
+
+print('element.serialize()')
+element.serialize(program, sys.stdout)
+
+print('context.execute()')
+context = t_def.Context(program)
+context.execute()
diff --git a/applesoft_basic.t b/applesoft_basic.t
new file mode 100644 (file)
index 0000000..ae3e9ab
--- /dev/null
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2019 Nick Downing <nick@ndcode.org>
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+%{
+  import element
+  import math
+%}
+
+%%
+
+/* generic */
+class Node;
+class NodeText: Node {
+  class Char;
+  str str_value = '';
+};
+
+/* lexical tokens */
+class NodeTextIntLiteral: NodeText {
+  int int_value = 0;
+};
+class NodeFloatLiteral: Node {
+  class NodeTextSign: NodeText;
+  class NodeTextInteger: NodeText;
+  class NodeTextFraction: NodeText;
+  class NodeExponent: Node;
+  float float_value = 0.;
+};
+class NodeStrLiteral: Node;
+class NodeTextVariable: NodeText;
+
+/* grammar productions */
+class NodeProgram: Node;
+class NodeLine: Node;
+class NodeStatement: Node;
+class NodeStatementLet: NodeStatement;
+class NodeStatementPrint: NodeStatement {
+  bool semicolon;
+};
+class NodeStatementGoto: NodeStatement;
+class NodeExpression: Node;
+class NodeExpressionOr: NodeExpression;
+class NodeExpressionAnd: NodeExpression;
+class NodeExpressionLT: NodeExpression;
+class NodeExpressionEqual: NodeExpression;
+class NodeExpressionGT: NodeExpression;
+class NodeExpressionGE: NodeExpression;
+class NodeExpressionLE: NodeExpression;
+class NodeExpressionNE: NodeExpression;
+class NodeExpressionAdd: NodeExpression;
+class NodeExpressionSubtract: NodeExpression;
+class NodeExpressionMultiply: NodeExpression;
+class NodeExpressionDivide: NodeExpression;
+class NodeExpressionPower: NodeExpression;
+class NodeExpressionSign: NodeExpression {
+  int sign;
+};
+class NodeExpressionNot: NodeExpression;
+class NodeExpressionIntLiteral: NodeExpression;
+class NodeExpressionFloatLiteral: NodeExpression;
+class NodeExpressionStrLiteral: NodeExpression;
+class NodeExpressionVariable: NodeExpression;
+
+%%
+
+# Copyright (C) 2018 Nick Downing <nick@ndcode.org>
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; version 2.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 51
+# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+
+class Context:
+  def __init__(self, program = None, variables = None, i = 0, j = 1):
+    self.program = program if program is not None else NodeProgram()
+    self.variables = variables if variables is not None else {}
+    self.i = i
+    self.j = j
+
+  def line_number(self):
+    assert self.i < len(self.program.children)
+    return self.program.children[self.i].children[0].int_value
+
+  def execute(self):
+    while self.i < len(self.program.children):
+      line = self.program.children[self.i]
+      if self.j < len(line.children):
+        statement = line.children[self.j]
+        self.j += 1
+        statement.execute(self)
+      else:
+        self.i += 1
+        self.j = 1
+
+def factory(tag, *args, **kwargs):
+  return tag_to_class[tag](*args, **kwargs)
+
+@method(Node)
+def post_process(self, program):
+  for i in self.children:
+    i.post_process(program)
+@method(NodeText)
+def post_process(self, program):
+  self.str_value = ''.join([i.text[0] for i in self.children])
+@method(NodeTextIntLiteral)
+def post_process(self, program):
+  NodeText.post_process(self, program)
+  self.int_value = int(self.str_value)  
+del post_process
+@method(NodeFloatLiteral)
+def post_process(self, program):
+  Node.post_process(self, program)
+  exponent = 0
+  if len(self.children) >= 4:
+    exponent = int(self.children[3].str_value)
+    if self.children[2].str_value == '-':
+      exponent = -exponent
+  value = (
+    int(self.children[0].str_value + self.children[1].str_value) *
+      10. ** (exponent - len(self.children[1].str_value))
+  )
+  self.float_value = value
+del post_process
+
+@method(NodeStatement)
+def execute(self, context):
+  raise NotImplementedError()
+@method(NodeStatementLet)
+def execute(self, context):
+  name = self.children[0].str_value
+  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}')
+  elif name[-1] == '%':
+    if not isinstance(value, float):
+      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}')
+  context.variables[name] = value
+@method(NodeStatementPrint)
+def execute(self, context):
+  for i in self.children:
+    value = i.evaluate(context)
+    if isinstance(value, float):
+      sign = ''
+      if value < 0.:
+        sign = '-'
+        value = -value
+      if value >= .01 and value < 999999999.2:
+        if value >= 100000000.:
+          value = f'{value:.0f}.'
+        elif value >= 10000000.:
+          value = f'{value:.1f}'
+        elif value >= 1000000.:
+          value = f'{value:.2f}'
+        elif value >= 100000.:
+          value = f'{value:.3f}'
+        elif value >= 10000.:
+          value = f'{value:.4f}'
+        elif value >= 1000.:
+          value = f'{value:.5f}'
+        elif value >= 100.:
+          value = f'{value:.6f}'
+        elif value >= 10.:
+          value = f'{value:.7f}'
+        elif value >= 1.:
+          value = f'{value:.8f}'
+        elif value >= .1:
+          value = f'{value:.9f}'
+        else:
+          value = f'{value:.10f}'
+        exponent = ''
+      else:
+        value = f'{value:10.8e}'
+        assert value[-4] == 'e'
+        exponent = 'E' + value[-3:]
+        value = value[:-4]
+      while value[-1] == '0':
+        value = value[:-1]
+      if value[-1] == '.':
+        value = value[:-1]
+      value = sign + value + exponent
+    assert isinstance(value, str)
+    print(value, end = '')
+  if not self.semicolon:
+    print()
+@method(NodeStatementGoto)
+def execute(self, context):
+  target = self.children[0].int_value
+  for i in range(len(context.program.children)):
+    if target == context.program.children[i].children[0].int_value:
+      context.i = i
+      context.j = 1
+      break
+  else:
+    raise Exception(f'?UNDEF\'D STATEMENT ERROR IN {context.line_number():d}')
+del execute
+
+@method(NodeExpression)
+def evaluate(self, context):
+  raise NotImplementedError()
+@method(NodeExpressionOr)
+def evaluate(self, context):
+  return float(
+    self.children[0].evaluate(context) != 0. or
+      self.children[1].evaluate(context) != 0.
+  )
+@method(NodeExpressionAnd)
+def evaluate(self, context):
+  return float(
+    self.children[0].evaluate(context) != 0. and
+      self.children[1].evaluate(context) != 0.
+  )
+@method(NodeExpressionLT)
+def evaluate(self, context):
+  return float(
+    self.children[0].evaluate(context) <
+      self.children[1].evaluate(context)
+  )
+@method(NodeExpressionEqual)
+def evaluate(self, context):
+  return float(
+    self.children[0].evaluate(context) ==
+      self.children[1].evaluate(context)
+  )
+@method(NodeExpressionGT)
+def evaluate(self, context):
+  return float(
+    self.children[0].evaluate(context) >
+      self.children[1].evaluate(context)
+  )
+@method(NodeExpressionGE)
+def evaluate(self, context):
+  return float(
+    self.children[0].evaluate(context) >=
+      self.children[1].evaluate(context)
+  )
+@method(NodeExpressionLE)
+def evaluate(self, context):
+  return float(
+    self.children[0].evaluate(context) <=
+      self.children[1].evaluate(context)
+  )
+@method(NodeExpressionNE)
+def evaluate(self, context):
+  return float(
+    self.children[0].evaluate(context) !=
+      self.children[1].evaluate(context)
+  )
+@method(NodeExpressionAdd)
+def evaluate(self, context):
+  return (
+    self.children[0].evaluate(context) +
+      self.children[1].evaluate(context)
+  )
+@method(NodeExpressionSubtract)
+def evaluate(self, context):
+  return (
+    self.children[0].evaluate(context) -
+      self.children[1].evaluate(context)
+  )
+@method(NodeExpressionMultiply)
+def evaluate(self, context):
+  return (
+    self.children[0].evaluate(context) *
+      self.children[1].evaluate(context)
+  )
+@method(NodeExpressionDivide)
+def evaluate(self, context):
+  return (
+    self.children[0].evaluate(context) /
+      self.children[1].evaluate(context)
+  )
+@method(NodeExpressionPower)
+def evaluate(self, context):
+  return (
+    self.children[0].evaluate(context) **
+      self.children[1].evaluate(context)
+  )
+@method(NodeExpressionSign)
+def evaluate(self, context):
+  return self.sign * self.children[0].evaluate(context)
+@method(NodeExpressionNot)
+def evaluate(self, context):
+  return float(
+    self.children[0].evaluate(context) == 0.
+  )
+@method(NodeExpressionIntLiteral)
+def evaluate(self, context):
+  return float(self.children[0].int_value)
+@method(NodeExpressionFloatLiteral)
+def evaluate(self, context):
+  return self.children[0].float_value
+@method(NodeExpressionStrLiteral)
+def evaluate(self, context):
+  return self.children[0].text[0]
+@method(NodeExpressionVariable)
+def evaluate(self, context):
+  name = self.children[0].str_value
+  if name[-1] == '$':
+    value = context.variables.get(name, '')
+  elif name[-1] == '%':
+    value = float(context.variables.get(name, 0))
+  else:
+    value = context.variables.get(name, 0.)
+  return value
+del evaluate
diff --git a/applesoft_basic.y b/applesoft_basic.y
new file mode 100644 (file)
index 0000000..c2a5698
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2018 Nick Downing <nick@ndcode.org>
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+%{
+  import t_def
+  import sys
+%}
+
+%locations
+
+%token KEYWORD_ABS
+%token KEYWORD_AND
+%token KEYWORD_ASC
+%token KEYWORD_AT
+%token KEYWORD_ATN
+%token KEYWORD_CALL
+%token KEYWORD_CHR_DOLLAR
+%token KEYWORD_CLEAR
+%token KEYWORD_COLOR_EQUAL
+%token KEYWORD_CONT
+%token KEYWORD_COS
+%token KEYWORD_DATA
+%token KEYWORD_DEF
+%token KEYWORD_DEL
+%token KEYWORD_DIM
+%token KEYWORD_DRAW
+%token KEYWORD_END
+%token KEYWORD_EXP
+%token KEYWORD_FLASH
+%token KEYWORD_FN
+%token KEYWORD_FOR
+%token KEYWORD_FRE
+%token KEYWORD_GET
+%token KEYWORD_GO
+%token KEYWORD_GOTO
+%token KEYWORD_GR
+%token KEYWORD_HCOLOR_EQUAL
+%token KEYWORD_HGR
+%token KEYWORD_HGR2
+%token KEYWORD_HIMEM_COLON
+%token KEYWORD_HLIN
+%token KEYWORD_HOME
+%token KEYWORD_HPLOT
+%token KEYWORD_HTAB
+%token KEYWORD_IF
+%token KEYWORD_IN_FLOAT_LITERAL
+%token KEYWORD_INPUT
+%token KEYWORD_INT
+%token KEYWORD_INVERSE
+%token KEYWORD_LEFT_DOLLAR
+%token KEYWORD_LEN
+%token KEYWORD_LET
+%token KEYWORD_LIST
+%token KEYWORD_LOAD
+%token KEYWORD_LOG
+%token KEYWORD_LOMEM_COLON
+%token KEYWORD_MID_DOLLAR
+%token KEYWORD_NEW
+%token KEYWORD_NEXT
+%token KEYWORD_NORMAL
+%token KEYWORD_NOT
+%token KEYWORD_NOTRACE
+%token KEYWORD_ON
+%token KEYWORD_ONERR
+%token KEYWORD_OR
+%token KEYWORD_PDL
+%token KEYWORD_PEEK
+%token KEYWORD_PLOT
+%token KEYWORD_POKE
+%token KEYWORD_POP
+%token KEYWORD_POS
+%token KEYWORD_PR_FLOAT_LITERAL
+%token KEYWORD_PRINT
+%token KEYWORD_READ
+%token KEYWORD_RECALL
+%token KEYWORD_REM
+%token KEYWORD_RESTORE
+%token KEYWORD_RESUME
+%token KEYWORD_RETURN
+%token KEYWORD_RIGHT_DOLLAR
+%token KEYWORD_SAVE
+%token KEYWORD_SCALE_EQUAL
+%token KEYWORD_SCRN_LPAREN
+%token KEYWORD_SGN
+%token KEYWORD_SHLOAD
+%token KEYWORD_SIN
+%token KEYWORD_SPC_LPAREN
+%token KEYWORD_SPEED_EQUAL
+%token KEYWORD_SQR
+%token KEYWORD_STEP
+%token KEYWORD_STOP
+%token KEYWORD_STORE
+%token KEYWORD_STR_DOLLAR
+%token KEYWORD_TAB_LPAREN
+%token KEYWORD_TAN
+%token KEYWORD_TEXT
+%token KEYWORD_THEN
+%token KEYWORD_TO
+%token KEYWORD_TRACE
+%token KEYWORD_USR
+%token KEYWORD_VAL
+%token KEYWORD_VLIN
+%token KEYWORD_VTAB
+%token KEYWORD_WAIT
+%token KEYWORD_XDRAW
+%token KEYWORD_XPLOT
+%token INT_LITERAL
+%token FLOAT_LITERAL
+%token OPERATOR_GE
+%token OPERATOR_LE
+%token OPERATOR_NE
+%token STR_LITERAL
+%token VARIABLE
+
+%left KEYWORD_OR
+%left KEYWORD_AND
+%left '<' '=' '>' OPERATOR_GE OPERATOR_LE OPERATOR_NE
+%left '+' '-'
+%left '*' '/'
+%left '^'
+%right UNARY KEYWORD_NOT
+
+%%
+
+program
+  :
+  | program %space (?E{t_def.NodeLine}INT_LITERAL statement_list) '\n'
+  | error '\n' {
+    yyerror('Syntax error\n')
+    yyerrok()
+  }
+  ;
+
+statement_list
+  : statement_opt
+  | statement_list ':' statement_opt
+  ;
+
+statement_opt
+  :
+  | %space (?E{t_def.NodeStatementLet}VARIABLE '=' expression)
+  | %space (?E{t_def.NodeStatementLet}KEYWORD_LET VARIABLE '=' expression)
+  | %space (?E{t_def.NodeStatementPrint, semicolon = False}KEYWORD_PRINT print_expression_list0)
+  | %space (?E{t_def.NodeStatementPrint, semicolon = True}KEYWORD_PRINT print_expression_list1)
+  | %space (?E{t_def.NodeStatementGoto}KEYWORD_GOTO INT_LITERAL)
+  ;
+
+print_expression_list0
+  :
+  | print_expression_list0 expression
+  | print_expression_list1 expression
+  ;
+
+print_expression_list1
+  : print_expression_list0 ';'
+  | print_expression_list1 ';'
+  ;
+
+expression
+  : %space (?E{t_def.NodeExpressionOr}expression KEYWORD_OR expression)
+  | %space (?E{t_def.NodeExpressionAnd}expression KEYWORD_AND expression)
+  | %space (?E{t_def.NodeExpressionLT}expression '<' expression)
+  | %space (?E{t_def.NodeExpressionEqual}expression '=' expression)
+  | %space (?E{t_def.NodeExpressionGT}expression '>' expression)
+  | %space (?E{t_def.NodeExpressionGE}expression OPERATOR_GE expression)
+  | %space (?E{t_def.NodeExpressionLE}expression OPERATOR_LE expression)
+  | %space (?E{t_def.NodeExpressionNE}expression OPERATOR_NE expression)
+  | %space (?E{t_def.NodeExpressionAdd}expression '+' expression)
+  | %space (?E{t_def.NodeExpressionSubtract}expression '-' expression)
+  | %space (?E{t_def.NodeExpressionMultiply}expression '*' expression)
+  | %space (?E{t_def.NodeExpressionDivide}expression '/' expression)
+  | %space (?E{t_def.NodeExpressionPower}expression '^' expression)
+  | %space (?E{t_def.NodeExpressionSign, sign = -1}'-' expression) %prec UNARY
+  | %space (?E{t_def.NodeExpressionSign, sign = 1}'+' expression) %prec UNARY
+  | %space (?E{t_def.NodeExpressionNot}KEYWORD_NOT expression)
+  | '(' expression ')'
+  | %space (?E{t_def.NodeExpressionIntLiteral}INT_LITERAL)
+  | %space (?E{t_def.NodeExpressionFloatLiteral}FLOAT_LITERAL)
+  | %space (?E{t_def.NodeExpressionStrLiteral}STR_LITERAL)
+  | %space (?E{t_def.NodeExpressionVariable}VARIABLE)
+  ;
+%%
+
+def yyerror(s):
+  sys.stdout.write('{0:s}\n'.format(s))
+  sys.exit(1)
diff --git a/test.bas b/test.bas
new file mode 100644 (file)
index 0000000..18ed223
--- /dev/null
+++ b/test.bas
@@ -0,0 +1,4 @@
+10 I$="PI":I%=-3.141:I=3.141
+20 PRINT I$" "I%" "I
+30 I%=I%+1
+40 GOTO 20