Add /deps.sh, in /linapple remove full-screen mode to make it run properly on recent...
[applesoft_basic.git] / applesoft_basic.t
1 /*
2  * Copyright (C) 2022 Nick Downing <nick@ndcode.org>
3  * SPDX-License-Identifier: GPL-2.0-only
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the Free
7  * Software Foundation; version 2.
8  *
9  * This program is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12  * more details.
13  *
14  * You should have received a copy of the GNU General Public License along with
15  * this program; if not, write to the Free Software Foundation, Inc., 51
16  * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18
19 %{
20   import apple_io
21   import data_types
22   import element
23   import gc
24   import math
25   import random
26   import time
27
28   # globals
29   random.seed(time.time())
30   last_random = random.random()
31   inter_statement_delay = 0.
32
33   FOR_NEXT_DELAY_HZ = 1080. # empty for/next loops per sec
34   FOR_NEXT_CLICK_HZ = 540. # clicks within a for/next loop
35   STATEMENT_CLICK_HZ = 1620. # clicks within a single statement
36 %}
37
38 %%
39
40 /* generic */
41 class Node;
42 class Text: Node {
43   class Char;
44   str str_value = '';
45 };
46
47 /* lexical tokens */
48 class IntLiteral: Text {
49   int int_value = 0;
50 };
51 class FloatLiteral: Node {
52   class Sign: Text;
53   class Integer: Text;
54   class Fraction: Text;
55   class Exponent: Node;
56   float float_value = 0.;
57 };
58 class StrLiteral: Node;
59 class VariableName: Text;
60
61 /* grammar productions */
62 class Program: Node;
63 class Line: Node;
64 class Statement: Node;
65 class StatementEnd: Statement;
66 class StatementFor: Statement;
67 class StatementNext: Statement;
68 class StatementData: Statement;
69 class StatementInput: Statement;
70 class StatementDel: Statement;
71 class StatementDim: Statement;
72 class StatementRead: Statement;
73 class StatementGr: Statement;
74 class StatementText: Statement;
75 class StatementPrHash: Statement;
76 class StatementInHash: Statement;
77 class StatementCall: Statement;
78 class StatementPlot: Statement;
79 class StatementHLin: Statement;
80 class StatementVLin: Statement;
81 class StatementHGR2: Statement;
82 class StatementHGR: Statement;
83 class StatementHColorEqual: Statement;
84 class StatementHPlot: Statement;
85 class StatementDraw: Statement;
86 class StatementXDraw: Statement;
87 class StatementHTab: Statement;
88 class StatementHome: Statement;
89 class StatementRotEqual: Statement;
90 class StatementScaleEqual: Statement;
91 class StatementShLoad: Statement;
92 class StatementTrace: Statement;
93 class StatementNoTrace: Statement;
94 class StatementNormal: Statement;
95 class StatementInverse: Statement;
96 class StatementFlash: Statement;
97 class StatementColorEqual: Statement;
98 class StatementPop: Statement;
99 class StatementVTab: Statement;
100 class StatementHimemColon: Statement;
101 class StatementLomemColon: Statement;
102 class StatementOnErr: Statement;
103 class StatementResume: Statement;
104 class StatementRecall: Statement;
105 class StatementStore: Statement;
106 class StatementSpeedEqual: Statement;
107 class StatementLet: Statement;
108 class StatementGoto: Statement;
109 class StatementRun: Statement;
110 class StatementIf: Statement;
111 class StatementRestore: Statement;
112 class StatementAmpersand: Statement;
113 class StatementGosub: Statement;
114 class StatementReturn: Statement;
115 class StatementRem: Statement;
116 class StatementStop: Statement;
117 class StatementOnGoto: Statement;
118 class StatementOnGosub: Statement;
119 class StatementWait: Statement;
120 class StatementLoad: Statement;
121 class StatementSave: Statement;
122 class StatementDef: Statement;
123 class StatementPoke: Statement;
124 class StatementPrint: Statement {
125   bool semicolon;
126 };
127 class StatementCont: Statement;
128 class StatementList: Statement;
129 class StatementClear: Statement;
130 class StatementGet: Statement;
131 class StatementNew: Statement;
132 class DataText: Node;
133 class DimItem: Node;
134 class RemText: Node;
135 class RValue: Node;
136 class RValueStrLiteral: RValue;
137 class RValueFloatLiteral: RValue;
138 class RValueIntLiteral: RValue;
139 class RValueTabLParen: RValue;
140 class RValueSpcLParen: RValue;
141 class RValueNot: RValue;
142 class RValueSign: RValue {
143   int sign;
144 };
145 class RValueAdd: RValue;
146 class RValueSubtract: RValue;
147 class RValueMultiply: RValue;
148 class RValueDivide: RValue;
149 class RValuePower: RValue;
150 class RValueAnd: RValue;
151 class RValueOr: RValue;
152 class RValueGT: RValue;
153 class RValueEqual: RValue;
154 class RValueLT: RValue;
155 class RValueGE: RValue;
156 class RValueLE: RValue;
157 class RValueNE: RValue;
158 class RValueSgn: RValue;
159 class RValueInt: RValue;
160 class RValueAbs: RValue;
161 class RValueUsr: RValue;
162 class RValueFre: RValue;
163 class RValueScrnLParen: RValue;
164 class RValuePdl: RValue;
165 class RValuePos: RValue;
166 class RValueSqr: RValue;
167 class RValueRnd: RValue;
168 class RValueLog: RValue;
169 class RValueExp: RValue;
170 class RValueCos: RValue;
171 class RValueSin: RValue;
172 class RValueTan: RValue;
173 class RValueAtn: RValue;
174 class RValuePeek: RValue;
175 class RValueLen: RValue;
176 class RValueStrDollar: RValue;
177 class RValueVal: RValue;
178 class RValueAsc: RValue;
179 class RValueChrDollar: RValue;
180 class RValueLeftDollar: RValue;
181 class RValueRightDollar: RValue;
182 class RValueMidDollar: RValue;
183 class LValue: RValue;
184 class LValueVariable: LValue;
185 class LValueArray: LValue;
186
187 %%
188
189 # Copyright (C) 2018 Nick Downing <nick@ndcode.org>
190 # SPDX-License-Identifier: GPL-2.0-only
191 #
192 # This program is free software; you can redistribute it and/or modify it under
193 # the terms of the GNU General Public License as published by the Free Software
194 # Foundation; version 2.
195 #
196 # This program is distributed in the hope that it will be useful, but WITHOUT
197 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
198 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
199 # details.
200 #
201 # You should have received a copy of the GNU General Public License along with
202 # this program; if not, write to the Free Software Foundation, Inc., 51
203 # Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
204
205 class EndException(Exception):
206   pass
207
208 class ReturnException(Exception):
209   pass
210
211 class NextException(Exception):
212   pass
213
214 # variables are boxed for easy referencing and/or subscripting
215 class Variable:
216   def __init__(self, value):
217     self.value = value
218
219 class Context:
220   def __init__(
221     self,
222     program = None,
223     variables = None,
224     arrays = None,
225     i = 0,
226     j = 1,
227     k = 0,
228     l = 1,
229     m = 0,
230     clicks = 0,
231     onerr = -1
232   ):
233     self.program = program if program is not None else Program()
234     self.variables = variables if variables is not None else {}
235     self.arrays = arrays if arrays is not None else {}
236
237     # execute pointer (line index, statement index)
238     self.i = i
239     self.j = j
240
241     # read pointer (line index, statement index, item index)
242     self.k = k
243     self.l = l
244     self.m = m
245
246     # count peek(-16336) within a statement, convert to a tone
247     self.clicks = clicks
248
249     # program index for ONERR GOTO (not used for anything yet)
250     self.onerr = onerr
251
252   def run(self):
253     try:
254       self.execute()
255     except EndException:
256       pass
257     except ReturnException:
258       raise Exception(
259         f'?RETURN WITHOUT GOSUB ERROR IN {self.line_number():d}'
260       )
261     except NextException:
262       raise Exception(
263         f'?NEXT WITHOUT FOR ERROR IN {self.line_number():d}'
264       )
265
266   def line_number(self):
267     assert self.i < len(self.program.children)
268     return self.program.children[self.i].children[0].int_value
269
270   def execute(self):
271     while self.i < len(self.program.children):
272       line = self.program.children[self.i]
273       if self.j < len(line.children):
274         statement = line.children[self.j]
275         self.j += 1
276         statement.execute(self)
277         if self.clicks:
278           apple_io.tone(
279             STATEMENT_CLICK_HZ,
280             self.clicks * 2 / STATEMENT_CLICK_HZ
281           )
282           self.clicks = 0
283         if inter_statement_delay != 0:
284           time.sleep(inter_statement_delay)
285       else:
286         self.i += 1
287         self.j = 1
288     raise EndException()
289
290   def find_line(self, target):
291     for i in range(len(self.program.children)):
292       if target == self.program.children[i].children[0].int_value:
293         return i
294     raise Exception(
295       f'?UNDEF\'D STATEMENT ERROR IN {self.line_number():d}'
296     )
297
298   def find_variable(self, name):
299     # variable is created and initialized to 0 if not existent
300     variable = self.variables.get(name)
301     if variable is None:
302       null_value = '' if name[-1] == '$' else 0 if name[-1] == '%' else 0.
303       variable = Variable(null_value)
304       self.variables[name] = variable
305     return variable
306
307   def find_array(self, name, n_dim):
308     # array is created with 11^n_dim zeroed entries if non-existent
309     array = self.arrays.get(name)
310     if array is None:
311       null_value = '' if name[-1] == '$' else 0 if name[-1] == '%' else 0.
312       def create_array(i):
313         return (
314           Variable(null_value)
315         if i >= n_dim else
316           [create_array(i + 1) for j in range(11)]
317         )
318       array = create_array(0)
319       self.arrays[name] = array
320     return array
321
322 def factory(tag, *args, **kwargs):
323   return tag_to_class[tag](*args, **kwargs)
324
325 @method(Node)
326 def post_process(self, program):
327   for i in self.children:
328     i.post_process(program)
329 @method(Text)
330 def post_process(self, program):
331   self.str_value = ''.join([i.text[0] for i in self.children])
332 @method(IntLiteral)
333 def post_process(self, program):
334   Text.post_process(self, program)
335   self.int_value = int(self.str_value)
336 del post_process
337 @method(FloatLiteral)
338 def post_process(self, program):
339   Node.post_process(self, program)
340   exponent_value = 0
341   if len(self.children) >= 3:
342     exponent = self.children[2]
343     exponent_value = int(exponent.children[1].str_value)
344     if exponent.children[0].str_value == '-':
345       exponent_value = -exponent_value
346   value = (
347     int(self.children[0].str_value + self.children[1].str_value) *
348       10. ** (exponent_value - len(self.children[1].str_value))
349   )
350   self.float_value = value
351 del post_process
352
353 @method(Statement)
354 def execute(self, context):
355   raise NotImplementedError()
356 @method(StatementEnd)
357 def execute(self, context):
358   raise EndException()
359 @method(StatementFor)
360 def execute(self, context):
361   name = self.children[0].str_value
362   # note: if the loop index is an integer, we will print a type
363   # mismatch error, whereas applesoft would print a syntax error
364   if name[-1] in '$%':
365     raise Exception(
366       f'?TYPE MISMATCH ERROR IN {context.line_number():d}'
367     )
368   variable = context.find_variable(name)
369
370   variable.value = self.children[1].get_float(context)
371   end = self.children[2].get_float(context)
372   step = (
373     self.children[3].get_float(context)
374   if len(self.children) >= 4 else
375     1.
376   )
377
378   # heuristically detect delay loops
379   if step != 0.:
380     i = context.i
381     j = context.j
382     clicks = 0
383     while i < len(context.program.children):
384       line = context.program.children[i]
385       if j < len(line.children):
386         statement = line.children[j]
387         j += 1
388         if (
389           isinstance(statement, StatementLet) and
390             isinstance(statement.children[1], RValuePeek) and
391             isinstance(statement.children[1].children[0], RValueSign) and
392             statement.children[1].children[0].sign == -1 and
393             isinstance(statement.children[1].children[0].children[0], RValueIntLiteral) and
394             statement.children[1].children[0].children[0].children[0].int_value == 16336
395         ):
396           clicks += 1
397         elif (
398           isinstance(statement, StatementNext) and (
399             len(statement.children) < 1 or
400               statement.children[0].str_value == name
401           )
402         ):
403           counts = math.floor((end - variable.value) / step) + 1
404           if clicks:
405             apple_io.tone(
406               FOR_NEXT_CLICK_HZ,
407               clicks * counts * 2 / FOR_NEXT_CLICK_HZ
408             )
409           else:
410             time.sleep(counts / FOR_NEXT_DELAY_HZ)
411           context.i = i
412           context.j = j
413           return
414         else:
415           break
416       else:
417         i += 1
418         j = 1
419
420   i = context.i
421   j = context.j
422   while True:
423     try:
424       context.execute()
425     except NextException:
426       pass
427
428     assert context.i < len(context.program.children)
429     line = context.program.children[context.i]
430     assert context.j > 1 and context.j <= len(line.children)
431     statement = line.children[context.j - 1]
432     assert isinstance(statement, StatementNext)
433     if (
434       len(statement.children) >= 1 and
435         statement.children[0].str_value != name
436     ):
437       raise Exception(
438         f'?NEXT WITHOUT FOR ERROR IN {context.line_number():d}'
439       )
440
441     variable.value += step
442     if (variable.value > end if step >= 0 else variable.value < end):
443       break
444
445     context.i = i
446     context.j = j
447 @method(StatementNext)
448 def execute(self, context):
449   raise NextException()
450 @method(StatementData)
451 def execute(self, context):
452   pass
453 @method(StatementInput)
454 def execute(self, context):
455   while True:
456     apple_io._print(
457       self.children[-2].text[0]
458     if len(self.children) >= 2 else
459       '?'
460     )
461     value = apple_io.input()
462     lvalue = self.children[-1]
463     name = lvalue.children[0].str_value
464     if name[-1] != '$':
465       n, result = data_types.val(value)
466       if n == 0 or n < len(value):
467         apple_io._print('?REENTER\r')
468         continue
469       value = result
470       if name[-1] == '%':
471         value = data_types.cint(value)
472     lvalue.find_variable(context).value = value
473     break
474 @method(StatementDel)
475 def execute(self, context):
476   assert False
477 @method(StatementDim)
478 def execute(self, context):
479   for i in self.children:
480     dim = [j.get_int(context) + 1 for j in i.children[1:]]
481
482     # we don't check for an existing array until here, as it could be
483     # created with the default 11 elements while evaluating dimensions
484     name = i.children[0].str_value
485     if name in context.arrays:
486       raise Exception(
487         f'?REDIM\'D ARRAY ERROR IN {context.line_number():d}'
488       )
489
490     null_value = '' if name[-1] == '$' else 0 if name[-1] == '%' else 0.
491     def create_array(j):
492       return (
493         Variable(null_value)
494       if j >= len(dim) else
495         [create_array(j + 1) for k in range(dim[j])]
496       )
497     context.arrays[name] = create_array(0)
498 @method(StatementRead)
499 def execute(self, context):
500   i = 0
501   while context.k < len(context.program.children):
502     line = context.program.children[context.k]
503     while context.l < len(line.children):
504       statement = line.children[context.l]
505       if isinstance(statement, StatementData):
506         while context.m < len(statement.children):
507           item = statement.children[context.m]
508           context.m += 1
509           value = item.text[0]
510           lvalue = self.children[i]
511           i += 1
512           name = lvalue.children[0].str_value
513           if name[-1] != '$':
514             n, result = data_types.val(value)
515             if n < len(value):
516               raise Exception(
517                 f'?SYNTAX ERROR IN {context.line_number():d}'
518               )
519             value = result
520             if name[-1] == '%':
521               value = data_types.cint(value)
522           lvalue.find_variable(context).value = value
523           if i >= len(self.children):
524             return
525       context.l += 1
526       context.m = 0
527     context.k += 1
528     context.l = 1
529     assert context.m == 0
530   raise Exception(
531     f'?OUT OF DATA ERROR IN {context.line_number():d}'
532   )
533 @method(StatementGr)
534 def execute(self, context):
535   apple_io.gr()
536 @method(StatementText)
537 def execute(self, context):
538   apple_io.text()
539 @method(StatementPrHash)
540 def execute(self, context):
541   value = self.children[0].get_int(context)
542   apple_io.pr_hash(value)
543 @method(StatementInHash)
544 def execute(self, context):
545   value = self.children[0].get_int(context)
546   apple_io.in_hash(value)
547 @method(StatementCall)
548 def execute(self, context):
549   value = self.children[0].get_int(context)
550   apple_io.call(value)
551 @method(StatementPlot)
552 def execute(self, context):
553   value0 = self.children[0].get_int(context)
554   value1 = self.children[1].get_int(context)
555   apple_io.plot(value0, value1)
556 @method(StatementHLin)
557 def execute(self, context):
558   value0 = self.children[0].get_int(context)
559   value1 = self.children[1].get_int(context)
560   value2 = self.children[2].get_int(context)
561   apple_io.hlin(value0, value1, value2)
562 @method(StatementVLin)
563 def execute(self, context):
564   value0 = self.children[0].get_int(context)
565   value1 = self.children[1].get_int(context)
566   value2 = self.children[2].get_int(context)
567   apple_io.vlin(value0, value1, value2)
568 @method(StatementHGR2)
569 def execute(self, context):
570   assert False
571 @method(StatementHGR)
572 def execute(self, context):
573   # for ribbit (HRCG)
574   pass
575 @method(StatementHColorEqual)
576 def execute(self, context):
577   assert False
578 @method(StatementHPlot)
579 def execute(self, context):
580   assert False
581 @method(StatementDraw)
582 def execute(self, context):
583   assert False
584 @method(StatementXDraw)
585 def execute(self, context):
586   assert False
587 @method(StatementHTab)
588 def execute(self, context):
589   value = self.children[0].get_int(context)
590   apple_io.htab(value)
591 @method(StatementHome)
592 def execute(self, context):
593   apple_io.home()
594 @method(StatementRotEqual)
595 def execute(self, context):
596   assert False
597 @method(StatementScaleEqual)
598 def execute(self, context):
599   assert False
600 @method(StatementShLoad)
601 def execute(self, context):
602   assert False
603 @method(StatementTrace)
604 def execute(self, context):
605   assert False
606 @method(StatementNoTrace)
607 def execute(self, context):
608   assert False
609 @method(StatementNormal)
610 def execute(self, context):
611   apple_io.normal()
612 @method(StatementInverse)
613 def execute(self, context):
614   apple_io.inverse()
615 @method(StatementFlash)
616 def execute(self, context):
617   apple_io.flash()
618 @method(StatementColorEqual)
619 def execute(self, context):
620   value = self.children[0].get_int(context)
621   apple_io.color(value)
622 @method(StatementPop)
623 def execute(self, context):
624   assert False
625 @method(StatementVTab)
626 def execute(self, context):
627   value = self.children[0].get_int(context)
628   apple_io.vtab(value)
629 @method(StatementHimemColon)
630 def execute(self, context):
631   value = self.children[0].get_int(context)
632   apple_io.himem(value)
633 @method(StatementLomemColon)
634 def execute(self, context):
635   value = self.children[0].get_int(context)
636   apple_io.lomem(value)
637 @method(StatementOnErr)
638 def execute(self, context):
639   context.onerr = context.find_line(self.children[0].int_value)
640 @method(StatementResume)
641 def execute(self, context):
642   assert False
643 @method(StatementRecall)
644 def execute(self, context):
645   assert False
646 @method(StatementStore)
647 def execute(self, context):
648   assert False
649 @method(StatementSpeedEqual)
650 def execute(self, context):
651   value = self.children[0].get_int(context)
652   if value < 0:
653     raise Exception(
654       f'?ILLEGAL QUANTITY ERROR IN {context.line_number():d}'
655     )
656   apple_io.speed(value)
657 @method(StatementLet)
658 def execute(self, context):
659   value = self.children[1].get(context)
660   self.children[0].set(context, value)
661 @method(StatementGoto)
662 def execute(self, context):
663   context.i = context.find_line(self.children[0].int_value)
664   context.j = 1
665 @method(StatementRun)
666 def execute(self, context):
667   assert False
668 @method(StatementIf)
669 def execute(self, context):
670   value = self.children[0].get(context)
671   if value == '' or value == 0.:
672     context.i += 1
673     context.j = 1
674 @method(StatementRestore)
675 def execute(self, context):
676   context.k = 0
677   context.l = 1
678   context.m = 0
679 @method(StatementAmpersand)
680 def execute(self, context):
681   assert False
682 @method(StatementGosub)
683 def execute(self, context):
684   i = context.i
685   j = context.j
686   context.i = context.find_line(self.children[0].int_value)
687   context.j = 1
688   try:
689     context.execute()
690   except ReturnException:
691     pass
692   except NextException:
693     raise Exception(
694       f'?NEXT WITHOUT FOR ERROR IN {context.line_number():d}'
695     )
696   context.i = i
697   context.j = j
698 @method(StatementReturn)
699 def execute(self, context):
700   raise ReturnException()
701 @method(StatementRem)
702 def execute(self, context):
703   pass
704 @method(StatementStop)
705 def execute(self, context):
706   assert False
707 @method(StatementOnGoto)
708 def execute(self, context):
709   value = self.children[0].get_int(context)
710   if value < 0:
711     raise Exception(
712       f'?ILLEGAL QUANTITY ERROR IN {context.line_number():d}'
713     )
714   if value >= 1 and value < len(self.children):
715     context.i = context.find_line(self.children[value].int_value)
716     context.j = 1
717 @method(StatementOnGosub)
718 def execute(self, context):
719   value = self.children[0].get_int(context)
720   if value < 0:
721     raise Exception(
722       f'?ILLEGAL QUANTITY ERROR IN {context.line_number():d}'
723     )
724   if value >= 1 and value < len(self.children):
725     i = context.i
726     j = context.j
727     context.i = context.find_line(self.children[value].int_value)
728     context.j = 1
729     try:
730       context.execute()
731     except ReturnException:
732       pass
733     except NextException:
734       raise Exception(
735         f'?NEXT WITHOUT FOR ERROR IN {context.line_number():d}'
736       )
737     context.i = i
738     context.j = j
739 @method(StatementWait)
740 def execute(self, context):
741   assert False
742 @method(StatementLoad)
743 def execute(self, context):
744   assert False
745 @method(StatementSave)
746 def execute(self, context):
747   assert False
748 @method(StatementDef)
749 def execute(self, context):
750   assert False
751 @method(StatementPoke)
752 def execute(self, context):
753   value0 = self.children[0].get_int(context)
754   value1 = self.children[1].get_int(context)
755   apple_io.poke(value0, value1)
756 @method(StatementPrint)
757 def execute(self, context):
758   for i in self.children:
759     value = i.get(context)
760     if isinstance(value, float):
761       value = data_types.str_dollar(context, value)
762     apple_io._print(value)
763   if not self.semicolon:
764     apple_io._print('\r')
765 @method(StatementCont)
766 def execute(self, context):
767   assert False
768 @method(StatementList)
769 def execute(self, context):
770   assert False
771 @method(StatementClear)
772 def execute(self, context):
773   assert False
774 @method(StatementGet)
775 def execute(self, context):
776   value = apple_io.get()
777   # note: if the lvalue isn't a string then we will print a type
778   # mismatch error, whereas applesoft would print a syntax error
779   self.children[0].set(context, value)
780 @method(StatementNew)
781 def execute(self, context):
782   assert False
783 del execute
784
785 @method(RValue)
786 def get(self, context):
787   raise NotImplementedError()
788 @method(RValueStrLiteral)
789 def get(self, context):
790   return self.children[0].text[0]
791 @method(RValueFloatLiteral)
792 def get(self, context):
793   return self.children[0].float_value
794 @method(RValueIntLiteral)
795 def get(self, context):
796   return float(self.children[0].int_value)
797 @method(RValueTabLParen)
798 def get(self, context):
799   value = self.children[0].get_int(context)
800   # strangely, pos() is 0-based whereas tab() is 1-based
801   value -= 1 + apple_io.pos()
802   return  ' ' * value if value >= 1 else ''
803 @method(RValueSpcLParen)
804 def get(self, context):
805   value = self.children[0].get_int(context)
806   if value < 0:
807     raise Exception(
808       f'?ILLEGAL QUANTITY ERROR IN {context.line_number():d}'
809     )
810   return ' ' * value
811 @method(RValueNot)
812 def get(self, context):
813   value = self.children[0].get_float(context)
814   return float(value == 0.)
815 @method(RValueSign)
816 def get(self, context):
817   value = self.children[0].get_float(context)
818   return self.sign * value
819 @method(RValueAdd)
820 def get(self, context):
821   value0 = self.children[0].get(context)
822   value1 = (
823     self.children[1].get_float(context)
824   if isinstance(value0, float) else
825     self.children[1].get_str(context)
826   )
827   return value0 + value1
828 @method(RValueSubtract)
829 def get(self, context):
830   value0 = self.children[0].get_float(context)
831   value1 = self.children[1].get_float(context)
832   return value0 - value1
833 @method(RValueMultiply)
834 def get(self, context):
835   value0 = self.children[0].get_float(context)
836   value1 = self.children[1].get_float(context)
837   return value0 * value1
838 @method(RValueDivide)
839 def get(self, context):
840   value0 = self.children[0].get_float(context)
841   value1 = self.children[1].get_float(context)
842   return value0 / value1
843 @method(RValuePower)
844 def get(self, context):
845   value0 = self.children[0].get_float(context)
846   value1 = self.children[1].get_float(context)
847   return value0 ** value1
848 @method(RValueAnd)
849 def get(self, context):
850   value0 = self.children[0].get_float(context)
851   value1 = self.children[1].get_float(context)
852   return float(value0 != 0. and value1 != 0.)
853 @method(RValueOr)
854 def get(self, context):
855   value0 = self.children[0].get_float(context)
856   value1 = self.children[1].get_float(context)
857   return float(value0 != 0. or value1 != 0.)
858 @method(RValueGT)
859 def get(self, context):
860   value0 = self.children[0].get(context)
861   value1 = (
862     self.children[1].get_float(context)
863   if isinstance(value0, float) else
864     self.children[1].get_str(context)
865   )
866   return float(value0 > value1)
867 @method(RValueEqual)
868 def get(self, context):
869   value0 = self.children[0].get(context)
870   value1 = (
871     self.children[1].get_float(context)
872   if isinstance(value0, float) else
873     self.children[1].get_str(context)
874   )
875   return float(value0 == value1)
876 @method(RValueLT)
877 def get(self, context):
878   value0 = self.children[0].get(context)
879   value1 = (
880     self.children[1].get_float(context)
881   if isinstance(value0, float) else
882     self.children[1].get_str(context)
883   )
884   return float(value0 < value1)
885 @method(RValueGE)
886 def get(self, context):
887   value0 = self.children[0].get(context)
888   value1 = (
889     self.children[1].get_float(context)
890   if isinstance(value0, float) else
891     self.children[1].get_str(context)
892   )
893   return float(value0 >= value1)
894 @method(RValueLE)
895 def get(self, context):
896   value0 = self.children[0].get(context)
897   value1 = (
898     self.children[1].get_float(context)
899   if isinstance(value0, float) else
900     self.children[1].get_str(context)
901   )
902   return float(value0 <= value1)
903 @method(RValueNE)
904 def get(self, context):
905   value0 = self.children[0].get(context)
906   value1 = (
907     self.children[1].get_float(context)
908   if isinstance(value0, float) else
909     self.children[1].get_str(context)
910   )
911   return float(value0 != value1)
912 @method(RValueSgn)
913 def get(self, context):
914   value = self.children[0].get_float(context)
915   return -1. if value < 0. else 1. if value > 0. else 0.
916 @method(RValueInt)
917 def get(self, context):
918   value = self.children[0].get_float(context)
919   return float(math.floor(value))
920 @method(RValueAbs)
921 def get(self, context):
922   value = self.children[0].get_float(context)
923   return abs(value)
924 @method(RValueUsr)
925 def get(self, context):
926   value = self.children[0].get(context)
927   value = apple_io.usr(value)
928   if isinstance(value, int):
929     value = float(value)
930   return value
931 @method(RValueFre)
932 def get(self, context):
933   gc.collect()
934   return -29188. # value I get from a freshly booted system
935 @method(RValueScrnLParen)
936 def get(self, context):
937   value0 = self.children[0].get_int(context)
938   value1 = self.children[1].get_int(context)
939   return float(apple_io.scrn(value0, value1))
940 @method(RValuePdl)
941 def get(self, context):
942   value = self.children[0].get_int(context)
943   if value < 0 or value >= 4:
944     raise Exception(
945       f'?ILLEGAL QUANTITY ERROR IN {context.line_number():d}'
946     )
947   return float(apple_io.pdl(value))
948 @method(RValuePos)
949 def get(self, context):
950   return float(apple_io.pos())
951 @method(RValueSqr)
952 def get(self, context):
953   value = self.children[0].get_float(context)
954   if value < 0.:
955     raise Exception(
956       f'?ILLEGAL QUANTITY ERROR IN {context.line_number():d}'
957     )
958   return math.sqrt(value)
959 @method(RValueRnd)
960 def get(self, context):
961   value = self.children[0].get_float(context)
962   if value:
963     if value < 0.:
964       random.seed(value)
965     last_random = random.random()
966   return last_random
967 @method(RValueLog)
968 def get(self, context):
969   value = self.children[0].get_float(context)
970   if value <= 0.:
971     raise Exception(
972       f'?ILLEGAL QUANTITY ERROR IN {context.line_number():d}'
973     )
974   return math.log(value)
975 @method(RValueExp)
976 def get(self, context):
977   value = self.children[0].get_float(context)
978   return math.exp(value)
979 @method(RValueCos)
980 def get(self, context):
981   value = self.children[0].get_float(context)
982   return math.cos(value)
983 @method(RValueSin)
984 def get(self, context):
985   value = self.children[0].get_float(context)
986   return math.sin(value)
987 @method(RValueTan)
988 def get(self, context):
989   value = self.children[0].get_float(context)
990   return math.tan(value)
991 @method(RValueAtn)
992 def get(self, context):
993   value = self.children[0].get_float(context)
994   return math.atan(value)
995 @method(RValuePeek)
996 def get(self, context):
997   value = self.children[0].get_int(context)
998   return float(apple_io.peek(value))
999 @method(RValueLen)
1000 def get(self, context):
1001   value = self.children[0].get_str(context)
1002   return float(len(value))
1003 @method(RValueStrDollar)
1004 def get(self, context):
1005   value = self.children[0].get_float(context)
1006   return data_types.str_dollar(context, value)
1007 @method(RValueVal)
1008 def get(self, context):
1009   value = self.children[0].get_str(context)
1010   return data_types.val(value)[1] # ignore n chars converted
1011 @method(RValueAsc)
1012 def get(self, context):
1013   value = self.children[0].get_str(context)
1014   if len(value) == 0:
1015     raise Exception(
1016       f'?ILLEGAL QUANTITY ERROR IN {context.line_number():d}'
1017     )
1018   return float(ord(value[0]))
1019 @method(RValueChrDollar)
1020 def get(self, context):
1021   value = self.children[0].get_int(context)
1022   if value < 0 or value >= 0x110000: # never seen a unicode apple 2 before!
1023     raise Exception(
1024       f'?ILLEGAL QUANTITY ERROR IN {context.line_number():d}'
1025     )
1026   return chr(value)
1027 @method(RValueLeftDollar)
1028 def get(self, context):
1029   value0 = self.children[0].get_str(context)
1030   value1 = self.children[1].get_int(context)
1031   if value1 < 0:
1032     raise Exception(
1033       f'?ILLEGAL QUANTITY ERROR IN {context.line_number():d}'
1034     )
1035   return value0[:value1]
1036 @method(RValueRightDollar)
1037 def get(self, context):
1038   value0 = self.children[0].get_str(context)
1039   value1 = self.children[1].get_int(context)
1040   if value1 < 0:
1041     raise Exception(
1042       f'?ILLEGAL QUANTITY ERROR IN {context.line_number():d}'
1043     )
1044   return '' if value1 == 0 else value0[-value1:]
1045 @method(RValueMidDollar)
1046 def get(self, context):
1047   value0 = self.children[0].get_str(context)
1048   value1 = self.children[1].get_int(context)
1049   if value1 < 1:
1050     raise Exception(
1051       f'?ILLEGAL QUANTITY ERROR IN {context.line_number():d}'
1052     )
1053   value1 -= 1
1054   if len(self.children) < 3:
1055     return value0[value1:]
1056   value2 = self.children[2].get_int(context)
1057   if value2 < 0:
1058     raise Exception(
1059       f'?ILLEGAL QUANTITY ERROR IN {context.line_number():d}'
1060     )
1061   return value0[value1:value1 + value2]
1062 @method(LValue)
1063 def get(self, context):
1064   value = self.find_variable(context).value
1065   name = self.children[0].str_value
1066   if name[-1] == '%':
1067     value = float(value)
1068   return value
1069 del get
1070
1071 @method(RValue)
1072 def get_str(self, context):
1073   value = self.get(context)
1074   if not isinstance(value, str):
1075     raise Exception(
1076       f'?TYPE MISMATCH ERROR IN {context.line_number():d}'
1077     )
1078   return value
1079 del get_str
1080
1081 @method(RValue)
1082 def get_float(self, context):
1083   value = self.get(context)
1084   if not isinstance(value, float):
1085     raise Exception(
1086       f'?TYPE MISMATCH ERROR IN {context.line_number():d}'
1087     )
1088   return value
1089 del get_float
1090
1091 @method(RValue)
1092 def get_int(self, context):
1093   return data_types.cint(self.get_float(context))
1094 del get_int
1095
1096 @method(LValue)
1097 def find_variable(self, context):
1098   raise NotImplementedError()
1099 @method(LValueVariable)
1100 def find_variable(self, context):
1101   name = self.children[0].str_value
1102   return context.find_variable(name)
1103 @method(LValueArray)
1104 def find_variable(self, context):
1105   name = self.children[0].str_value
1106   variable = context.find_array(name, len(self.children) - 1)
1107   for i in self.children[1:]:
1108     if not isinstance(variable, list):
1109       # too many subscripts
1110       raise Exception(
1111         f'?BAD SUBSCRIPT ERROR IN {context.line_number():d}'
1112       )
1113     index = data_types.cint(i.get_float(context))
1114     if index < 0 or index >= len(variable):
1115       raise Exception(
1116         f'?BAD SUBSCRIPT ERROR IN {context.line_number():d}'
1117       )
1118     variable = variable[index]
1119   if not isinstance(variable, Variable):
1120     # not enough subscripts
1121     raise Exception(
1122       f'?BAD SUBSCRIPT ERROR IN {context.line_number():d}'
1123     )
1124   return variable
1125 del find_variable
1126
1127 @method(LValue)
1128 def set(self, context, value):
1129   name = self.children[0].str_value
1130   if name[-1] == '$':
1131     if not isinstance(value, str):
1132       raise Exception(
1133         f'?TYPE MISMATCH ERROR IN {context.line_number():d}'
1134       )
1135   else:
1136     if not isinstance(value, float):
1137       raise Exception(
1138         f'?TYPE MISMATCH ERROR IN {context.line_number():d}'
1139       )
1140     if name[-1] == '%':
1141       value = data_types.cint(value)
1142   self.find_variable(context).value = value
1143 del set