-
Notifications
You must be signed in to change notification settings - Fork 1
/
varaq-engl
executable file
·666 lines (565 loc) · 17.2 KB
/
varaq-engl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
#!/usr/bin/env perl
# varaq-engl
#
# An English translation of var'aq, the "Klingon Forth".
#
# Last updated 9/19/2000.
# (Jul 14 2000: Chris Pressey generally hacks source to bits and
# sends back to Brian Connors...)
# (19 sep 2000: j proctor adds some stuff...
#
# (c)2000 Brian Connors (idea, semantics) and Chris Pressey (parsing logic
# and general plumbing) under terms of the Mozilla Public License (see
# http://www.mozilla.org for details).
# A note on comments: I don't distinguish my code from the other guy's code,
# so you'll just have to guess which is which. (Or for that matter who wrote
# which comment.)
### GLOBAL VARIABLES ###
$line = ''; # last line of text read from the input file
$token = ''; # last token parsed out of $line of text
$proc = {}; # dictionary of procedure names to defn's
$stack = []; # ooh, a stack, the last thing you'd expect here :-)
%vars = (); # dictionary of variables names to values
# For nice clean handling of lambda functions, we'll probably want
# to merge $proc and %vars into a single dictionary
### LINEAR SCANNER ###
# exact syntax for comments & strings needs to be determined
sub token
{
my $t = '';
RestartScan:
while ($line =~ /^\s*$/) # Get next line if we used up the current line
{
$line = <STDIN>;
return undef if not defined $line;
chomp $line;
$line =~ s/^\s*//;
}
# Remove any whitespace before the next token
$line =~ s/^\s*//;
# Assuming strings look like "this", and comments look like (* this *).
if ($line =~ /^\(\*(.*?)\*\)/)
{
$line = $';
goto RestartScan;
} elsif ($line =~ /^#!(.*?)$/) { #to recognize hashbang lines --
# you won't find this documented
# in the spec
$line = $';
goto RestartScan;
} elsif ($line =~ /^\/\/(.*?)$/) { #To avoid import symbols
$line = $';
goto RestartScan;
} elsif ($line =~ /^(\".*?\")/)
{
$t = $1;
$line = $';
return $t;
} elsif ($line =~ /^(\S+)/)
{
$t = $1;
$line = $';
return $t;
} else
{
$line = '';
goto RestartScan;
}
}
### RECURSIVE DESCENT PARSER ###
#defn has been modified to generate an array of tokens and push a
# reference on the stack to be bound to a name later.
# defn has been modified to handle nested definitions using recursion.
# defn itself does not touch the stack, as that would mean nested
# definitions would actually be 'spit out' onto the top level,
# which, unless it's what you want, probably isn't what you want
# (cf. Shelta.)
# defn now only returns a reference to the array that comprises the
# definition. It's up to the caller (elem) to push that ref onto
# the stack.
sub defn
{
my @temp = ();
if ($token eq '{')
{
$token = token();
while($token ne '}')
{
if ($token eq '{')
{
push @temp, defn();
}
push @temp, $token;
$token = token();
}
$token = token();
return [@temp];
} else
{
warn "Internal error: defn() should only be called at the start of a definition";
}
}
sub elem
{
if ($token eq '{')
{
push @{$stack}, defn();
} elsif ($token eq 'quit') # this should maybe be handled by 'execute'
{
die "all done.\n";
} else
{
execute($token);
$token = token();
}
}
sub parse
{
while(defined $token) {
elem();
}
}
### INTERPRETER ###
# Executing a proc object...
sub execblock() {
for($i = 0; $i <= $#{$block}; $i++) {
execute($block->[$i]); # recursively exec defined procedure
}
}
# composing a string
sub strcompose() {
$s2 = pop @{$stack};
$s1 = pop @{$stack};
if ($s1 eq 'mk' || $s1 eq undef) {
push @{$stack}, $s2;
} else {
push @{$stack}, $s1 . $s2;
strcompose();
}
}
# This is where most of the interesting stuff happens.
sub execute {
my $temp;
my $cmd = shift;
# Just print it
if($cmd eq 'disp') {
print pop @{$stack}, "\n";
# or bitch about it
} elsif ($cmd eq 'complain') {
print STDERR "Error: ", pop @{$stack}, "\n";
# but if you don't have it in the first place you have to get it
# from somewhere, yes?
} elsif ($cmd eq 'listen') {
$in = <STDIN>;
push @{$stack}, $in;
# This may seem redundant, but a careful reading of the code indicates
# that elem() is called only once a line.
#
# (Chris) What? No... elem() is called once per program element.
# A program element is a definition or an instruction in the main prog.
#
# (Brian) I knew I shoulda taken that left turn at Albuquerque...
#
# This clause duplicates its
# function for higher-level proc declarations (like say an iftrue clause)
# so this will actually work. It's a somewhat cheezy way of dealing with
# a really annoying bug, but here you go.
#
# (Chris) This IS redundant, because it can be (should be (is now))
# handled by the 'defn' production.
#
# } elsif ($cmd eq '{') {
# defn();
# (Chris) Instead, we may well be asked to execute a reference to an
# array: that is, a definition embedded into another definition. It
# should come to us in the form of an ARRAY ref. It's basically a
# literal value, so we do with it what we'd do with any literal
# value: push it onto the stack.
} elsif (ref($cmd) eq 'ARRAY') {
push @{$stack}, $cmd;
# dump is a debugging procedure. I don't know if it will be in the final
# language description, but I find it very useful for analyzing stack
# dynamics.
} elsif ($cmd eq "dump") {
print("@{$stack}\n");
# some basic stack operations
} elsif ($cmd eq 'pop') { #pop
pop @{$stack};
} elsif ($cmd eq 'dup') { #dup
$temp = pop @{$stack};
push @{$stack}, $temp;
push @{$stack}, $temp;
} elsif ($cmd eq 'exch') { #exch
$temp = pop @{$stack};
$tempb = pop @{$stack};
push @{$stack}, $temp;
push @{$stack}, $tempb;
} elsif ($cmd eq 'clear') {
$stack = [];
} elsif ($cmd eq 'remember') {
push @{$stack}, 'mk';
# This is a tough one -- the obvious implementation never bottoms out if
# there is no marker on the stack. Rather than going on a downward spiral, the
# interpreter simply looks for an empty stack and pretends that it found a
# marker.
} elsif ($cmd eq 'forget') {
my $pval = '';
while ($pval ne 'mk') { # could also say: while (defined($pval) and $pval ne 'mk')
$pval = pop @{$stack};
if ($pval eq undef) { # then just toss this
$pval = 'mk';
}
}
# math operations
} elsif ($cmd eq 'add') {
$a = pop @{$stack};
$b = pop @{$stack};
push @{$stack}, $a + $b;
} elsif ($cmd eq 'sub') {
$b = pop @{$stack};
$a = pop @{$stack};
push @{$stack}, $a - $b;
} elsif ($cmd eq 'mul') {
$a = pop @{$stack};
$b = pop @{$stack};
push @{$stack}, $a * $b;
} elsif ($cmd eq 'div') {
$b = pop @{$stack};
$a = pop @{$stack};
push @{$stack}, $a/ $b; #best drop Larry a line about this;
#$a/$b works, $a / $b doesn't. This
# is clearly a bug.
# (Chris) I dunno, works on my Perl. This is Perl 5 right?
} elsif ($cmd eq 'mod') {
$b = pop @{$stack};
$a = pop @{$stack};
push @{$stack}, $a % $b;
} elsif ($cmd eq 'pow') {
$b = pop @{$stack};
$a = pop @{$stack};
push @{$stack}, $a ** $b;
} elsif ($cmd eq 'rand') {
$rn = rand (pop @{$stack});
push @{$stack}, $rn;
} elsif ($cmd eq 'clip') {
$a = pop @{$stack};
push @{$stack}, int($a);
} elsif ($cmd eq 'smooth') {
$a = pop @{$stack};
push @{$stack}, int($a + .5);
} elsif ($cmd eq 'howmuch') {
$a = pop @{$stack};
push @{$stack}, abs($a);
# increment/decrement
} elsif ($cmd eq 'sub1') {
$a = pop@{$stack};
push @{$stack}, $a - 1;
} elsif ($cmd eq 'add1') {
$a = pop@{$stack};
push @{$stack}, $a + 1
# SIHpoj jangwI'mey
} elsif ($cmd eq 'sin') {
$a = pop @{$stack};
push @{$stack}, sin($a);
} elsif ($cmd eq 'cos') {
$a = pop @{$stack};
push @{$stack}, cos($a);
} elsif ($cmd eq 'tan') {
$a = pop @{$stack};
push @{$stack}, tan($a);
} elsif ($cmd eq 'atan') { #equivalent to atan2() in C
$d = pop @{$stack};
$n = pop @{$stack};
push @{$stack}, atan2($n,$d);
# ghurjangwI'mey
} elsif ($cmd eq 'ln') {
$a = pop @{$stack};
push @{$stack}, log($a);
# numerical constants
} elsif ($cmd eq 'pi') {
push @{$stack}, 3.141592654;
} elsif ($cmd eq 'e') {
push @{$stack}, 2.718281824;
# data tests
# provided by j proctor <[email protected]>
} elsif ($cmd eq 'number?') {
$a = pop @{$stack};
if ($a =~ /^-?\d+\.?\d*$/) { # lifted from Cookbook p.44
push @{$stack}, 1;
} else {
push @{$stack}, 0;
}
} elsif ($cmd eq 'int?') {
$a = pop @{$stack};
if ($a =~ /^-?\d+$/) { # lifted from Cookbook p.44
push @{$stack}, 1;
} else {
push @{$stack}, 0;
}
} elsif ($cmd eq 'negative?') {
$a = pop @{$stack};
if ($a < 0) {
push @{$stack}, 1;
} else {
push @{$stack}, 0;
}
} elsif ($cmd eq 'null?') {
$a = pop @{$stack};
if ($a eq "") {
push @{$stack}, 1;
} else {
push @{$stack}, 0;
}
# relational operators
#
# Note that the English keywords honor the ? convention. This is to
# maintain consistency with the use of -'a' in Klingon.
} elsif ($cmd eq 'gt?') {
$b = pop @{$stack};
$a = pop @{$stack};
if ($a > $b) {
push @{$stack}, 1;
} else {
push @{$stack}, 0;
}
} elsif ($cmd eq 'ge?') {
$b = pop @{$stack};
$a = pop @{$stack};
if ($a >= $b) {
push @{$stack}, 1;
} else {
push @{$stack}, 0;
}
} elsif ($cmd eq 'lt?') {
$b = pop @{$stack};
$a = pop @{$stack};
if ($a < $b) {
push @{$stack}, 1;
} else {
push @{$stack}, 0;
}
} elsif ($cmd eq 'le?') {
$b = pop @{$stack};
$a = pop @{$stack};
if ($a <= $b) {
push @{$stack}, 1;
} else {
push @{$stack}, 0;
}
} elsif ($cmd eq 'eq?') {
$b = pop @{$stack};
$a = pop @{$stack};
if ($a == $b) {
push @{$stack}, 1;
} else {
push @{$stack}, 0;
}
} elsif ($cmd eq 'ne?') {
$b = pop @{$stack};
$a = pop @{$stack};
if ($a != $b) {
push @{$stack}, 1;
} else {
push @{$stack}, 0;
}
# logical operators
} elsif ($cmd eq 'and') {
$a = pop @{$stack};
$b = pop @{$stack};
if ($a != 0 && $b != 0) {
push @{$stack}, 1;
} else {
push @{$stack}, 1;
}
} elsif ($cmd eq 'or') {
$a = pop @{$stack};
$b = pop @{$stack};
if ($a != 0 || $b != 0) {
push @{$stack}, 1;
} else {
push @{$stack}, 1;
}
} elsif ($cmd eq 'xor') {
$a = pop @{$stack};
$b = pop @{$stack};
if (($a != 0) xor ($b != 0)) {
push @{$stack}, 1;
} else {
push @{$stack}, 1;
}
# tlheghjangwI'
} elsif ($cmd eq 'strmeasure') {
$str = pop @{$stack};
push @{$stack}, length($str);
} elsif ($cmd eq 'strcut') {
$end = pop @{$stack};
$begin = pop @{$stack};
$str = pop @{$stack};
$end = $end - $begin; #make the implementation conform
# to the spec instead of the Perl
# interpreter
push @{$stack}, substr ($str, $begin, $end);
} elsif ($cmd eq 'strtie') {
$s2 = pop @{$stack};
$s1 = pop @{$stack};
push @{$stack}, $s1 . $s2;
} elsif ($cmd eq 'streq?') {
$s2 = pop @{$stack};
$s1 = pop @{$stack};
if ($s1 eq $s2) {
push @{$stack}, 1;
} else {
push @{$stack}, 0;
}
} elsif ($cmd eq 'compose') {
strcompose();
# You can't have a functional language without a conditional clause
#
# (Chris) Don't tempt me!!! :-)
#
# or you'll
# recurse forever and ever AND WE WOULDN'T WANT THAT, NOW, WOULD WE? For
# your convenience, var'aq provides two such constructs.
} elsif ($cmd eq 'ifyes') {
$block = pop @{$stack};
$cval = pop @{$stack};
if (ref($block) ne 'ARRAY') # better safe than sorry
{
warn "Can't execute a non-ARRAY reference as a routine";
}
elsif ($cval != 0) {
execblock();
# this almost deserves it's own routine 'execblock' (and gets it)
}
} elsif ($cmd eq 'ifno') {
$block = pop @{$stack};
$cval = pop @{$stack};
if (ref($block) ne 'ARRAY') # better safe than sorry
{
warn "Can't execute a non-ARRAY reference as a routine";
}
elsif ($cval == 0) {
execblock();
}
} elsif ($cmd eq 'repeat') {
$block = pop @{$stack};
$cval = pop @{$stack};
if (ref($block) ne 'ARRAY') # better safe than sorry
{
warn "Can't execute a non-ARRAY reference as a routine";
}
else {
while ($cval > 0) {
execblock();
if ($escv = 1) {
$cval = 1;
$escv = 0;
}
$cval--;
}
}
} elsif ($cmd eq 'eval') {
$block = pop @{$stack};
$cval = pop @{$stack};
if (ref($block) ne 'ARRAY') # better safe than sorry
{
warn "Can't execute a non-ARRAY reference as a routine";
}
else {
execblock();
}
# This operator exists for the sole purpose of making it possible to string
# a pair of if clauses together. Sharp-eyed code readers will note that
# in this implementation it's simply syntactic sugar for dup/latlh; that in
# mind, a more proper implementation might actually typecheck to see if
# that's an actual boolean on the stack.
} elsif ($cmd eq 'choose') {
$temp = pop @{$stack};
push @{$stack}, $temp;
push @{$stack}, $temp;
# (j proctor) Perl makes escaping easy: if it encounters this in the middle of
# executing a proc, it'll jump immediately to the end of it. If
# there's nothing next, well, it's done.
# (Brian) Note, however, that repeat/vangqa' won't necessarily notice; you have
# to give it a nudge.
} elsif ($cmd eq 'escape') {
last;
$escv = 1;
# The quote operator (~) will push a bare identifier onto the stack. Note that
# it's a special form; it comes *before* its operand. Veddy unusual, no?
# This operator is read as lI'moH (make useful) in Klingon.
} elsif ($cmd eq '~') {
push @{$stack}, $token = token(); # assign to global
# The name instruction takes a proc def and a value off the stack and binds
# it to an entry in $proc.
} elsif ($cmd eq 'name') {
$proc->{ pop @{$stack} } = pop @{$stack};
# Set does the same thing for %vars, but the contents get to not be
# write-only.
# (Chris) Mightn't you want to merge these two someday?
# (Brian) Eventually, maybe. Depends on whether I ever figure out how to
# separate variable and proc namespaces. At this point, we won't -- it
# isn't really necessary, and the parsing logic does that for us anyway.
} elsif ($cmd eq 'set') {
$n = pop @{$stack};
$v = pop @{$stack};
$vars{$n} = $v;
# At this point, we come down to anything that isn't a keyword.
# First we look for string literals. This makes it possible to do very
# useful things like write "hello, world" programs. Which you can now do.
} elsif($cmd =~ /^\"(.*?)\"$/) {
push @{$stack}, $1;
# It's not a string, so we assume it's a number. At this point only decimal
# notation is supported.
} elsif($cmd =~ /^-?\d+.?\d*$/) {
push @{$stack}, 0+$cmd;
# Technically, variables and functions aren't in the same namespace, but
# since this is how it's parsed, they are, but you don't really want to know.
# Just for the sake of argument, let's say they are and do the lookup.
# (Chris) with this, you could just say that variables 'mask' procedure
# definitions. Again, probably simpler to just merge them into the single
# namespace they so obviously share.
} elsif (exists $vars{$cmd}) {
push @{$stack}, $vars{$cmd};
# If that doesn't work we look it up in $proc and execute it if it's there.
} elsif(exists $proc->{$cmd}) {
my $i = 0;
for($i = 0; $i <= $#{$proc->{$cmd}}; $i++) {
execute($proc->{$cmd}->[$i]); # recursively exec defined procedure
}
# Otherwise we bitch, whine, and complain to the user.
} else {
warn "Undefined function '$cmd'";
}
}
### MAIN ###
print "var'aq Reference Edition -- English v0.5\n";
print "(c)2000 Brian Connors, Chris Pressey, and Mark Shoulson\n\n";
$token = token();
parse();
### END ###
# To those who actually read the code:
#
# Brian sez:
#
# Thanks for taking the time. I have found and continue to find the creation
# of this language a major educational experience, and I believe in making
# my (or I should say our, since Chris didn't have a problem with this either)
# code public record for whatever reason. At this point I'd like to thank
# Chris, without whom a lot of this would have been possible but not work
# nearly as nicely, and Mark Shoulson from the Klingon Language Institute, who
# as I write has only recently come aboard as a linguistic consultant to the
# project and will be making the best of his contribution down the road a
# ways. The code is free under the Mozilla Public License, yours to do with as
# you wish as long as you don't try to tell us that we didn't write it.
#
# Chris sez:
#
# I did an awful lot of random hacking at this and adding comments meant
# mainly for Brian, who will likely take them (and possibly) this out
# (feel free, I doubt much of my jabber is actually likely to help some
# interested outside observer understand what's going on in here.)
# Truth be told, I probably broke as much as I fixed; it depends on how
# Brian wants to handle some of the binding issues (at runtime or
# definetime?)
# (Brian) Chris, you're free to jabber as much as you want :-)