/basic

AP Computer Science A Unit 5 Project: BASIC interpreter

Primary LanguageJavaMIT LicenseMIT

UNIT 5 PROJECT INSTRUCTIONS
===========================

The starting point for this project is a tiny interpreter for the
BASIC programming language. The particular dialect of BASIC that
we're implementing, and there are many dialects of BASIC, is
AppleSoft BASIC as experienced on the Apple ][ computer released
in 1977.

You'll be adding support for some missing language features.

BASIC overview
==============

First, let's give a brief overview of the language. BASIC was meant to
be a very easy language to learn. It has some similarities to Python,
like you don't need to declare variables before using them.

Back when BASIC was king of personal computer programming languages,
a full-screen text editor like you use in repl.it was considered an
amazing luxury. What you usually dealt with was BASIC's REPL
(Read-Evaluate-Print Loop). The REPL lets you enter statements, and
the language executes them immediately. Many languages like Python, 
Ruby, and BASIC have a REPL that lets you directly interact with the language.
Java didn't for a long time, although one called JShell was released
relatively recently (2017).

When you run our BASIC interpreter, you'll get the prompt "]".
This is the REPL prompt. You can enter a BASIC statement like
PRINT "Hello, world!" There are also interpreter-level commands for
loading, saving and running programs.

Writing BASIC programs CAN be done by creating a file in your repl.it
with ".basic" extension and typing it right into the file. Or, you
can do it the way we did in the '80s... you enter statements prefixed
with line numbers into the REPL, and then SAVE your program to a file.
Later, you can load it back with the LOAD command and RUN it:

You type into the REPL:
10 REM We like infinite loops!
20 PRINT "Hello, world!"
30 GOTO 20
SAVE "InfiniteHello.basic"

Later:
LOAD "InfiniteHello.basic"
RUN
(You can hit Ctrl+C to break out of an infinite loop, or click Stop.)

Your project goal is to run two programs, Age.basic and RollSnakeEyes.basic.
Let's run through how it works:

Age.basic:
10 PRINT "HI, WHAT IS YOUR NAME?"
20 INPUT NAME$
30 PRINT "AND WHAT IS YOUR AGE?"
40 INPUT AGE
50 PRINT "HELLO, " + NAME$ + "."
60 PRINT "YOU ARE APPROXIMATELY " + (AGE*365.25*24*60*60) + " SECONDS OLD."

RollSnakeEyes.basic:
10 REM This is the BASIC equivalent of the rollSnakeEyes exercise
20 REM from the Unit 4 Test.
30 REM If you've implemented the BASIC features required for this
40 REM project, this code should run correctly.
50 REM You will need to implement the INPUT statement, RND() function,
60 REM and the IF statement.
100 PRINT "COMPUTING HOW MANY ROLLS IT TAKES TO GET SNAKE EYES"
110 ROLLS = 0
120 DIE1 = INT(RND() * 6 + 1)
130 DIE2 = INT(RND() * 6 + 1)
140 ROLLS = ROLLS + 1
150 IF DIE1 <> 1 THEN GOTO 120
160 IF DIE2 <> 1 THEN GOTO 120
170 PRINT "IT TOOK " + ROLLS + " ROLLS TO GET SNAKE EYES"

Lines are numbered. The tradition is to start at line 10 and go
in multiples of 10. Why? So if you need to stick lines in between,
you have 9 spots to choose from before you have to "renumber"
your program.

REM is a "remark", i.e. a comment. A REM line is ignored.

There are no semicolons. A statement ends with the return key.

There are two varible types: number and string. Any string variable
ends with a "$", like NAME$. In Age.basic, you can see we print stuff
with the PRINT statement, and the Scanner equivalent is the INPUT
statement.

Instead of while and for loops, you jump back to another part of the
program using the GOTO statement. On line 150 and line 160 of
RollSnakeEyes.basic, we keep the snake eyes rolling loop going by
jumping back to the start of the loop if the dice didn't come up
snake eyes.

With the project as it is, the above programs won't run because
some language features are missing, namely, INPUT, IF, and RND().

So here's what you'll actually be adding to the language.

1. INPUT statement
==================

The INPUT statement takes input from keyboard, much like Scanner in Java.

INPUT X reads a double number and stores it in variable X

INPUT NAME$ reads a string and stores it in variable NAME$

How to implement INPUT
----------------------

Lexical Scanner Support:
There are a few things you need to do. INPUT is a reserved word in BASIC,
so you need to register the new reserved word with the lexical scanner.
The lexical scanner is the part of the BASIC interpreter responsible for
turning the input characters of BASIC source into BASIC "tokens" that the
recursive descent parser can understand.

In LexicalScanner.java, find the list of reserved words and add INPUT to it.
In TokenType.java, you'll need to add INPUT to the enumerated type TokenType.

AST Node:
You'll need an AST Node for the INPUT statement. Thee INPUT statement is kind of like
assignment statement
  X = 1
  NAME$ = "Hello"
except that instead of a value on the right side, it takes its input from the user.
But both assignment statement and INPUT take a variable name.
So you can model your new class InputNode on the existing AssignmentNode class.
Copy AssignmentNode.java into a new file InputNode.java and change all
occurrences of AssignmentNode to InputNode. IdentifierNode is a good suitable
type for storing the variable name that you're storing into, so you can leave
that the same from AssignmentNode.

Next, you can take out any mention of the "right" value since there is no right
side of the assignment... you'll be reading it from the user. Then change the
evaluate() method so that instead of evaluating the right side expression and
assigning it to the variable, it reads a line of text or a double number from
the user and assigns that to the variable.

There is already a Scanner you can use in your evaluate() method by calling
context.getScanner(). If the variable name ends in "$", it's a string variable
and you can construct a Value of type string with the new Value(String s)
constructor. If the variable name doesn't end in "$", you can use
Scanner.nextDouble() to read a double from the user and construct a value
with the new Value(double d) constructor. Remember to call Scanner.nextLine()
to flush out the input. Lastly, set the variable using
context.setVariable(). This part will look much the same as it does in
AssignmentNode.

Make sure you implement the toString() method, because this is how the
LIST and SAVE commands work on your new statement type. It should return
just the string "INPUT " followed by the toString() of the IdentifierNode
for your variable name.

Parser Support:
In RecursiveDescentParser, find the statement() method. This is where
statements are parsed from tokens into AST nodes. Look at how PRINT is
implemented... if the token encountered is PRINT, we call match() to
advance to the next token and then expression() to parse the expression
to print. Here, you'll be matching the token INPUT, and then calling
identifier() to parse the variable name to put the user input in.
Then construct an InputNode with the new operator.

2. IF statement
===============

The IF statement evaluates an expression, and if the result is non-zero
(BASIC doesn't have boolean), the statement after the expression is evaluated.

  10 I=1
  20 PRINT I
  30 I=I+1
  40 IF I < 100 THEN GOTO 10

So, the grammar for the IF statement is
  IF condition_expression THEN then_statement

(Back then we didn't HAVE the optional ELSE clause! What sweet heaven it
would have been to have an ELSE clause!)

The relational operators in BASIC like < and > and = return 1 and 0
instead of "true" and "false". Note that the equality operator is just
"=", not "=="! And the does-not-equal operator is "<>", not "!=".
The relational and equality operators are already supported, so you don't
have to implement those, but you'll be able to use them in your IF
conditional expressions.

How to implement IF
-------------------

Lexical Scanner Support:
There are two new reserved words for the IF statement, IF and THEN.
Register both of these with the lexical scanner in the same way you
did INPUT.

AST Node:
Create an IfNode class which is a subclass of ASTNode. It should have
two private instance variables, "condition" and "statement".
The evaluate() method for IfNode should evaluate condition.
If the Value that comes back is non-zero, you then want to evaluate
"statement", because that's how you execute the statement when
the condition is true. You can check if the Value is non-zero using the
"isTruthy" method.

The toString() method of IfNode should return something like
  "IF " + condition + " THEN " + statement
The toString() methods of condition and statement will turn them into
strings of BASIC for you.

Parser Support:
In RecursiveDescentParser.java, you'll again add a new case to the
statement() method checking for the IF token. If you encounter an IF
token, match it with match(), and then parse the condition with
expression(). Then make sure the THEN token is there... you can check
if the token in "lookahead" is of the TokenType.THEN type. If it is,
match(), and then parse the IF's statement with statement().
(If it isn't, throw an exception. You can see lots of other exception
throws in RecursiveDescentParser.java; copy one of those.)
Lastly, construct an IfNode with the condition and statement you found.

3. RND() function
=================

The RND() function returns a random number x such that
  x >= 0.0 and x < 1.0
Sound familiar? It's exactly the same as Math.random()

This one is easier because it's just a built-in function, so it doesn't
need any additions to the lexical scanner or the recursive descent parser.
Look in FunctionCallNode.java and follow the model for functions such
as INT. The difference is that RND() doesn't even take any arguments
so you don't need to deal with those. Just call Math.random() and set
that as the returned value from the function call.
Note that this language is so basic (ha) that you can't define your
own user-defined functions. Only built-in ones can be used in expressions.

Testing Your Work
=================

If you've implemented the above 3 language features correctly, you
should be able to run the Age.basic program and the RollSnakeEyes.basic
program by loading them in with the LOAD command and running them with
the RUN command. You should also be able to list out their source with
the LIST command, and the output of LIST should match what's in the
.basic files they came from.
You can do this by running your program and then typing

 LOAD "Age.basic"
 LIST
 RUN

 LOAD "RollSnakeEyes.basic"
 LIST
 RUN

Write Your Own BASIC Program
============================

LASTLY, write a program of your own in BASIC and save it in your
replit. Your program can do anything... it could be a translation of
an earlier exercise we did from Java into BASIC, for example.

Of course, if you finish all this, you are welcome to add any other
language features that you like. You may find that you need additional
language features to support a program that you want to write in BASIC.

THE REAL DEAL
=============

If you want to try the real original AppleSoft BASIC that ran on the
Apple ][ computer, you can do so by going to this URL:
https://www.scullinsteel.com/apple2/

There is a language reference for it here:

https://www.calormen.com/jsbasic/reference.html

... and if you're curious, there's even a PDF of the original
spiral-bound BASIC manual that came with the computer:

https://mirrors.apple2.org.za/Apple%20II%20Documentation%20Project/Software/Languages/Applesoft%20BASIC/Manuals/Applesoft%20II%20BASIC%20Programming%20Reference%20Manual.pdf

Initially, the computer is trying to boot off its virtual disk drive.
IRL the motor of the disk drive would be going. Click the RESET button
on the virtual keyboard to get to an AppleSoft BASIC prompt, "]".
Then you can type BASIC commands like PRINT "HELLO, WORLD".
Note the computer only has uppercase letters. There isn't even a
CAPS LOCK key on the keyboard!