line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Language::Basic::Statement; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# Part of Language::Basic by Amir Karger (See Basic.pm for details) |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
=pod |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
=head1 NAME |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
Language::Basic::Statement - Package to handle parsing and implementing single |
10
|
|
|
|
|
|
|
BASIC statements. |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
=head1 SYNOPSIS |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
See L for the overview of how the Language::Basic module |
15
|
|
|
|
|
|
|
works. This pod page is more technical. |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
A Statement is something like 'GOTO 20' or 'PRINT "HELLO"'. A line of |
18
|
|
|
|
|
|
|
BASIC code is made up of one or more Statements. |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
# Create the statement from an LB::Token::Group and |
21
|
|
|
|
|
|
|
# bless it to an LBS::* subclass |
22
|
|
|
|
|
|
|
my $statement = new Language::Basic::Statement $token_group; |
23
|
|
|
|
|
|
|
$statement->parse; # Parse the statement |
24
|
|
|
|
|
|
|
$statement->implement; # Implement the statement |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
# Return a string containing the Perl equivalent of the statement |
27
|
|
|
|
|
|
|
$str = $statement->output_perl; |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
=head1 DESCRIPTION |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
Take a program like: |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
5 LET A = 2 |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
10 IF A >= 3 THEN GOTO 20 ELSE PRINT "IT'S SMALLER" |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
Line 5 has just one statement. Line 10 actually contains three. The first |
38
|
|
|
|
|
|
|
is an IF statement, but the results of the THEN and the ELSE are entire |
39
|
|
|
|
|
|
|
statements in themselves. |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
Each type of statement in BASIC has an associated LB::Statement class. |
42
|
|
|
|
|
|
|
For example, there's LB::Statement::Let and LB::Statement::If. (But no |
43
|
|
|
|
|
|
|
LB::Statement::Then! Instead the "then" field of the LB::Statement::If |
44
|
|
|
|
|
|
|
object will point to another statement. In the above program, it would |
45
|
|
|
|
|
|
|
point to a LB::Statement::Goto.) |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
Parsing a line of BASIC starts with removing the line number and lexing |
48
|
|
|
|
|
|
|
the line, breaking it into Tokens which are held in an LB::Token::Group. |
49
|
|
|
|
|
|
|
LB::Statement::new, refine, and parse, are all called with a Token::Group |
50
|
|
|
|
|
|
|
argument. These methods gradually "eat" their way through the Tokens. |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
LBS::new simply creates an LBS object. However, it then calls LBS::refine, |
53
|
|
|
|
|
|
|
which looks at the first Token of the command and blesses the object to |
54
|
|
|
|
|
|
|
the correct LBS::* subclass. |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
Each LBS subclass then has (at least) the methods parse, implement, |
57
|
|
|
|
|
|
|
and output_perl. |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
The parse method goes through the text and digests it and sets various |
60
|
|
|
|
|
|
|
fields in the object, which are used by implement and output_perl. The |
61
|
|
|
|
|
|
|
implement method actually implements the BASIC command. The |
62
|
|
|
|
|
|
|
output_perl method returns a string (with ; but not \n at the end) of the Perl |
63
|
|
|
|
|
|
|
equivalent of the BASIC statement. |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
=cut |
66
|
|
|
|
|
|
|
|
67
|
16
|
|
|
16
|
|
101
|
use strict; |
|
16
|
|
|
|
|
28
|
|
|
16
|
|
|
|
|
667
|
|
68
|
16
|
|
|
16
|
|
87
|
use Language::Basic::Common; |
|
16
|
|
|
|
|
29
|
|
|
16
|
|
|
|
|
8662
|
|
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
# sub-packages |
71
|
|
|
|
|
|
|
{ |
72
|
|
|
|
|
|
|
package Language::Basic::Statement::Data; |
73
|
|
|
|
|
|
|
package Language::Basic::Statement::Def; |
74
|
|
|
|
|
|
|
package Language::Basic::Statement::Dim; |
75
|
|
|
|
|
|
|
package Language::Basic::Statement::End; |
76
|
|
|
|
|
|
|
package Language::Basic::Statement::For; |
77
|
|
|
|
|
|
|
package Language::Basic::Statement::Gosub; |
78
|
|
|
|
|
|
|
package Language::Basic::Statement::Goto; |
79
|
|
|
|
|
|
|
package Language::Basic::Statement::If; |
80
|
|
|
|
|
|
|
package Language::Basic::Statement::Input; |
81
|
|
|
|
|
|
|
package Language::Basic::Statement::Let; |
82
|
|
|
|
|
|
|
package Language::Basic::Statement::Next; |
83
|
|
|
|
|
|
|
package Language::Basic::Statement::On; |
84
|
|
|
|
|
|
|
package Language::Basic::Statement::Print; |
85
|
|
|
|
|
|
|
package Language::Basic::Statement::Read; |
86
|
|
|
|
|
|
|
package Language::Basic::Statement::Rem; |
87
|
|
|
|
|
|
|
package Language::Basic::Statement::Return; |
88
|
|
|
|
|
|
|
} |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
# Note: This sub first blesses itself to be class LB::Statement, but then |
91
|
|
|
|
|
|
|
# class LB::Statement::refine, which blesses the object to a subclass |
92
|
|
|
|
|
|
|
# depending on what sort of statement it is. The refined object is returned. |
93
|
|
|
|
|
|
|
# |
94
|
|
|
|
|
|
|
# Fields: |
95
|
|
|
|
|
|
|
# next_statement - reference to next Statment on this Line. (or undef) |
96
|
|
|
|
|
|
|
# Note that next doesn't point to an If's Then/Else sub-statements |
97
|
|
|
|
|
|
|
# |
98
|
|
|
|
|
|
|
# lvalue - an LB::Expression::Lvalue object, which represents an |
99
|
|
|
|
|
|
|
# expression like X or AR(3+Q), which can be on the left hand |
100
|
|
|
|
|
|
|
# side of an assignment statement |
101
|
|
|
|
|
|
|
# expression - an LB::Expression:: subclass (e.g., Arithmetic or |
102
|
|
|
|
|
|
|
# Relational.) Sometimes there are multiple expressions. |
103
|
|
|
|
|
|
|
sub new { |
104
|
193
|
|
|
193
|
0
|
290
|
my $class = shift; |
105
|
193
|
|
|
|
|
290
|
my $token_group = shift; |
106
|
193
|
|
|
|
|
270
|
my $line_num_ok = shift; |
107
|
193
|
|
|
|
|
597
|
my $self = { |
108
|
|
|
|
|
|
|
"next_statement" => undef, |
109
|
|
|
|
|
|
|
"line_number" => undef, |
110
|
|
|
|
|
|
|
}; |
111
|
|
|
|
|
|
|
|
112
|
193
|
|
|
|
|
502
|
bless $self, $class; |
113
|
193
|
|
|
|
|
598
|
$self->refine( $token_group, $line_num_ok ); |
114
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::new |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
# Refine LB::Statement to the correct subclass |
117
|
|
|
|
|
|
|
# I.e., Read the command this statement starts with, and bless the |
118
|
|
|
|
|
|
|
# Statement to be a new subclass |
119
|
|
|
|
|
|
|
sub refine { |
120
|
193
|
|
|
193
|
0
|
329
|
my $self = shift; |
121
|
193
|
|
|
|
|
245
|
my $token_group = shift; |
122
|
193
|
|
|
|
|
230
|
my $line_num_ok = shift; |
123
|
193
|
50
|
66
|
|
|
624
|
die "LBS::refine called with weird arg $line_num_ok" if |
124
|
|
|
|
|
|
|
defined $line_num_ok && $line_num_ok ne "line_num_ok"; |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
# Valid BASIC statements |
127
|
16
|
|
|
|
|
10378
|
use constant KEYWORDS => |
128
|
|
|
|
|
|
|
qw(DATA DEF DIM END FOR GOSUB GOTO IF INPUT |
129
|
16
|
|
|
16
|
|
126
|
LET NEXT ON PRINT READ REM RETURN); |
|
16
|
|
|
|
|
59
|
|
130
|
|
|
|
|
|
|
# TODO In theory, this would let us make STOP exactly synonymous |
131
|
|
|
|
|
|
|
# with END, or CLEAR synonymous with CLS, etc. |
132
|
193
|
|
|
|
|
481
|
my %keywords = map {$_, ucfirst(lc($_))} (KEYWORDS); |
|
3088
|
|
|
|
|
9111
|
|
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
# First word is a command, or a variable (implied LET statment) |
135
|
193
|
|
|
|
|
996
|
my $tok = $token_group->lookahead; |
136
|
193
|
50
|
|
|
|
448
|
Exit_Error("Empty statement?!") unless defined $tok; |
137
|
193
|
|
|
|
|
218
|
my $command; |
138
|
193
|
|
|
|
|
1344
|
(my $class = ref($tok)) =~ s/^Language::Basic::Token:://; |
139
|
193
|
100
|
33
|
|
|
632
|
if ($class eq "Keyword") { |
|
|
100
|
33
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
140
|
171
|
|
|
|
|
577
|
my $text = $tok->text; |
141
|
171
|
50
|
|
|
|
465
|
if (exists $keywords{$text}) { |
142
|
171
|
|
|
|
|
566
|
$token_group->eat; |
143
|
171
|
|
|
|
|
583
|
$command = $keywords{$text}; |
144
|
|
|
|
|
|
|
} else { |
145
|
|
|
|
|
|
|
# Statement started with, e.g., "TO" or "ELSE" |
146
|
0
|
|
|
|
|
0
|
Exit_Error("Illegal reserved word '$text' at start of statement"); |
147
|
|
|
|
|
|
|
} |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
} elsif ($class eq "Comment") { |
150
|
6
|
|
|
|
|
13
|
$command = "Rem"; |
151
|
|
|
|
|
|
|
} elsif ($class eq "Identifier") { |
152
|
14
|
|
|
|
|
24
|
$command = "Let"; |
153
|
|
|
|
|
|
|
# If we're in a THEN or ELSE, a line number means GOTO that line |
154
|
|
|
|
|
|
|
} elsif ($line_num_ok && |
155
|
|
|
|
|
|
|
$class eq "Numeric_Constant" && |
156
|
|
|
|
|
|
|
$tok->text =~ /^\d+$/) { |
157
|
2
|
|
|
|
|
4
|
$command = "Goto"; |
158
|
|
|
|
|
|
|
} else { |
159
|
0
|
|
|
|
|
0
|
Exit_Error("Syntax Error: No Keyword or Identifier at start of statement!"); |
160
|
|
|
|
|
|
|
} |
161
|
193
|
|
|
|
|
349
|
my $subclass = "Language::Basic::Statement::" . $command; |
162
|
|
|
|
|
|
|
#print "New $subclass Statement\n"; |
163
|
|
|
|
|
|
|
|
164
|
193
|
|
|
|
|
1985
|
bless $self, $subclass; |
165
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::refine |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
# By default, parsing does nothing. Useful, e.g., for REM |
168
|
19
|
|
|
19
|
0
|
46
|
sub parse { } |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
# By default, implementing does nothing. Useful, e.g., for REM |
171
|
14
|
|
|
14
|
0
|
46
|
sub implement { } |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
# By default, output an empty statement. Note that you need the semicolon, |
174
|
|
|
|
|
|
|
# because we write a line label for each line. |
175
|
0
|
|
|
0
|
0
|
0
|
sub output_perl {return ";";} |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
sub set_line_number { |
178
|
165
|
|
|
165
|
0
|
218
|
my $self = shift; |
179
|
165
|
|
|
|
|
240
|
my $num = shift; |
180
|
165
|
|
|
|
|
526
|
$self->{"line_number"} = $num; |
181
|
|
|
|
|
|
|
} |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
###################################################################### |
184
|
|
|
|
|
|
|
# package Language::Basic::Statement::Data |
185
|
|
|
|
|
|
|
# A DATA statement in a BASIC program. |
186
|
|
|
|
|
|
|
{ |
187
|
|
|
|
|
|
|
package Language::Basic::Statement::Data; |
188
|
|
|
|
|
|
|
@Language::Basic::Statement::Data::ISA = qw(Language::Basic::Statement); |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
sub parse { |
191
|
5
|
|
|
5
|
|
10
|
my $self = shift; |
192
|
5
|
|
|
|
|
7
|
my $token_group = shift; |
193
|
5
|
|
|
|
|
16
|
my $prog = &Language::Basic::Program::current_program; |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
# The rest of the statement is things to dim and how big to dim them |
196
|
5
|
|
|
|
|
9
|
do { |
197
|
23
|
|
|
|
|
87
|
my $exp = new Language::Basic::Expression::Constant $token_group; |
198
|
23
|
|
|
|
|
85
|
$prog->add_data($exp); |
199
|
|
|
|
|
|
|
} while ($token_group->eat_if_string(",")); |
200
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Data::parse |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
# no sub implement nec. |
203
|
|
|
|
|
|
|
# no sub output_perl nec. |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
} # end package Language::Basic::Statement::Data |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
###################################################################### |
208
|
|
|
|
|
|
|
# package Language::Basic::Statement::Def |
209
|
|
|
|
|
|
|
# A DEF statement in a BASIC program. |
210
|
|
|
|
|
|
|
{ |
211
|
|
|
|
|
|
|
package Language::Basic::Statement::Def; |
212
|
|
|
|
|
|
|
@Language::Basic::Statement::Def::ISA = qw(Language::Basic::Statement); |
213
|
16
|
|
|
16
|
|
114
|
use Language::Basic::Common; |
|
16
|
|
|
|
|
29
|
|
|
16
|
|
|
|
|
12876
|
|
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
sub parse { |
216
|
3
|
|
|
3
|
|
149
|
my $self = shift; |
217
|
3
|
|
|
|
|
6
|
my $token_group = shift; |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
# Function name (and args) is stuff up to equals |
220
|
|
|
|
|
|
|
# Call LBE::Function::new with extra argument so it knows not to |
221
|
|
|
|
|
|
|
# complain about an unknown function. |
222
|
3
|
50
|
|
|
|
114
|
my $funcexp = new Language::Basic::Expression::Function |
223
|
|
|
|
|
|
|
($token_group, "defining") |
224
|
|
|
|
|
|
|
or Exit_Error("Missing/Bad Function Name or Args in DEF!"); |
225
|
3
|
50
|
|
|
|
13
|
$token_group->eat_if_string("=") or Exit_Error("DEF missing '='!"); |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
# We don't actually want the LB::Expression, just the function |
228
|
|
|
|
|
|
|
# we've declared. |
229
|
3
|
|
|
|
|
84
|
my $func = $funcexp->{"function"}; |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
# Read function definition |
232
|
3
|
50
|
|
|
|
21
|
my $exp = new Language::Basic::Expression::Arithmetic $token_group |
233
|
|
|
|
|
|
|
or Exit_Error("Missing/Bad function definition in DEF!"); |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
# Now actually define the function |
236
|
3
|
|
|
|
|
20
|
$func->define($exp); |
237
|
|
|
|
|
|
|
|
238
|
3
|
|
|
|
|
35
|
$self->{"function"} = $func; |
239
|
|
|
|
|
|
|
# TODO note that output_perl may not work now |
240
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Def::parse |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
# No sub implement: definition happens at compile time |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
sub output_perl { |
245
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
246
|
0
|
|
|
|
|
0
|
my $prog = &Language::Basic::Program::current_program; |
247
|
|
|
|
|
|
|
# LB::Function::Defined object |
248
|
0
|
|
|
|
|
0
|
my $func = $self->{"function"}; |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
# Function name |
251
|
0
|
|
|
|
|
0
|
my $name = $func->output_perl; |
252
|
0
|
|
|
|
|
0
|
my $desc = "{\n"; |
253
|
0
|
|
|
|
|
0
|
$desc .= "INDENT\n"; |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
# Function args |
256
|
0
|
|
|
|
|
0
|
$desc .= "my ("; |
257
|
0
|
|
|
|
|
0
|
my @args = map {$_->output_perl} @{$func->{"arguments"}}; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
258
|
0
|
|
|
|
|
0
|
$desc .= join (", ", @args); |
259
|
0
|
|
|
|
|
0
|
$desc .= ") = \@_;\n"; |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
# Function def |
262
|
0
|
|
|
|
|
0
|
my $exp = $func->{"expression"}->output_perl; |
263
|
0
|
|
|
|
|
0
|
$desc .= "return " . $exp . ";\n"; |
264
|
0
|
|
|
|
|
0
|
$desc .= "UNINDENT\n}"; |
265
|
|
|
|
|
|
|
# Tell program to print it out at the end of the perl script |
266
|
0
|
|
|
|
|
0
|
$prog->need_sub($name, $desc); |
267
|
|
|
|
|
|
|
|
268
|
0
|
|
|
|
|
0
|
return (";"); # put empty statement in program here |
269
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Def::output_perl |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
} # end package Language::Basic::Statement::Def |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
###################################################################### |
274
|
|
|
|
|
|
|
# package Language::Basic::Statement::Dim |
275
|
|
|
|
|
|
|
# A DIM statement in a BASIC program. |
276
|
|
|
|
|
|
|
{ |
277
|
|
|
|
|
|
|
package Language::Basic::Statement::Dim; |
278
|
|
|
|
|
|
|
@Language::Basic::Statement::Dim::ISA = qw(Language::Basic::Statement); |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
sub parse { |
281
|
2
|
|
|
2
|
|
7
|
my $self = shift; |
282
|
2
|
|
|
|
|
4
|
my $token_group = shift; |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
# The rest of the statement is things to dim and how big to dim them |
285
|
2
|
|
|
|
|
4
|
do { |
286
|
3
|
|
|
|
|
26
|
my $exp = new Language::Basic::Expression::Lvalue $token_group; |
287
|
3
|
|
|
|
|
5
|
push @{$self->{"arrays"}}, $exp; |
|
3
|
|
|
|
|
29
|
|
288
|
|
|
|
|
|
|
# TODO test that dims are constants! |
289
|
|
|
|
|
|
|
} while ($token_group->eat_if_string(",")); |
290
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Dim::parse |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
sub implement { |
293
|
2
|
|
|
2
|
|
4
|
my $self = shift; |
294
|
2
|
|
|
|
|
6
|
foreach (@{$self->{"arrays"}}) { |
|
2
|
|
|
|
|
10
|
|
295
|
|
|
|
|
|
|
# The Lvalue's Array |
296
|
3
|
|
|
|
|
9
|
my $array = $_->{"varptr"}; |
297
|
3
|
|
|
|
|
15
|
my @indices = $_->{"arglist"}->evaluate; |
298
|
3
|
|
|
|
|
18
|
$array->dimension(@indices); |
299
|
|
|
|
|
|
|
} |
300
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Dim::implement |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
# no sub output_perl necessary |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
} # end package Language::Basic::Statement::Dim |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
###################################################################### |
307
|
|
|
|
|
|
|
# package Language::Basic::Statement::End |
308
|
|
|
|
|
|
|
# An END statement in a BASIC program. |
309
|
|
|
|
|
|
|
{ |
310
|
|
|
|
|
|
|
package Language::Basic::Statement::End; |
311
|
|
|
|
|
|
|
@Language::Basic::Statement::End::ISA = qw(Language::Basic::Statement); |
312
|
16
|
|
|
16
|
|
103
|
use Language::Basic::Common; |
|
16
|
|
|
|
|
37
|
|
|
16
|
|
|
|
|
2596
|
|
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
sub implement { |
315
|
7
|
|
|
7
|
|
26
|
my $prog = &Language::Basic::Program::current_program; |
316
|
7
|
|
|
|
|
41
|
$prog->goto_line(undef); |
317
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::End::implement |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
sub output_perl { |
320
|
0
|
|
|
0
|
|
0
|
return ("exit;"); |
321
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::End::output_perl |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
} # end package Language::Basic::Statement::End |
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
###################################################################### |
326
|
|
|
|
|
|
|
# package Language::Basic::Statement::For |
327
|
|
|
|
|
|
|
# A FOR statement in a BASIC program. |
328
|
|
|
|
|
|
|
{ |
329
|
|
|
|
|
|
|
package Language::Basic::Statement::For; |
330
|
|
|
|
|
|
|
@Language::Basic::Statement::For::ISA = qw(Language::Basic::Statement); |
331
|
16
|
|
|
16
|
|
103
|
use Language::Basic::Common; |
|
16
|
|
|
|
|
37
|
|
|
16
|
|
|
|
|
13284
|
|
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
sub parse { |
334
|
10
|
|
|
10
|
|
25
|
my $self = shift; |
335
|
10
|
|
|
|
|
20
|
my $token_group = shift; |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
# Read variable name and "=" |
338
|
10
|
50
|
|
|
|
213
|
my $lvalue = new Language::Basic::Expression::Lvalue $token_group |
339
|
|
|
|
|
|
|
or Exit_Error("Missing variable in FOR!"); |
340
|
|
|
|
|
|
|
# No strings allowed, at least for now |
341
|
10
|
50
|
|
|
|
99
|
if ($lvalue->isa("Language::Basic::Expression::String")) { |
342
|
0
|
|
|
|
|
0
|
Exit_Error("FOR statements can't use strings!"); |
343
|
|
|
|
|
|
|
} |
344
|
10
|
|
|
|
|
58
|
$self->{"lvalue"} = $lvalue; |
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
# Read initialization value |
347
|
10
|
50
|
|
|
|
42
|
$token_group->eat_if_string("=") or Exit_Error("FOR missing '='!"); |
348
|
10
|
50
|
|
|
|
136
|
$self->{"start"} = |
349
|
|
|
|
|
|
|
new Language::Basic::Expression::Arithmetic::Numeric $token_group |
350
|
|
|
|
|
|
|
or Exit_Error("Missing/Bad initialization expression in FOR!"); |
351
|
10
|
50
|
|
|
|
40
|
$token_group->eat_if_string("TO") or Exit_Error("FOR missing 'TO'!"); |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
# Until the token "step" OR the end of the statement, we're copying an |
354
|
|
|
|
|
|
|
# expression, namely the variable's increment |
355
|
10
|
50
|
|
|
|
57
|
$self->{"limit"} = |
356
|
|
|
|
|
|
|
new Language::Basic::Expression::Arithmetic::Numeric $token_group |
357
|
|
|
|
|
|
|
or Exit_Error("Missing/Bad limit expression in FOR!"); |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
# If there's anything left, it had better be a step... |
360
|
|
|
|
|
|
|
# Otherwise, step = 1 |
361
|
10
|
|
|
|
|
19
|
my $step; |
362
|
10
|
100
|
|
|
|
43
|
if ($token_group->eat_if_string("STEP")) { |
363
|
2
|
50
|
|
|
|
11
|
$step = new Language::Basic::Expression::Arithmetic::Numeric |
364
|
|
|
|
|
|
|
$token_group |
365
|
|
|
|
|
|
|
or Exit_Error("Missing/Bad step expression in FOR!"); |
366
|
|
|
|
|
|
|
} else { |
367
|
8
|
50
|
|
|
|
37
|
Exit_Error("Unknown stuff after limit expression in FOR!") |
368
|
|
|
|
|
|
|
if $token_group->stuff_left; |
369
|
8
|
|
|
|
|
53
|
my $foo = new Language::Basic::Token::Group; |
370
|
8
|
|
|
|
|
30
|
$foo->lex("1"); |
371
|
8
|
|
|
|
|
41
|
$step = new Language::Basic::Expression::Arithmetic::Numeric $foo; |
372
|
|
|
|
|
|
|
} |
373
|
10
|
|
|
|
|
47
|
$self->{"step"} = $step; |
374
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::For::parse |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
sub implement { |
377
|
|
|
|
|
|
|
# TODO BASIC doesn't check for start being greater than limit |
378
|
|
|
|
|
|
|
# before doing a loop once. Might want to make a flag to do it. |
379
|
12
|
|
|
12
|
|
22
|
my $self = shift; |
380
|
12
|
|
|
|
|
40
|
my $prog = &Language::Basic::Program::current_program; |
381
|
12
|
|
|
|
|
27
|
my $lvalue = $self->{"lvalue"}; |
382
|
12
|
|
|
|
|
38
|
my $var = $lvalue->variable; |
383
|
12
|
|
|
|
|
54
|
$var->set($self->{"start"}->evaluate); |
384
|
|
|
|
|
|
|
# Store this FOR statement, so that we can access it when we |
385
|
|
|
|
|
|
|
# get to "NEXT var" |
386
|
12
|
|
|
|
|
47
|
$prog->store_for($self); |
387
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::For::implement |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
# Outputs $var = start; and the beginning of a do {} |
390
|
|
|
|
|
|
|
# We also have to set the step here, because we need to test in the loop |
391
|
|
|
|
|
|
|
# whether it's positive or negative so we can know whether to test for |
392
|
|
|
|
|
|
|
# being greater than or less than the limit! |
393
|
|
|
|
|
|
|
sub output_perl { |
394
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
395
|
|
|
|
|
|
|
# print var = start |
396
|
0
|
|
|
|
|
0
|
my $lvalue = $self->{"lvalue"}->output_perl; |
397
|
0
|
|
|
|
|
0
|
my $exp = $self->{"start"}->output_perl; |
398
|
0
|
|
|
|
|
0
|
my $ret = join(" ", $lvalue, "=", $exp); |
399
|
0
|
|
|
|
|
0
|
$ret .= ";\n"; |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
# set the step |
402
|
0
|
|
|
|
|
0
|
my $step = $self->{"step"}->output_perl; |
403
|
0
|
|
|
|
|
0
|
$lvalue =~ /\w+/; |
404
|
0
|
|
|
|
|
0
|
my $vname = $&; |
405
|
0
|
|
|
|
|
0
|
$ret .= join(" ", "\$step_for_$vname =", $step); |
406
|
0
|
|
|
|
|
0
|
$ret .= ";\n"; |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
# set the limit |
409
|
0
|
|
|
|
|
0
|
my $limit = $self->{"limit"}->output_perl; |
410
|
0
|
|
|
|
|
0
|
$ret .= join(" ", "\$limit_for_$vname =", $limit); |
411
|
0
|
|
|
|
|
0
|
$ret .= ";\n"; |
412
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
# Now start the do loop |
414
|
0
|
|
|
|
|
0
|
$ret .= "do {"; |
415
|
0
|
|
|
|
|
0
|
$ret .= "\nINDENT"; |
416
|
0
|
|
|
|
|
0
|
return $ret; |
417
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::For::output_perl |
418
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
} # end package Language::Basic::Statement::For |
420
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
###################################################################### |
422
|
|
|
|
|
|
|
# package Language::Basic::Statement::Gosub |
423
|
|
|
|
|
|
|
# A GOSUB statement in a BASIC program. |
424
|
|
|
|
|
|
|
{ |
425
|
|
|
|
|
|
|
package Language::Basic::Statement::Gosub; |
426
|
|
|
|
|
|
|
@Language::Basic::Statement::Gosub::ISA = qw(Language::Basic::Statement); |
427
|
16
|
|
|
16
|
|
111
|
use Language::Basic::Common; |
|
16
|
|
|
|
|
39
|
|
|
16
|
|
|
|
|
21249
|
|
428
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
sub parse { |
430
|
5
|
|
|
5
|
|
10
|
my $self = shift; |
431
|
5
|
|
|
|
|
9
|
my $token_group = shift; |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
# The rest of the statement is an expression for the line to go to |
434
|
5
|
50
|
|
|
|
26
|
$self->{"expression"} = new Language::Basic::Expression::Arithmetic $token_group |
435
|
|
|
|
|
|
|
or Exit_Error("Bad expression in GOSUB!"); |
436
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Gosub::parse |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
sub implement { |
439
|
7
|
|
|
7
|
|
11
|
my $self = shift; |
440
|
7
|
|
|
|
|
20
|
my $prog = &Language::Basic::Program::current_program; |
441
|
7
|
|
|
|
|
25
|
my $goto = $self->{"expression"}->evaluate; |
442
|
7
|
50
|
|
|
|
45
|
if ($goto !~ /^\d+$/) {Exit_Error("Bad GOSUB: $goto")} |
|
0
|
|
|
|
|
0
|
|
443
|
|
|
|
|
|
|
# Push the current statement onto the subroutine stack; |
444
|
7
|
|
|
|
|
24
|
$prog->push_stack($self); |
445
|
|
|
|
|
|
|
# Then GOTO the new line |
446
|
7
|
|
|
|
|
31
|
$prog->goto_line($goto); |
447
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Gosub::implement |
448
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
sub output_perl { |
450
|
|
|
|
|
|
|
# Perl script should print a label after the gosub. But before that, |
451
|
|
|
|
|
|
|
# it pushes the label name onto the global gosub stack. THen when |
452
|
|
|
|
|
|
|
# we hit the RETURN, we can pop the stack & goto back to this lable. |
453
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
454
|
0
|
|
|
|
|
0
|
my $prog = &Language::Basic::Program::current_program; |
455
|
0
|
|
|
|
|
0
|
my $exp = $self->{"expression"}; |
456
|
0
|
|
|
|
|
0
|
my $goto = $exp->output_perl; |
457
|
0
|
|
|
|
|
0
|
my $ret = ""; |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
# Form the label name to return to |
460
|
0
|
|
|
|
|
0
|
my $label = "AL" . $prog->current_line_number; |
461
|
0
|
|
|
|
|
0
|
$ret .= "push \@Gosub_Stack, \"$label\";\n"; |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
# Form the label name to goto |
464
|
|
|
|
|
|
|
# if it's just a number , don't use $tmp |
465
|
0
|
0
|
|
|
|
0
|
if ($goto =~ /^\d+$/) { |
466
|
0
|
|
|
|
|
0
|
$ret .= "goto L$goto;"; |
467
|
|
|
|
|
|
|
} else { |
468
|
|
|
|
|
|
|
# Form the label name |
469
|
0
|
|
|
|
|
0
|
$ret .= "\$Gosub_tmp = 'L' . " . $goto . ";\n"; |
470
|
|
|
|
|
|
|
# Go to it |
471
|
0
|
|
|
|
|
0
|
$ret .= "goto \$Gosub_tmp;"; |
472
|
|
|
|
|
|
|
} |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
# Write the return-to label after the goto |
475
|
0
|
|
|
|
|
0
|
$ret .= "\n$label:;"; |
476
|
|
|
|
|
|
|
|
477
|
0
|
|
|
|
|
0
|
return ($ret); |
478
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Gosub::output_perl |
479
|
|
|
|
|
|
|
} # end package Language::Basic::Statement::Gosub |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
###################################################################### |
482
|
|
|
|
|
|
|
# package Language::Basic::Statement::Goto |
483
|
|
|
|
|
|
|
# A GOTO statement in a BASIC program. |
484
|
|
|
|
|
|
|
{ |
485
|
|
|
|
|
|
|
package Language::Basic::Statement::Goto; |
486
|
|
|
|
|
|
|
@Language::Basic::Statement::Goto::ISA = qw(Language::Basic::Statement); |
487
|
16
|
|
|
16
|
|
118
|
use Language::Basic::Common; |
|
16
|
|
|
|
|
31
|
|
|
16
|
|
|
|
|
13224
|
|
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
sub parse { |
490
|
5
|
|
|
5
|
|
8
|
my $self = shift; |
491
|
5
|
|
|
|
|
8
|
my $token_group = shift; |
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
# The rest of the statement is an expression for the line to go to |
494
|
5
|
50
|
|
|
|
28
|
$self->{"expression"} = new Language::Basic::Expression::Arithmetic $token_group |
495
|
|
|
|
|
|
|
or Exit_Error("Bad expression in GOTO!"); |
496
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Goto::parse |
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
# Note that this sub allows "GOTO X+17/3", not just "GOTO 20" |
499
|
|
|
|
|
|
|
sub implement { |
500
|
3
|
|
|
3
|
|
7
|
my $self = shift; |
501
|
3
|
|
|
|
|
8
|
my $prog = &Language::Basic::Program::current_program; |
502
|
3
|
|
|
|
|
14
|
my $goto = $self->{"expression"}->evaluate; |
503
|
3
|
50
|
|
|
|
19
|
if ($goto !~ /^\d+$/) {Exit_Error("Bad GOTO: $goto")} |
|
0
|
|
|
|
|
0
|
|
504
|
3
|
|
|
|
|
10
|
$prog->goto_line($goto); |
505
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Goto::implement |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
sub output_perl { |
508
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
509
|
|
|
|
|
|
|
# if it's just a number , don't use $tmp |
510
|
0
|
|
|
|
|
0
|
my $exp = $self->{"expression"}; |
511
|
0
|
|
|
|
|
0
|
my $goto = $exp->output_perl; |
512
|
0
|
|
|
|
|
0
|
my $ret; |
513
|
0
|
0
|
|
|
|
0
|
if ($goto =~ /^\d+$/) { |
514
|
0
|
|
|
|
|
0
|
$ret = "goto L$goto;"; |
515
|
|
|
|
|
|
|
} else { |
516
|
|
|
|
|
|
|
# Form the label name |
517
|
0
|
|
|
|
|
0
|
$ret = "\$Goto_tmp = 'L' . " . $goto . ";\n"; |
518
|
|
|
|
|
|
|
# Go to it |
519
|
0
|
|
|
|
|
0
|
$ret .= "goto \$Goto_tmp;"; |
520
|
|
|
|
|
|
|
} |
521
|
|
|
|
|
|
|
|
522
|
0
|
|
|
|
|
0
|
return ($ret); |
523
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Goto::output_perl |
524
|
|
|
|
|
|
|
} # end package Language::Basic::Statement::Goto |
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
###################################################################### |
527
|
|
|
|
|
|
|
# package Language::Basic::Statement::If |
528
|
|
|
|
|
|
|
# An IF statement in a BASIC program. |
529
|
|
|
|
|
|
|
{ |
530
|
|
|
|
|
|
|
package Language::Basic::Statement::If; |
531
|
|
|
|
|
|
|
@Language::Basic::Statement::If::ISA = qw(Language::Basic::Statement); |
532
|
16
|
|
|
16
|
|
101
|
use Language::Basic::Common; |
|
16
|
|
|
|
|
33
|
|
|
16
|
|
|
|
|
16045
|
|
533
|
|
|
|
|
|
|
|
534
|
|
|
|
|
|
|
sub parse { |
535
|
28
|
|
|
28
|
|
56
|
my $self = shift; |
536
|
28
|
|
|
|
|
40
|
my $token_group = shift; |
537
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
# Until the token "then", we're copying a conditional expression |
539
|
28
|
50
|
|
|
|
142
|
my $exp = new Language::Basic::Expression::Logical_Or $token_group or |
540
|
|
|
|
|
|
|
Exit_Error("Bad Condition in IF!"); |
541
|
28
|
|
|
|
|
166
|
$self->{"condition"} = $exp; |
542
|
28
|
50
|
|
|
|
77
|
$token_group->eat_if_string("THEN") or Exit_Error("IF missing 'THEN'!"); |
543
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
# Until the token "ELSE" or the end of the line, is one or more |
545
|
|
|
|
|
|
|
# statements to do if the IF is true |
546
|
|
|
|
|
|
|
# TODO we need to handle ELSE either within the same statement |
547
|
|
|
|
|
|
|
# as the last THEN statement *OR* at the beginning of a statement. |
548
|
|
|
|
|
|
|
# Also nested IFs? |
549
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
# Take everything up to ELSE into a separate Token::Group & |
551
|
|
|
|
|
|
|
# call parsing with that so that other parse routines can complain if |
552
|
|
|
|
|
|
|
# there's something left in their token_group. Right now, they'll have |
553
|
|
|
|
|
|
|
# problem with ELSE token |
554
|
|
|
|
|
|
|
# TODO need a Token::Group::split method or some such |
555
|
28
|
|
|
|
|
126
|
my $t1 = new Language::Basic::Token::Group; |
556
|
28
|
|
|
|
|
98
|
$t1->slurp($token_group, "ELSE"); |
557
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
# Call new with an extra arg so it knows it's parsing a THEN/ELSE. |
559
|
|
|
|
|
|
|
# That way, "THEN 20" gets parsed like "THEN GOTO 20" |
560
|
28
|
50
|
|
|
|
85
|
my $then = new Language::Basic::Statement $t1, "line_num_ok" or |
561
|
|
|
|
|
|
|
Exit_Error("No statement found after THEN"); |
562
|
28
|
|
|
|
|
87
|
$then->parse($t1); |
563
|
28
|
|
|
|
|
56
|
my $oldst = $then; |
564
|
|
|
|
|
|
|
# Eat [: Statement]* |
565
|
28
|
|
|
|
|
86
|
while (defined($t1->eat_if_class("Statement_End"))) { |
566
|
|
|
|
|
|
|
# Plain line number is only allowed in the *first* THEN/ELSE statement |
567
|
0
|
|
|
|
|
0
|
my $st = new Language::Basic::Statement $t1; |
568
|
0
|
|
|
|
|
0
|
$st->parse($t1); |
569
|
0
|
|
|
|
|
0
|
$oldst->{"next_statement"} = $st; |
570
|
0
|
|
|
|
|
0
|
$oldst = $st; |
571
|
|
|
|
|
|
|
} |
572
|
|
|
|
|
|
|
# Make sure we don't do the ELSE after the THEN! |
573
|
28
|
|
|
|
|
50
|
$oldst->{"next_statement"} = undef; |
574
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
# If there's anything left in $token_group, it's the ELSE. |
576
|
28
|
|
|
|
|
47
|
my $else; |
577
|
28
|
100
|
|
|
|
76
|
if (defined($token_group->eat_if_string("ELSE"))) { |
578
|
|
|
|
|
|
|
# Use up all the leftover tokens |
579
|
8
|
50
|
|
|
|
30
|
$else = new Language::Basic::Statement $token_group, "line_num_ok" or |
580
|
|
|
|
|
|
|
Exit_Error("No statement found after THEN"); |
581
|
8
|
|
|
|
|
27
|
$else->parse ($token_group); |
582
|
8
|
|
|
|
|
13
|
$oldst = $else; |
583
|
8
|
|
|
|
|
27
|
while (defined($token_group->eat_if_class("Statement_End"))) { |
584
|
0
|
|
|
|
|
0
|
my $st = new Language::Basic::Statement $token_group; |
585
|
0
|
|
|
|
|
0
|
$st->parse($token_group); |
586
|
0
|
|
|
|
|
0
|
$oldst->{"next_statement"} = $st; |
587
|
0
|
|
|
|
|
0
|
$oldst = $st; |
588
|
|
|
|
|
|
|
} |
589
|
8
|
|
|
|
|
16
|
$oldst->{"next_statement"} = undef; |
590
|
8
|
50
|
|
|
|
29
|
Exit_Error("Unknown stuff after ELSE statement(s)") if |
591
|
|
|
|
|
|
|
$token_group->stuff_left; |
592
|
|
|
|
|
|
|
} else { |
593
|
20
|
50
|
|
|
|
60
|
Exit_Error("Unknown stuff after THEN statement(s)") if |
594
|
|
|
|
|
|
|
$token_group->stuff_left; |
595
|
|
|
|
|
|
|
} |
596
|
|
|
|
|
|
|
|
597
|
28
|
|
|
|
|
68
|
$self->{"then_s"} = $then; |
598
|
28
|
|
|
|
|
206
|
$self->{"else_s"} = $else; # may be undef |
599
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::If::parse |
600
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
# Need to set line numbers for THEN and ELSE statements, so we can't |
602
|
|
|
|
|
|
|
# use the default LBS::set_line_number |
603
|
|
|
|
|
|
|
sub set_line_number { |
604
|
28
|
|
|
28
|
|
49
|
my $self = shift; |
605
|
28
|
|
|
|
|
45
|
my $num = shift; |
606
|
28
|
|
|
|
|
59
|
$self->{"line_number"} = $num; |
607
|
28
|
|
|
|
|
63
|
foreach ("then_s", "else_s") { |
608
|
56
|
|
|
|
|
105
|
my $st = $self->{"$_"}; |
609
|
56
|
|
|
|
|
152
|
while (defined $st) { |
610
|
36
|
|
|
|
|
103
|
$st->set_line_number($num); |
611
|
36
|
|
|
|
|
118
|
$st = $st->{"next_statement"}; |
612
|
|
|
|
|
|
|
} |
613
|
|
|
|
|
|
|
} |
614
|
|
|
|
|
|
|
} |
615
|
|
|
|
|
|
|
|
616
|
|
|
|
|
|
|
sub implement { |
617
|
30
|
|
|
30
|
|
38
|
my $self = shift; |
618
|
30
|
|
|
|
|
71
|
my $prog = &Language::Basic::Program::current_program; |
619
|
|
|
|
|
|
|
|
620
|
30
|
100
|
|
|
|
115
|
if ($self->{"condition"}->evaluate) { |
621
|
21
|
|
|
|
|
131
|
$prog->{"next_statement"} = $self->{"then_s"}; |
622
|
|
|
|
|
|
|
} else { |
623
|
|
|
|
|
|
|
# This may be undef, in which case, code will just continue to next line |
624
|
9
|
|
|
|
|
48
|
$prog->{"next_statement"} = $self->{"else_s"}; |
625
|
|
|
|
|
|
|
} |
626
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::If::implement |
627
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
sub output_perl { |
629
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
630
|
0
|
|
|
|
|
0
|
my $ret = "if ("; |
631
|
0
|
|
|
|
|
0
|
$ret .= $self->{"condition"}->output_perl; |
632
|
0
|
|
|
|
|
0
|
$ret .= ") {\n"; |
633
|
0
|
|
|
|
|
0
|
$ret .= "INDENT"; |
634
|
0
|
|
|
|
|
0
|
my $st = $self->{"then_s"}; |
635
|
0
|
|
|
|
|
0
|
do { |
636
|
0
|
|
|
|
|
0
|
$ret .= "\n" . $st->output_perl; |
637
|
|
|
|
|
|
|
} while (defined ($st = $st->{"next_statement"})); |
638
|
|
|
|
|
|
|
|
639
|
0
|
0
|
|
|
|
0
|
if (defined $self->{"else_s"}) { |
640
|
|
|
|
|
|
|
# TODO only double-\n if there's a long THEN |
641
|
0
|
|
|
|
|
0
|
$ret .= "\n\nUNINDENT"; |
642
|
0
|
|
|
|
|
0
|
$ret .= "\n} else {\n"; |
643
|
0
|
|
|
|
|
0
|
$ret .= "INDENT"; |
644
|
0
|
|
|
|
|
0
|
$st = $self->{"else_s"}; |
645
|
0
|
|
|
|
|
0
|
do { |
646
|
0
|
|
|
|
|
0
|
$ret .= "\n" . $st->output_perl; |
647
|
|
|
|
|
|
|
} while (defined ($st = $st->{"next_statement"})); |
648
|
|
|
|
|
|
|
} |
649
|
0
|
|
|
|
|
0
|
$ret .= "\nUNINDENT"; |
650
|
0
|
|
|
|
|
0
|
$ret .= "\n}"; |
651
|
|
|
|
|
|
|
|
652
|
0
|
|
|
|
|
0
|
return ($ret); |
653
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::If::output_perl |
654
|
|
|
|
|
|
|
|
655
|
|
|
|
|
|
|
} # end package Language::Basic::Statement::If |
656
|
|
|
|
|
|
|
|
657
|
|
|
|
|
|
|
###################################################################### |
658
|
|
|
|
|
|
|
# package Language::Basic::Statement::Input |
659
|
|
|
|
|
|
|
# An INPUT statement in a BASIC program. |
660
|
|
|
|
|
|
|
{ |
661
|
|
|
|
|
|
|
package Language::Basic::Statement::Input; |
662
|
|
|
|
|
|
|
@Language::Basic::Statement::Input::ISA = qw(Language::Basic::Statement); |
663
|
16
|
|
|
16
|
|
111
|
use Language::Basic::Common; |
|
16
|
|
|
|
|
34
|
|
|
16
|
|
|
|
|
16880
|
|
664
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
sub parse { |
666
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
667
|
0
|
|
|
|
|
0
|
my $token_group = shift; |
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
# Handle INPUT "FOO"; BAR, BLAH |
670
|
|
|
|
|
|
|
# TODO I should really just try to call LBE::Constant::String and not |
671
|
|
|
|
|
|
|
# do anything if it returns undef. But currently that warns that what |
672
|
|
|
|
|
|
|
# we're trying to input isn't a quoted string if there's not quotation |
673
|
|
|
|
|
|
|
# mark. |
674
|
0
|
0
|
|
|
|
0
|
if ($token_group->lookahead-> |
675
|
|
|
|
|
|
|
isa("Language::Basic::Token::String_Constant")) { |
676
|
0
|
|
|
|
|
0
|
my $prompt = new |
677
|
|
|
|
|
|
|
Language::Basic::Expression::Constant::String $token_group; |
678
|
0
|
|
|
|
|
0
|
$self->{"to_print"} = $prompt; |
679
|
0
|
0
|
|
|
|
0
|
$token_group->eat_if_string(";") or |
680
|
|
|
|
|
|
|
Exit_Error("Expected ';' after INPUT prompt!"); |
681
|
|
|
|
|
|
|
} |
682
|
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
# The rest of the inputs will be separated by commas |
684
|
0
|
|
|
|
|
0
|
do { |
685
|
0
|
0
|
|
|
|
0
|
my $exp = new Language::Basic::Expression::Lvalue $token_group |
686
|
|
|
|
|
|
|
or Exit_Error("Incorrect INPUT!"); |
687
|
0
|
|
|
|
|
0
|
push @{$self->{"lvalues"}}, $exp; |
|
0
|
|
|
|
|
0
|
|
688
|
|
|
|
|
|
|
} while $token_group->eat_if_string(","); |
689
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Input::parse |
690
|
|
|
|
|
|
|
|
691
|
|
|
|
|
|
|
sub implement { |
692
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
693
|
0
|
0
|
|
|
|
0
|
TRY_AGAIN: |
694
|
|
|
|
|
|
|
# Print a prompt, if it exists |
695
|
|
|
|
|
|
|
my $to_print = (exists $self->{"to_print"} ? |
696
|
|
|
|
|
|
|
$self->{"to_print"}->evaluate : |
697
|
|
|
|
|
|
|
""); |
698
|
0
|
|
|
|
|
0
|
print "$to_print? "; |
699
|
|
|
|
|
|
|
|
700
|
|
|
|
|
|
|
# TODO set Program's "column" field to zero! |
701
|
|
|
|
|
|
|
# Read the variables |
702
|
0
|
|
|
|
|
0
|
my $in = <>; |
703
|
0
|
|
|
|
|
0
|
chomp($in); |
704
|
|
|
|
|
|
|
# TODO read Constants (String or Numeric) followed by commas if nec. |
705
|
|
|
|
|
|
|
# TODO type checking: make sure a string is a string |
706
|
|
|
|
|
|
|
# (this might be done by a different part of the program) |
707
|
|
|
|
|
|
|
# TODO Use "EXTRA IGNORED?" to let user know they need to quote commas? |
708
|
0
|
|
|
|
|
0
|
my @ins = split(/\s*,\s*/, $in); |
709
|
0
|
0
|
|
|
|
0
|
if (@ins != @{$self->{"lvalues"}}) { |
|
0
|
|
|
|
|
0
|
|
710
|
0
|
|
|
|
|
0
|
print "Not enough inputs! Try whole statement again...\n"; |
711
|
|
|
|
|
|
|
# Can't have a BASIC interpreter without a GOTO! |
712
|
0
|
|
|
|
|
0
|
goto TRY_AGAIN; |
713
|
|
|
|
|
|
|
} |
714
|
|
|
|
|
|
|
|
715
|
|
|
|
|
|
|
# set the variables to the inputted value |
716
|
0
|
|
|
|
|
0
|
foreach (@{$self->{"lvalues"}}) { |
|
0
|
|
|
|
|
0
|
|
717
|
0
|
|
|
|
|
0
|
my $var = $_->variable; # LB::Variable object |
718
|
|
|
|
|
|
|
# TODO Print "??" if they don't input enough. |
719
|
0
|
|
|
|
|
0
|
my $value = shift @ins; |
720
|
0
|
|
|
|
|
0
|
$var->set($value); |
721
|
|
|
|
|
|
|
} |
722
|
|
|
|
|
|
|
|
723
|
0
|
|
|
|
|
0
|
return $self->{"next_statement"}; |
724
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Input::implement |
725
|
|
|
|
|
|
|
|
726
|
|
|
|
|
|
|
sub output_perl { |
727
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
728
|
|
|
|
|
|
|
# Print the prompt |
729
|
0
|
|
|
|
|
0
|
my $ret = "print "; |
730
|
0
|
0
|
|
|
|
0
|
if (exists $self->{"to_print"}) { |
731
|
0
|
|
|
|
|
0
|
$ret .= $self->{"to_print"}->output_perl; |
732
|
0
|
|
|
|
|
0
|
$ret .= " . "; # concat with the ? below |
733
|
|
|
|
|
|
|
} |
734
|
0
|
|
|
|
|
0
|
$ret .= "\"? \";\n"; |
735
|
|
|
|
|
|
|
|
736
|
|
|
|
|
|
|
# Input the line |
737
|
0
|
|
|
|
|
0
|
$ret .= "\$input_tmp = <>;\n"; |
738
|
0
|
|
|
|
|
0
|
$ret .= "chomp(\$input_tmp);\n"; |
739
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
# Set the values |
741
|
0
|
|
|
|
|
0
|
my @lvalues = map {$_->output_perl} @{$self->{"lvalues"}}; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
742
|
0
|
|
|
|
|
0
|
my $tmp = join(", ", @lvalues); |
743
|
|
|
|
|
|
|
# Make the code a bit simpler for just one input |
744
|
0
|
|
|
|
|
0
|
my $multi = @lvalues > 1; |
745
|
0
|
0
|
|
|
|
0
|
if ($multi) { |
746
|
0
|
|
|
|
|
0
|
$ret .="($tmp) = split(/\\s*,\\s*/, \$input_tmp);"; |
747
|
|
|
|
|
|
|
} else { |
748
|
0
|
|
|
|
|
0
|
$ret .="$tmp = \$input_tmp;"; |
749
|
|
|
|
|
|
|
} |
750
|
|
|
|
|
|
|
|
751
|
0
|
|
|
|
|
0
|
return $ret; |
752
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Input::output_perl |
753
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
} # end package Language::Basic::Statement::Input |
755
|
|
|
|
|
|
|
|
756
|
|
|
|
|
|
|
###################################################################### |
757
|
|
|
|
|
|
|
# package Language::Basic::Statement::Let |
758
|
|
|
|
|
|
|
# A LET statement in a BASIC program. |
759
|
|
|
|
|
|
|
{ |
760
|
|
|
|
|
|
|
package Language::Basic::Statement::Let; |
761
|
|
|
|
|
|
|
@Language::Basic::Statement::Let::ISA = qw(Language::Basic::Statement); |
762
|
16
|
|
|
16
|
|
134
|
use Language::Basic::Common; |
|
16
|
|
|
|
|
41
|
|
|
16
|
|
|
|
|
6932
|
|
763
|
|
|
|
|
|
|
|
764
|
|
|
|
|
|
|
sub parse { |
765
|
15
|
|
|
15
|
|
29
|
my $self = shift; |
766
|
15
|
|
|
|
|
25
|
my $token_group = shift; |
767
|
|
|
|
|
|
|
|
768
|
|
|
|
|
|
|
# Read variable name and "=" |
769
|
15
|
50
|
|
|
|
89
|
my $lvalue = new Language::Basic::Expression::Lvalue $token_group |
770
|
|
|
|
|
|
|
or Exit_Error("Missing variable in LET!"); |
771
|
15
|
|
|
|
|
61
|
$self->{"lvalue"} = $lvalue; |
772
|
|
|
|
|
|
|
|
773
|
|
|
|
|
|
|
# The rest of the statement is an expression to set the variable equal to |
774
|
15
|
50
|
|
|
|
370
|
$token_group->eat_if_string("=") or Exit_Error("LET missing '='!"); |
775
|
15
|
50
|
|
|
|
116
|
$self->{"expression"} = |
776
|
|
|
|
|
|
|
new Language::Basic::Expression::Arithmetic $token_group |
777
|
|
|
|
|
|
|
or Exit_Error("Missing right side expression in LET!"); |
778
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Let::parse |
779
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
sub implement { |
781
|
49
|
|
|
49
|
|
64
|
my $self = shift; |
782
|
49
|
|
|
|
|
76
|
my $lvalue = $self->{"lvalue"}; |
783
|
49
|
|
|
|
|
170
|
my $var = $lvalue->variable; |
784
|
49
|
|
|
|
|
181
|
my $value = $self->{"expression"}->evaluate; |
785
|
49
|
|
|
|
|
179
|
$var->set($value); |
786
|
|
|
|
|
|
|
|
787
|
49
|
|
|
|
|
192
|
return $self->{"next_statement"}; |
788
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Let::implement |
789
|
|
|
|
|
|
|
|
790
|
|
|
|
|
|
|
sub output_perl { |
791
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
792
|
0
|
|
|
|
|
0
|
my $lvalue = $self->{"lvalue"}->output_perl; |
793
|
0
|
|
|
|
|
0
|
my $exp = $self->{"expression"}->output_perl; |
794
|
0
|
|
|
|
|
0
|
my $ret = join(" ", $lvalue, "=", $exp); |
795
|
0
|
|
|
|
|
0
|
$ret .= ";"; |
796
|
|
|
|
|
|
|
|
797
|
0
|
|
|
|
|
0
|
return ($ret); |
798
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Let::output_perl |
799
|
|
|
|
|
|
|
|
800
|
|
|
|
|
|
|
} # end package Language::Basic::Statement::Let |
801
|
|
|
|
|
|
|
|
802
|
|
|
|
|
|
|
###################################################################### |
803
|
|
|
|
|
|
|
# package Language::Basic::Statement::Next |
804
|
|
|
|
|
|
|
# A NEXT statement in a BASIC program. |
805
|
|
|
|
|
|
|
{ |
806
|
|
|
|
|
|
|
package Language::Basic::Statement::Next; |
807
|
|
|
|
|
|
|
@Language::Basic::Statement::Next::ISA = qw(Language::Basic::Statement); |
808
|
16
|
|
|
16
|
|
102
|
use Language::Basic::Common; |
|
16
|
|
|
|
|
38
|
|
|
16
|
|
|
|
|
8840
|
|
809
|
|
|
|
|
|
|
|
810
|
|
|
|
|
|
|
sub parse { |
811
|
10
|
|
|
10
|
|
22
|
my $self = shift; |
812
|
10
|
|
|
|
|
19
|
my $token_group = shift; |
813
|
|
|
|
|
|
|
|
814
|
10
|
50
|
|
|
|
52
|
my $lvalue = new Language::Basic::Expression::Lvalue $token_group |
815
|
|
|
|
|
|
|
or Exit_Error("Incorrect NEXT!"); |
816
|
|
|
|
|
|
|
# No strings allowed, at least for now |
817
|
10
|
50
|
|
|
|
78
|
if ($lvalue->variable->isa("Language::Basic::Variable::String")) { |
818
|
0
|
|
|
|
|
0
|
Exit_Error("NEXT statements can't use strings!"); |
819
|
|
|
|
|
|
|
} |
820
|
10
|
|
|
|
|
61
|
$self->{"lvalue"} = $lvalue; |
821
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Next::parse |
822
|
|
|
|
|
|
|
|
823
|
|
|
|
|
|
|
sub implement { |
824
|
60
|
|
|
60
|
|
78
|
my $self = shift; |
825
|
60
|
|
|
|
|
153
|
my $prog = &Language::Basic::Program::current_program; |
826
|
|
|
|
|
|
|
|
827
|
|
|
|
|
|
|
# Get the "FOR var" statement that this NEXT refers to. |
828
|
60
|
|
|
|
|
173
|
my $for_statement = $prog->get_for($self); |
829
|
120
|
|
|
|
|
438
|
my ($limit,$step) = |
830
|
60
|
|
|
|
|
95
|
map {$for_statement->{$_}->evaluate} qw (limit step); |
831
|
|
|
|
|
|
|
|
832
|
|
|
|
|
|
|
# Increment the variable |
833
|
60
|
|
|
|
|
99
|
my $lvalue = $self->{"lvalue"}; |
834
|
60
|
|
|
|
|
208
|
my $var = $lvalue->variable; |
835
|
60
|
|
|
|
|
171
|
my $value = $var->value; |
836
|
60
|
|
|
|
|
84
|
$value += $step; |
837
|
60
|
|
|
|
|
160
|
$var->set($value); |
838
|
|
|
|
|
|
|
#print "next: '$value' '$limit' '$step' '$goto'\n"; |
839
|
|
|
|
|
|
|
|
840
|
|
|
|
|
|
|
#test |
841
|
60
|
100
|
|
|
|
292
|
my $done = ($step > 0 ? $value > $limit : $value < $limit); |
842
|
60
|
100
|
|
|
|
180
|
unless ($done) { |
843
|
|
|
|
|
|
|
# Go to the statement *after* the statement the FOR started on |
844
|
48
|
|
|
|
|
139
|
$prog->goto_after_statement($for_statement); |
845
|
|
|
|
|
|
|
} |
846
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Next::implement |
847
|
|
|
|
|
|
|
|
848
|
|
|
|
|
|
|
# Outputs $var increment and end of do{}until block |
849
|
|
|
|
|
|
|
sub output_perl { |
850
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
851
|
|
|
|
|
|
|
# Increment variable |
852
|
0
|
|
|
|
|
0
|
my $lvalue = $self->{"lvalue"}; |
853
|
0
|
|
|
|
|
0
|
my $lv = $lvalue->output_perl; |
854
|
0
|
|
|
|
|
0
|
$lv =~ /\w+/; |
855
|
0
|
|
|
|
|
0
|
my $vname = $&; |
856
|
|
|
|
|
|
|
# Note that we add step_for even if it's negative. |
857
|
0
|
|
|
|
|
0
|
my $ret = join(" ", $lv, "+=", "\$step_for_$vname"); |
858
|
0
|
|
|
|
|
0
|
$ret .= ";\n"; |
859
|
0
|
|
|
|
|
0
|
$ret .= "UNINDENT\n"; |
860
|
|
|
|
|
|
|
|
861
|
|
|
|
|
|
|
# End the do {} block |
862
|
0
|
|
|
|
|
0
|
$ret .= "} "; |
863
|
|
|
|
|
|
|
|
864
|
|
|
|
|
|
|
# test the until |
865
|
0
|
|
|
|
|
0
|
$ret .= "until (\$step_for_$vname > 0 ? "; |
866
|
0
|
|
|
|
|
0
|
$ret .= $lv . " > \$limit_for_$vname : " .$lv. " < \$limit_for_$vname);"; |
867
|
0
|
|
|
|
|
0
|
return $ret; |
868
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Next::output_perl |
869
|
|
|
|
|
|
|
|
870
|
|
|
|
|
|
|
} # end package Language::Basic::Statement::Next |
871
|
|
|
|
|
|
|
|
872
|
|
|
|
|
|
|
###################################################################### |
873
|
|
|
|
|
|
|
# package Language::Basic::Statement::On |
874
|
|
|
|
|
|
|
# An ON statement in a BASIC program. |
875
|
|
|
|
|
|
|
{ |
876
|
|
|
|
|
|
|
package Language::Basic::Statement::On; |
877
|
|
|
|
|
|
|
@Language::Basic::Statement::On::ISA = qw(Language::Basic::Statement); |
878
|
16
|
|
|
16
|
|
110
|
use Language::Basic::Common; |
|
16
|
|
|
|
|
32
|
|
|
16
|
|
|
|
|
30264
|
|
879
|
|
|
|
|
|
|
|
880
|
|
|
|
|
|
|
sub parse { |
881
|
2
|
|
|
2
|
|
6
|
my $self = shift; |
882
|
2
|
|
|
|
|
5
|
my $token_group = shift; |
883
|
|
|
|
|
|
|
|
884
|
2
|
50
|
|
|
|
14
|
$self->{"expression"} = |
885
|
|
|
|
|
|
|
new Language::Basic::Expression::Arithmetic $token_group |
886
|
|
|
|
|
|
|
or Exit_Error("Missing Arith. Exp. in ON!"); |
887
|
|
|
|
|
|
|
# Until the token "GOSUB/GOTO", we're copying an arithmetic expression |
888
|
2
|
|
|
|
|
9
|
my $tok = $token_group->eat_if_class("Keyword"); |
889
|
2
|
50
|
33
|
|
|
16
|
defined $tok and $tok->text =~ /^GO(SUB|TO)$/ |
890
|
|
|
|
|
|
|
or Exit_Error("ON missing GOSUB/GOTO!"); |
891
|
2
|
|
|
|
|
8
|
my $type = $tok->text; |
892
|
2
|
|
|
|
|
6
|
$self->{"type"} = $type; |
893
|
|
|
|
|
|
|
|
894
|
|
|
|
|
|
|
# The rest of the inputs will be separated by commas |
895
|
2
|
|
|
|
|
5
|
do { |
896
|
6
|
50
|
|
|
|
24
|
my $exp = |
897
|
|
|
|
|
|
|
new Language::Basic::Expression::Arithmetic::Numeric $token_group |
898
|
|
|
|
|
|
|
or Exit_Error("Incorrect Expression in ON ... $type!"); |
899
|
6
|
|
|
|
|
9
|
push @{$self->{"gotos"}}, $exp; |
|
6
|
|
|
|
|
39
|
|
900
|
|
|
|
|
|
|
} while $token_group->eat_if_string(","); |
901
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::On::parse |
902
|
|
|
|
|
|
|
|
903
|
|
|
|
|
|
|
sub implement { |
904
|
6
|
|
|
6
|
|
8
|
my $self = shift; |
905
|
6
|
|
|
|
|
14
|
my $prog = &Language::Basic::Program::current_program; |
906
|
6
|
|
|
|
|
14
|
my $type = $self->{"type"}; |
907
|
6
|
|
|
|
|
37
|
my $value = $self->{"expression"}->evaluate; |
908
|
6
|
50
|
33
|
|
|
38
|
if ($value !~ /^\d+$/ || $value > @{$self->{"gotos"}}) { |
|
6
|
|
|
|
|
29
|
|
909
|
0
|
|
|
|
|
0
|
Exit_Error("Bad value in ON: $value") |
910
|
|
|
|
|
|
|
} |
911
|
|
|
|
|
|
|
|
912
|
6
|
|
|
|
|
10
|
my $goto = ${$self->{"gotos"}}[$value-1]->evaluate; |
|
6
|
|
|
|
|
25
|
|
913
|
6
|
50
|
|
|
|
26
|
if ($goto !~ /^\d+$/) {Exit_Error("Bad GOTO in ON: $goto")} |
|
0
|
|
|
|
|
0
|
|
914
|
6
|
|
|
|
|
21
|
$prog->goto_line($goto); |
915
|
|
|
|
|
|
|
|
916
|
|
|
|
|
|
|
# And if it's a GOSUB, push the program stack so we can get back |
917
|
6
|
50
|
|
|
|
32
|
$prog->push_stack($self) if $type eq "GOSUB"; |
918
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::On::implement |
919
|
|
|
|
|
|
|
|
920
|
|
|
|
|
|
|
sub output_perl { |
921
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
922
|
0
|
|
|
|
|
0
|
my $prog = &Language::Basic::Program::current_program; |
923
|
0
|
|
|
|
|
0
|
my $type = $self->{"type"}; |
924
|
|
|
|
|
|
|
|
925
|
|
|
|
|
|
|
# List of lines to go to |
926
|
0
|
|
|
|
|
0
|
my @gotos = map {$_->output_perl} @{$self->{"gotos"}}; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
927
|
0
|
|
|
|
|
0
|
my $ret = "\@Gotos_tmp = map {'L' . "; |
928
|
|
|
|
|
|
|
# If there's any expressions, be more fancy |
929
|
0
|
0
|
|
|
|
0
|
if (grep {$_ !~ /^\d+$/} @gotos) {$ret .= "eval "} |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
930
|
0
|
|
|
|
|
0
|
$ret .= "\$_} ("; |
931
|
0
|
|
|
|
|
0
|
$ret .= join(", ", @gotos); |
932
|
0
|
|
|
|
|
0
|
$ret .= ");\n"; |
933
|
|
|
|
|
|
|
|
934
|
|
|
|
|
|
|
# Index in the list |
935
|
0
|
|
|
|
|
0
|
my $branch = $self->{"expression"}->output_perl; |
936
|
0
|
|
|
|
|
0
|
$ret .= "\$index_tmp = "; |
937
|
0
|
|
|
|
|
0
|
$ret .= $branch . ";\n"; |
938
|
|
|
|
|
|
|
|
939
|
|
|
|
|
|
|
# Form the label name to return to |
940
|
0
|
|
|
|
|
0
|
my $label; |
941
|
0
|
0
|
|
|
|
0
|
if ($type eq "GOSUB") { |
942
|
0
|
|
|
|
|
0
|
$label = "AL" . $prog->current_line_number; |
943
|
0
|
|
|
|
|
0
|
$ret .= "push \@Gosub_Stack, \"$label\";\n"; |
944
|
|
|
|
|
|
|
} |
945
|
|
|
|
|
|
|
|
946
|
|
|
|
|
|
|
# Go to it |
947
|
0
|
|
|
|
|
0
|
$ret .= "goto \$Gotos_tmp[\$index_tmp-1];"; |
948
|
|
|
|
|
|
|
|
949
|
|
|
|
|
|
|
# Write the return-to label after the goto |
950
|
0
|
0
|
|
|
|
0
|
if ($type eq "GOSUB") { |
951
|
0
|
|
|
|
|
0
|
$ret .= "\n$label:;"; |
952
|
|
|
|
|
|
|
} |
953
|
|
|
|
|
|
|
|
954
|
0
|
|
|
|
|
0
|
return ($ret); |
955
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::On::output_perl |
956
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
} # end package Language::Basic::Statement::On |
958
|
|
|
|
|
|
|
|
959
|
|
|
|
|
|
|
###################################################################### |
960
|
|
|
|
|
|
|
# package Language::Basic::Statement::Print |
961
|
|
|
|
|
|
|
# A PRINT statement in a BASIC program. |
962
|
|
|
|
|
|
|
{ |
963
|
|
|
|
|
|
|
package Language::Basic::Statement::Print; |
964
|
|
|
|
|
|
|
@Language::Basic::Statement::Print::ISA = qw(Language::Basic::Statement); |
965
|
16
|
|
|
16
|
|
148
|
use Language::Basic::Common; |
|
16
|
|
|
|
|
46
|
|
|
16
|
|
|
|
|
22229
|
|
966
|
|
|
|
|
|
|
|
967
|
|
|
|
|
|
|
sub parse { |
968
|
80
|
|
|
80
|
|
132
|
my $self = shift; |
969
|
80
|
|
|
|
|
256
|
my $token_group = shift; |
970
|
|
|
|
|
|
|
# empty print statement? |
971
|
80
|
100
|
|
|
|
281
|
unless ($token_group->stuff_left) { |
972
|
2
|
|
|
|
|
8
|
$token_group = new Language::Basic::Token::Group; |
973
|
2
|
|
|
|
|
8
|
$token_group->lex('""'); |
974
|
|
|
|
|
|
|
} |
975
|
|
|
|
|
|
|
|
976
|
80
|
|
|
|
|
142
|
my $endchar; |
977
|
80
|
|
|
|
|
114
|
do { |
978
|
88
|
50
|
|
|
|
486
|
my $exp = new Language::Basic::Expression::Arithmetic $token_group |
979
|
|
|
|
|
|
|
or Exit_Error("Weird thing to print in PRINT statement!"); |
980
|
88
|
|
|
|
|
128
|
my $tok; |
981
|
88
|
100
|
|
|
|
269
|
if ($tok = $token_group->eat_if_class("Separator")) { |
|
|
50
|
|
|
|
|
|
982
|
|
|
|
|
|
|
# It's a comma or semicolon |
983
|
73
|
|
|
|
|
244
|
$endchar = $tok->text; |
984
|
|
|
|
|
|
|
} elsif (! $token_group->stuff_left) { |
985
|
15
|
|
|
|
|
30
|
$endchar = ""; |
986
|
|
|
|
|
|
|
} else { |
987
|
0
|
|
|
|
|
0
|
Exit_Error("Unexpected extra thing in PRINT statement!"); |
988
|
|
|
|
|
|
|
} |
989
|
88
|
|
|
|
|
236
|
push @{$self->{"to_print"}}, [$exp , $endchar]; |
|
88
|
|
|
|
|
786
|
|
990
|
|
|
|
|
|
|
|
991
|
|
|
|
|
|
|
} while ($token_group->stuff_left); |
992
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Print::parse |
993
|
|
|
|
|
|
|
|
994
|
|
|
|
|
|
|
sub implement { |
995
|
|
|
|
|
|
|
# TODO More than one expression to print! Use an array of LB::Expressions |
996
|
82
|
|
|
82
|
|
115
|
my $self = shift; |
997
|
82
|
|
|
|
|
185
|
my $prog = &Language::Basic::Program::current_program; |
998
|
82
|
|
|
|
|
112
|
foreach my $thing (@{$self->{"to_print"}}) { |
|
82
|
|
|
|
|
206
|
|
999
|
90
|
|
|
|
|
196
|
my ($exp, $endchar) = @$thing; |
1000
|
90
|
|
|
|
|
314
|
my $string = $exp->evaluate; |
1001
|
|
|
|
|
|
|
|
1002
|
|
|
|
|
|
|
# Never print after column 70 |
1003
|
|
|
|
|
|
|
# But "print ''" shouldn't print two \n's! |
1004
|
90
|
50
|
33
|
|
|
448
|
if ($prog->{"column"} >= 70 && length($string)) { |
1005
|
0
|
|
|
|
|
0
|
print "\n"; |
1006
|
0
|
|
|
|
|
0
|
$prog->{"column"} = 0; |
1007
|
|
|
|
|
|
|
} |
1008
|
|
|
|
|
|
|
|
1009
|
|
|
|
|
|
|
# Print the string |
1010
|
90
|
|
|
|
|
492
|
print $string; |
1011
|
90
|
|
|
|
|
176
|
$prog->{"column"} += length($string); |
1012
|
|
|
|
|
|
|
|
1013
|
|
|
|
|
|
|
# Handle the thing after the string |
1014
|
90
|
50
|
|
|
|
326
|
if ($endchar eq ",") { |
|
|
100
|
|
|
|
|
|
1015
|
|
|
|
|
|
|
# Paraphrased from a BASIC manual: |
1016
|
|
|
|
|
|
|
# If the printhead (!) is at char 56 or more after the expression, |
1017
|
|
|
|
|
|
|
# print \n, else print spaces until the printhead is at the |
1018
|
|
|
|
|
|
|
# beginning of the next 14-character field |
1019
|
0
|
0
|
|
|
|
0
|
if ($prog->{"column"} >= 56) { |
1020
|
0
|
|
|
|
|
0
|
print "\n"; |
1021
|
0
|
|
|
|
|
0
|
$prog->{"column"} = 0; |
1022
|
|
|
|
|
|
|
} else { |
1023
|
0
|
|
|
|
|
0
|
my $c = 14 - $prog->{"column"} % 14; |
1024
|
0
|
|
|
|
|
0
|
print (" " x $c); |
1025
|
0
|
|
|
|
|
0
|
$prog->{"column"} += $c; |
1026
|
|
|
|
|
|
|
} |
1027
|
|
|
|
|
|
|
} elsif ($endchar eq ";") { |
1028
|
|
|
|
|
|
|
# In BASIC, you always print a space after numbers, but not |
1029
|
|
|
|
|
|
|
# after strings. That seems a bit dumb, but that's how it is. |
1030
|
70
|
100
|
|
|
|
393
|
if (ref($exp) =~ /::Numeric$/) { |
1031
|
50
|
|
|
|
|
66
|
print " "; |
1032
|
50
|
|
|
|
|
474
|
$prog->{"column"}++; |
1033
|
|
|
|
|
|
|
} |
1034
|
|
|
|
|
|
|
} else { |
1035
|
20
|
|
|
|
|
1000
|
print "\n"; |
1036
|
20
|
|
|
|
|
147
|
$prog->{"column"} = 0; |
1037
|
|
|
|
|
|
|
} |
1038
|
|
|
|
|
|
|
} # end foreach loop over expressions to print |
1039
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Print::implement |
1040
|
|
|
|
|
|
|
|
1041
|
|
|
|
|
|
|
sub output_perl { |
1042
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
1043
|
0
|
|
|
|
|
0
|
my $ret = "print("; |
1044
|
0
|
|
|
|
|
0
|
my @to_print = @{$self->{"to_print"}}; |
|
0
|
|
|
|
|
0
|
|
1045
|
|
|
|
|
|
|
# TODO create a Print subroutine that takes exp/endchar array & prints |
1046
|
|
|
|
|
|
|
# in the exact way BASIC does. (How do we make that subroutine print |
1047
|
|
|
|
|
|
|
# a space after numerical expressions?!) |
1048
|
0
|
|
|
|
|
0
|
while (my $thing = shift @to_print) { |
1049
|
0
|
|
|
|
|
0
|
my ($exp, $endchar) = @$thing; |
1050
|
0
|
|
|
|
|
0
|
my $string = $exp->output_perl; |
1051
|
0
|
|
|
|
|
0
|
$ret .= $string; |
1052
|
0
|
0
|
|
|
|
0
|
$ret .= ",' '" if ref($exp) =~ /Numeric$/; |
1053
|
0
|
0
|
|
|
|
0
|
if ($endchar eq ",") { |
|
|
0
|
|
|
|
|
|
1054
|
0
|
|
|
|
|
0
|
$ret .= ", \"\\t\""; |
1055
|
|
|
|
|
|
|
} elsif ($endchar eq "") { |
1056
|
0
|
|
|
|
|
0
|
$ret .= ", \"\\n\""; |
1057
|
|
|
|
|
|
|
# This had better be the last exp! |
1058
|
0
|
0
|
|
|
|
0
|
warn "Internal error: obj. w/out endchar isn't last!" if @to_print; |
1059
|
|
|
|
|
|
|
} # otherwise it's ';', we hope |
1060
|
|
|
|
|
|
|
|
1061
|
0
|
0
|
|
|
|
0
|
if (@to_print) { |
1062
|
0
|
|
|
|
|
0
|
$ret .= ", "; |
1063
|
|
|
|
|
|
|
} else { |
1064
|
0
|
|
|
|
|
0
|
$ret .= ");"; |
1065
|
|
|
|
|
|
|
} |
1066
|
|
|
|
|
|
|
} |
1067
|
|
|
|
|
|
|
|
1068
|
0
|
|
|
|
|
0
|
return ($ret); |
1069
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Print::output_perl |
1070
|
|
|
|
|
|
|
|
1071
|
|
|
|
|
|
|
} # end package Language::Basic::Statement::Print |
1072
|
|
|
|
|
|
|
|
1073
|
|
|
|
|
|
|
###################################################################### |
1074
|
|
|
|
|
|
|
# package Language::Basic::Statement::Read |
1075
|
|
|
|
|
|
|
# A READ statement in a BASIC program. |
1076
|
|
|
|
|
|
|
{ |
1077
|
|
|
|
|
|
|
package Language::Basic::Statement::Read; |
1078
|
|
|
|
|
|
|
@Language::Basic::Statement::Read::ISA = qw(Language::Basic::Statement); |
1079
|
|
|
|
|
|
|
|
1080
|
|
|
|
|
|
|
sub parse { |
1081
|
3
|
|
|
3
|
|
7
|
my $self = shift; |
1082
|
3
|
|
|
|
|
6
|
my $token_group = shift; |
1083
|
|
|
|
|
|
|
|
1084
|
|
|
|
|
|
|
# The rest of the statement is lvalues to read in |
1085
|
3
|
|
|
|
|
5
|
do { |
1086
|
6
|
50
|
|
|
|
61
|
my $exp = new Language::Basic::Expression::Lvalue $token_group |
1087
|
|
|
|
|
|
|
or Exit_Error("Incorrect READ statement!"); |
1088
|
6
|
|
|
|
|
11
|
push @{$self->{"lvalues"}}, $exp; |
|
6
|
|
|
|
|
63
|
|
1089
|
|
|
|
|
|
|
} while $token_group->eat_if_string(","); |
1090
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Read::parse |
1091
|
|
|
|
|
|
|
|
1092
|
|
|
|
|
|
|
sub implement { |
1093
|
20
|
|
|
20
|
|
27
|
my $self = shift; |
1094
|
20
|
|
|
|
|
81
|
my $prog = &Language::Basic::Program::current_program; |
1095
|
20
|
|
|
|
|
42
|
foreach (@{$self->{"lvalues"}}) { |
|
20
|
|
|
|
|
53
|
|
1096
|
23
|
|
|
|
|
73
|
my $var = $_->variable; |
1097
|
23
|
|
|
|
|
93
|
my $data = $prog->get_data(); |
1098
|
|
|
|
|
|
|
# Data will just be a LBE::Constant, but we still have to &evaluate it |
1099
|
23
|
|
|
|
|
64
|
my $value = $data->evaluate; |
1100
|
23
|
|
|
|
|
72
|
$var->set($value); |
1101
|
|
|
|
|
|
|
} |
1102
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Read::implement |
1103
|
|
|
|
|
|
|
|
1104
|
|
|
|
|
|
|
sub output_perl { |
1105
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
1106
|
|
|
|
|
|
|
# Set a list... |
1107
|
0
|
|
|
|
|
0
|
my $ret = "("; |
1108
|
0
|
|
|
|
|
0
|
my @lvalues = map {$_->output_perl} @{$self->{"lvalues"}}; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
1109
|
0
|
|
|
|
|
0
|
$ret .= join(", ", @lvalues); |
1110
|
0
|
|
|
|
|
0
|
$ret .= ") = "; |
1111
|
|
|
|
|
|
|
|
1112
|
|
|
|
|
|
|
# equal to a splice from @Data |
1113
|
0
|
|
|
|
|
0
|
my $num = @lvalues; |
1114
|
0
|
|
|
|
|
0
|
$ret .= "splice(\@Data, 0, $num);"; |
1115
|
|
|
|
|
|
|
|
1116
|
0
|
|
|
|
|
0
|
return ($ret); |
1117
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Read::output_perl |
1118
|
|
|
|
|
|
|
|
1119
|
|
|
|
|
|
|
} # end package Language::Basic::Statement::Read |
1120
|
|
|
|
|
|
|
|
1121
|
|
|
|
|
|
|
###################################################################### |
1122
|
|
|
|
|
|
|
# package Language::Basic::Statement::Rem |
1123
|
|
|
|
|
|
|
# A REM statement in a BASIC program. |
1124
|
|
|
|
|
|
|
{ |
1125
|
|
|
|
|
|
|
package Language::Basic::Statement::Rem; |
1126
|
|
|
|
|
|
|
@Language::Basic::Statement::Rem::ISA = qw(Language::Basic::Statement); |
1127
|
|
|
|
|
|
|
sub parse { |
1128
|
|
|
|
|
|
|
# Eat the whole line (including colons if any) |
1129
|
6
|
|
|
6
|
|
11
|
my $self = shift; |
1130
|
6
|
|
|
|
|
12
|
my $token_group = shift; |
1131
|
6
|
|
|
|
|
24
|
my $tok = $token_group->eat_if_class("Comment"); |
1132
|
|
|
|
|
|
|
# Use original text to retain spaces and case. |
1133
|
6
|
|
|
|
|
28
|
my $text = $tok->{"original_text"}; |
1134
|
6
|
|
|
|
|
27
|
$text =~ s/REM//; |
1135
|
6
|
|
|
|
|
69
|
$self->{"comment"} = $text; |
1136
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Rem::parse |
1137
|
|
|
|
|
|
|
|
1138
|
|
|
|
|
|
|
sub output_perl { |
1139
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
1140
|
|
|
|
|
|
|
# Need to have a semicolon because the line label requires a |
1141
|
|
|
|
|
|
|
# statement after it. (And we need a line label in case we GOTO this line |
1142
|
0
|
|
|
|
|
0
|
my $ret = "; # " . $self->{"comment"}; |
1143
|
0
|
|
|
|
|
0
|
return $ret; |
1144
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Rem::output_perl |
1145
|
|
|
|
|
|
|
|
1146
|
|
|
|
|
|
|
} # end package Language::Basic::Statement::Rem |
1147
|
|
|
|
|
|
|
|
1148
|
|
|
|
|
|
|
###################################################################### |
1149
|
|
|
|
|
|
|
# package Language::Basic::Statement::Return |
1150
|
|
|
|
|
|
|
# A RETURN statement in a BASIC program. |
1151
|
|
|
|
|
|
|
{ |
1152
|
|
|
|
|
|
|
package Language::Basic::Statement::Return; |
1153
|
|
|
|
|
|
|
@Language::Basic::Statement::Return::ISA = qw(Language::Basic::Statement); |
1154
|
16
|
|
|
16
|
|
151
|
use Language::Basic::Common; |
|
16
|
|
|
|
|
44
|
|
|
16
|
|
|
|
|
4234
|
|
1155
|
|
|
|
|
|
|
|
1156
|
|
|
|
|
|
|
# No need to have a sub parse |
1157
|
|
|
|
|
|
|
|
1158
|
|
|
|
|
|
|
sub implement { |
1159
|
13
|
|
|
13
|
|
19
|
my $self = shift; |
1160
|
13
|
|
|
|
|
35
|
my $prog = &Language::Basic::Program::current_program; |
1161
|
13
|
50
|
|
|
|
40
|
my $gosub = $prog->pop_stack or |
1162
|
|
|
|
|
|
|
Exit_Error("RETURN without GOSUB"); |
1163
|
|
|
|
|
|
|
# Start at the statement *after* the GOSUB statement |
1164
|
13
|
|
|
|
|
42
|
$prog->goto_after_statement($gosub); |
1165
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Return::implement |
1166
|
|
|
|
|
|
|
|
1167
|
|
|
|
|
|
|
sub output_perl { |
1168
|
0
|
|
|
0
|
|
|
my $ret = "\$Return_tmp = pop \@Gosub_Stack;\n"; |
1169
|
0
|
|
|
|
|
|
$ret .= "goto \$Return_tmp;"; |
1170
|
|
|
|
|
|
|
|
1171
|
0
|
|
|
|
|
|
return ($ret); |
1172
|
|
|
|
|
|
|
} # end sub Language::Basic::Statement::Return::output_perl |
1173
|
|
|
|
|
|
|
|
1174
|
|
|
|
|
|
|
} # end package Language::Basic::Statement::Return |
1175
|
|
|
|
|
|
|
|
1176
|
|
|
|
|
|
|
1; # end of package Language::Basic::Statement |