For people of a certain age one their early computing experiences included going into WHSmiths on a Saturday afternoon, finding a VIC20 on display, and typing:
10 PRINT "I AM AWESOME"
20 GOTO 10
RUN
In less civilised times this might have been extended to include a number of rude words, but obviously this never happened in the 80's. Even to this day, when presented with an 8 bit computer many otherwise normal adults will revert to this deeply instinctive behaviour.
We are of course talking about BASIC - Beginners All-purpose Symbolic Instruction Code. The language that came with every computer. Even IBM PC's - the original version of the machines all around us originally ran BASIC when you turned them on - DOS was strictly optional!!! BASIC was built into the machine in the same way the BIOS was, on ROM.
In the UK, most home programming was done in BBC Basic, and even to day you'll still find BBC Basic being used - OCR provide many of their examples using it. In comparison to regular BASIC from the 1980's, BBC BASIC (originally for BBC Micro) was excelllent. Goto and gosub where deprecated (or would have been if the word had been in common usage at the time) being augmented with real Functions, Procedures, and properly structured loops. You could actually write decent code on it.
Back then we had BBC BASIC 2. Official Acorn releases progressed though the Master and Archimedes series, but by that time a generation BBC hackers had gone off to university, and got their hands on bigger toys. I moved rapidly from BASIC to Pascal to C and by the early 90's to Objective-C. These (and pretty much any modern programming language) are all much better programming languages. But I spent most of my childhood writing BASIC on a BBC micro (the same machine which is still plugged in and operational on my desk right now), and remember it fondly.
So how about we implement BASIC using Sniff! We love to see how far we can push Sniff, so lets see if we can implement a programming language using it!
make currentLineNum number
make currentText string
make currentTextIndex number
The implementation is centred around these three variables - currentLineNum is the line number of the BASIC program that we're executing. currentText is the text of that line, and currentTextIndex is how far we are through it.
when start
.set currentLineNum to 0
.forever
..ask ">" and wait
..set currentText to answer
..set currentTextIndex to 1
..broadcast getNumber and wait
..if tokenOK
...set currentLineNum to numVal
...broadcast insertLine and wait
..else
...broadcast doLine and wait
This is the code to the main interactive loop - print a prompt and get back line of text. If it begins with a number then add it to the stored program, other wise try executing it.
The script getNumber is key and along with getWord, and getString are the basis of our "lexical analysis" - breaking the text into its useful parts.
when getNumber
.
.broadcast eatSpace and wait
.
.set tokenOK to yes
.set wordVal to ""
.
.repeat until not tokenOK
..broadcast isDigit and wait
..if tokenOK
...set wordVal to join wordVal letter currentTextIndex of currentText
...change currentTextIndex by 1
.
.if length of wordVal >0
..set numVal to value of wordVal
..set tokenOK to yes
.else
..set tokenOK to no
It checks that the next bit of text is indeed a series of digits, and turns them into a number. If it succeeds it sets tokenOK to be true. getWord and getString are similar.
The work of running a program take place in doLine:
when doLine
.make command string
.
.forever
..repeat until currentTextIndex >length of currentText
...broadcast getWord and wait
...if tokenOK
....set command to wordVal
....
....set tokenOK to no
....
....if command="LIST"
.....broadcast doList and wait
....
....if command="PRINT"
.....broadcast doPrint and wait
....
MORE STUFF GOES HERE!!
....
....if length of command=1
.....broadcast doAssign and wait
....
...else
....if currentTextIndex>length of currentText
.....set tokenOK to yes
...
...if not tokenOK
....say join "MISTAKE AT LINE " join [currentLineNum] ":"
....set currentLineNum to 0
....stop script
...
..if not currentLineNum=0
...change currentLineNum by 1
...broadcast getTextForLine and wait
..if currentLineNum=0
...stop script
We try to getWord and if that works we use that as the command. We then check the command against all the BASIC keywords, and call scripts to handle which ever matches. Simple implementations of BASIC only allow single letter variable names, so we can identify an assignment when we see a single letter "command".
If we get to the end of the loop and either we haven't found a matching command, or we did, but it generated an error itself, then we print out the error message, and stop running the program.
We use currentLineNum of 0 to represent that the program isn't running, and we're exectuing whatever was just typed in, so if the line number isn't zero we increment it and call getTextForLine which searches for the next line of code (which is held in a list of strings called lines).
With that in place most of the rest of the code is simple:
when doRun
.set currentLineNum to 1
.broadcast getTextForLine and wait
.set tokenOK to yes
for example to run the program we set the line to 1, and call getTextForLine which sets up the next line to be executed, loading it into currentText, and setting currentLineNum appropriately (the next line probably isn't currentLine+1, but rather something bigger than that).
when doGosub
.broadcast getNumber and wait
.if tokenOK
..add currentTextIndex to stack
..add currentLineNum to stack
..set currentLineNum to numVal
..broadcast getTextForLine and wait
when doReturn
.set currentLineNum to item last of stack
.delete item last of stack
.broadcast getTextForLine and wait
.set currentTextIndex to item last of stack
.delete item last of stack
Goto and Gosub provide good insight into how subroutines are implemented in most language (except Scratch and Sniff of course which execute scripts in parallel!). We have a list called "stack", and we store the current place we are in the program on the end of the list, before jumping to the destination. When we return, we can just pull that information off the end of the stack, and continue where we left off.
With everything more or less in place (we'll gloss over the details as they're not that interesting), we can run BASIC programs:
>LIST
1 REM THIS IS A TEST PROGRAM
2 PRINT 'MAX?'
3 INPUT M
5X=1
10 PRINT 'HELLO'
20 GOSUB 100
25X=X*3/2
26 IF X>=M END
30 GOTO 10
100 PRINT 'WORLD'
101PRINT X
105 IF X>5 PRINT 'BIG' GOTO 110
106 PRINT 'small'
110 RETURN
This is essentially a meaningless program, but it should all the important bits of BASIC in operation. The Sniff we've used to do this is all pure Scratch (apart from the save and load routines which are optional), so if you have the patience to drag out those blocks it will work identically in Scratch! One thing old timers will notice is that we're using single quotes for strings - Sniff uses double quotes so writing Sniff code to work with double quotes is tricky you can't write if letter 5 of answer=""" because it all breaks horribly. Using single quotes makes it all work.
Here's the Fibonacci series writtin in Sniff Basic:
>LIST
10 A=1
20 B=1
30 C=A+B
40 PRINT A '+' B '=' C
50 A=B
60 B=C
70 IF C<10000 GOTO 30
>RUN
1+1=2
1+2=3
2+3=5
3+5=8
5+8=13
8+13=21
13+21=34
21+34=55
34+55=89
55+89=144
89+144=233
144+233=377
233+377=610
377+610=987
610+987=1597
987+1597=2584
1597+2584=4181
2584+4181=6765
4181+6765=10946
A while back we write a whole load of numerical code in Sniff, including Newton Rhapson. Well here it is in Sniff BASIC:
>LIST
10 X=45
20 D=1/10000
25
30 GOSUB 1000
40 A=F
45 PRINT 'F(' X ')=' A
46 IF A<1/1000000 END
50 X=X+D
55 GOSUB 1000
60 X=X-D
70 M=F/D-A/D
90 X=X-A/M
100 GOTO 30
800
1000 REM CALCF
1010 F=X*X*X+2*X*X-X-1
1020 RETURN
>RUN
F(45)=95129
F(29.5168)=27428.1
F(19.4329)=8073.43
F(12.6599)=2335.92
F(8.26906)=692.902
F(5.36381)=205.496
F(3.44104)=59.9848
F(2.19622)=17.0436
F(1.43075)=4.59211
F(1.00788)=1.04756
F(0.835609)=0.144332
F(0.803083)=0.00474429
F(0.80194)=7.92742e-06
F(0.801938)=1.19209e-07
>
There's actually something very odd, and possibly unique in this code... Back in the days of BASIC computers couldn't do floating point maths very efficiently so if you owned an APPLE II it came with "Integer BASIC" . In Sniff BASIC the CPU can handle floating point pretty easily, but the getNumber routine only handles integers - to represent non integers you need to use fractions!!!
It would actually be pretty easy to fix that, but I kind of like it. Programming in BASIC is something that we might get nostalgic about, but with Sniff BASIC its a totally rational option!
[you might also think that's the most code I've has ever written for the sake of such a dumb joke but I would a search for "stupid renderman tricks" will prove otherwise!]
Code for Sniff BASIC is included as an example in Release 20
Code for Sniff BASIC is included as an example in Release 20
No comments:
Post a Comment