diff --git a/programs/develop/tinybasic-1.0.4/ChangeLog b/programs/develop/tinybasic-1.0.4/ChangeLog new file mode 100644 index 0000000000..9b4a14a60f --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/ChangeLog @@ -0,0 +1,7 @@ +TinyBASIC 1.0.1 + + * 'Cannot open file' error now refers to the correct argument. + +TinyBASIC 1.0.0 + + * Initial release \ No newline at end of file diff --git a/programs/develop/tinybasic-1.0.4/Makefile b/programs/develop/tinybasic-1.0.4/Makefile new file mode 100755 index 0000000000..6167905caf --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/Makefile @@ -0,0 +1,31 @@ +KTCC_DIR=../ktcc/trunk +KLIBC_DIR = ../libraries/kolibri-libc + +NAME=bin/tinybas.kex + +KTCC=$(KTCC_DIR)/bin/kos32-tcc +KPACK=kpack + +SRC= src/common.c \ + src/errors.c \ + src/expression.c \ + src/formatter.c \ + src/generatec.c \ + src/interpret.c \ + src/options.c \ + src/parser.c \ + src/statement.c \ + src/tinybasic.c \ + src/token.c \ + src/tokeniser.c + +CFLAGS= -D_KOLIBRI -I$(KLIBC_DIR)/include -I inc +LFLAGS= -nobss -nostdlib -L $(KLIBC_DIR)/lib $(KLIBC_DIR)/lib/crt0.o +LIBS = -ltcc -lc.obj + +all: + $(KTCC) $(CFLAGS) $(SRC) $(LFLAGS) $(LIBS) -o $(NAME) + $(KPACK) $(NAME) + +clean: + rm $(NAME) diff --git a/programs/develop/tinybasic-1.0.4/Makefile.linux b/programs/develop/tinybasic-1.0.4/Makefile.linux new file mode 100644 index 0000000000..a0a3e3430e --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/Makefile.linux @@ -0,0 +1,56 @@ +# +# Tiny BASIC Interpreter and Compiler Project +# Makefile +# +# Released as Public Domain by Damian Gareth Walker 2019 +# Created: 18-Aug-2019 +# + +# Target +TARGET = tinybasic + +# Paths and extensions +SRCDIR := src +INCDIR := inc +DOCDIR := doc +BASDIR := bas +BUILDDIR := obj +TARGETDIR := bin +INSTALLDIR := /usr/local +SRCEXT := c +HDREXT := h +OBJEXT := o +MANEXT := man +BASEXT := bas + +# Compiler flags +CFLAGS := -Wall +INC := -I$(INCDIR) -I/usr/local/include + +# Generate file lists +SOURCES := $(shell find $(SRCDIR) -type f -name *.$(SRCEXT)) +OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT))) +SAMPLES := $(shell find $(BASDIR) -type f -name *.$(BASEXT)) + +# Default make +all: $(TARGETDIR)/$(TARGET) + +$(TARGETDIR)/$(TARGET): $(OBJECTS) + gcc -o $(TARGETDIR)/$(TARGET) $(OBJECTS) + +$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) + gcc $(CFLAGS) $(INC) -c -o $@ $< + +# Cleanup +clean: + rm -f $(BUILDDIR)/*.$(OBJEXT) + rm -f $(TARGETDIR)/$(TARGET) + +# Installation (Unix) +install: $(TARGETDIR)/$(TARGET) $(DOCDIR)/tinybasic.man $(SAMPLES) + mkdir -p $(INSTALLDIR)/bin + cp $(TARGETDIR)/$(TARGET) $(INSTALLDIR)/bin + mkdir -p $(INSTALLDIR)/share/man/man1 + cp $(DOCDIR)/tinybasic.man $(INSTALLDIR)/share/man/man1/tinybasic.1 + mkdir -p $(INSTALLDIR)/share/doc/tinybasic/samples + cp $(SAMPLES) $(INSTALLDIR)/share/doc/tinybasic/samples \ No newline at end of file diff --git a/programs/develop/tinybasic-1.0.4/bas/hammurabi.bas b/programs/develop/tinybasic-1.0.4/bas/hammurabi.bas new file mode 100644 index 0000000000..c3c5d65d14 --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/bas/hammurabi.bas @@ -0,0 +1,160 @@ + REM + REM --- Tiny BASIC Interpreter and Compiler Project + REM --- Hammurabi Demonstration Game + REM + REM --- Released as Public Domain by Damian Gareth Walker, 2019 + REM --- Created: 25-Aug-2019 + REM + + REM --- Variable List + REM + REM A - A random number returned by the Random Number Generator + REM B - The amount of land bought/sold by the player + REM D - The number of people who died of starvation + REM E - Average percentage of deaths + REM F - The amount of grain fed to the people + REM G - The quantity of stored grain + REM I - The number of immigrants on a given turn + REM L - How much land the city owns + REM M - Maximum land that can be planted + REM P - The number of people in the city + REM R - The amount of grain eaten by rats + REM S - The amount of grain planted as seed + REM T - Time played + REM V - The value of land per acre + REM Y - The grain yield of each piece of land + REM Z - The random number seed + + REM --- Initialise the random number seed + PRINT "Think of a number." + INPUT Z + + REM --- Initialise the game + LET D=0 + LET E=0 + LET G=2800 + LET I=5 + LET L=1000 + LET P=100 + LET R=200 + LET T=1 + LET Y=3 + + REM --- Print the report + 15 PRINT "Hammurabi, I beg to report to you," + PRINT "In year ",T,", ",D," people starved," + PRINT "and ",I," came to the city." + PRINT "You harvested ",Y," bushels of grain per acre." + PRINT "Rats destroyed ",R," bushels." + PRINT "Population is now ",P,"," + PRINT "and the city owns ",L," acres of land." + PRINT "You have ",G," bushels of grain in store." + IF D>P*45/100 THEN GOTO 100 + IF T=11 THEN GOTO 90 + + REM --- Buy land + GOSUB 250 + LET V=17+A-A/10*10 + PRINT "Land is trading at ",V," bushels per acre." + 30 PRINT "How many acres do you wish to buy (0-",G/V,")?" + INPUT B + IF B>G/V THEN GOTO 30 + IF B>0 THEN GOTO 40 + + REM --- Sell land + 35 PRINT "How many acres do you wish to sell (0-",L,")?" + INPUT B + IF B>L THEN GOTO 35 + LET B=-B + + REM --- Feed the people + 40 PRINT "How many bushels to feed the people (0-",G-B*V,")?" + INPUT F + IF F>=0 THEN IF F<=G-B*V THEN GOTO 45 + GOTO 40 + + REM --- Plant with seed + 45 LET M=2*(G-F-B*V) + IF 10*PM THEN GOTO 50 + + REM --- Work out the result + LET L=L+B + LET G=G-B*V-F-S/2 + + REM Yield + GOSUB 250 + LET Y=1+A-A/5*5 + + REM Rats + GOSUB 250 + LET A=1+A-A/5*5 + LET R=0 + IF A>2 THEN GOTO 70 + GOSUB 250 + IF G>0 THEN LET R=1+A-A/G*G + + REM Recalculate grain + 70 LET G=G+S*Y-R + IF G<0 THEN LET G=0 + + REM Immigration/Birth + GOSUB 250 + LET A=1+A-A/5*5 + LET I=A*(L+S/20)/P/5+1 + + REM Feeding the people + LET D=0 + IF P<=F/20 THEN GOTO 80 + LET D=P-F/20 + LET E=((T-1)*E+(100*D/P))/(T+1) + 80 LET P=P-D+I + IF P<=0 THEN GOTO 210 + LET T=T+1 + + REM Back to report + PRINT "" + GOTO 15 + + REM --- Evaluation + 90 PRINT "Your reign ends after ",T-1," years." + PRINT "You leave your city with ",P," people." + PRINT "You have ",L," acres of land to support them." + PRINT G," bushels of grain remain in store." + PRINT "" + IF E<=3 THEN IF L/P>=10 THEN GOTO 110 + IF E<=10 THEN IF L/P>=9 THEN GOTO 120 + IF E<=33 THEN IF L/P>=7 THEN GOTO 130 + + REM --- Terrible performance - including premature end +100 PRINT "Your performance has been so terrible that" + PRINT "you were driven from your throne after ",T," years!" + END + + REM --- Best performance +110 PRINT "Your expert statesmanship is worthy of Hammurabi himself!" + PRINT "The city will honour your memory for all eternity." + END + + REM --- Average performance +120 PRINT "Your competent rule is appreciated by your citizens." + PRINT "They will remember you fondly for some time to come." + END + + REM --- Poor performance +130 PRINT "Your mismanagement left your city in a very poor state." + PRINT "Your incompetence and oppression will not be missed by your people." + END + + REM --- Everybody starved +210 PRINT "You have starved your entire kingdom to death!" + END + + REM --- Random Number Generator +250 LET Z=5*Z+35 + LET Z=Z-Z/4096*4096 + LET A=Z + RETURN diff --git a/programs/develop/tinybasic-1.0.4/bas/hurkle.bas b/programs/develop/tinybasic-1.0.4/bas/hurkle.bas new file mode 100644 index 0000000000..e716589b2c --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/bas/hurkle.bas @@ -0,0 +1,67 @@ + REM + REM --- Tiny BASIC Interpreter and Compiler Project + REM --- Hunt the Hurkle Demostration Game + REM + REM --- Released as Public Domain by Damian Gareth Walker 2019 + REM --- Created: 11-Aug-2019 + REM + + REM --- Variables + REM G: hurkle column + REM H: hurkle row + REM M: moves taken + REM R: random number seed + REM X: player guess column + REM Y: player guess row + + REM --- Initialise the random number generator + PRINT "Think of a number." + INPUT R + IF R<0 THEN LET R=0 + IF R>4095 THEN LET R=4095 + + REM --- Initialise the game + GOSUB 200 + LET G=R-(R/10*10) + GOSUB 200 + LET H=R-(R/10*10) + LET M=0 + + REM --- Input player guess + 30 PRINT "Where is the hurkle? Enter column then row." + INPUT X,Y + IF X>=0 THEN IF X<=9 THEN IF Y>=0 THEN IF Y<=9 THEN GOTO 40 + PRINT "That location is off the grid!" + GOTO 30 + + REM --- Process player guess + 40 LET M=M+1 + PRINT "The Hurkle is..." + IF GX THEN IF HX THEN IF H=Y THEN PRINT "...to the east." + IF G>X THEN IF H>Y THEN PRINT "...to the southeast." + IF G=X THEN IF H>Y THEN PRINT "...to the south." + IF GY THEN PRINT "...to the southwest." + IF G6 THEN GOTO 70 + PRINT "You have taken ",M," turns so far." + GOTO 30 + + REM --- Player has won + 60 PRINT "...RIGHT HERE!" + PRINT "You took ",M," turns to find it." + END + + REM --- Player has lost + 70 PRINT "You have taken too long over this. You lose!" + END + + REM --- Random number generator + REM Input: R - current seed + REM Outputs: R - updated seed +200 LET R=5*R+35 + LET R=R-R/4096*4096 + RETURN \ No newline at end of file diff --git a/programs/develop/tinybasic-1.0.4/bas/lander.bas b/programs/develop/tinybasic-1.0.4/bas/lander.bas new file mode 100644 index 0000000000..02e671ca52 --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/bas/lander.bas @@ -0,0 +1,48 @@ + REM + REM --- Tiny BASIC Interpreter and Compiler Project + REM --- Lunar Lander Demonstration Game + REM + REM --- Released as Public Domain by Damian Gareth Walker 2019 + REM --- Created: 15-Aug-2019 + REM + + REM --- Variables: + REM A: altitude + REM B: fuel to burn this turn + REM F: fuel remaining + REM T: time elapsed + REM V: velocity this turn + REM W: velocity next turn + + REM --- Initialise the Program + LET A=1000 + LET B=0 + LET F=150 + LET V=50 + LET T=0 + + REM --- Main Loop +100 PRINT "Time:",T," Alt:",A," Velocity:",V," Fuel:",F," Thrust:",B +111 IF F>30 THEN PRINT "Thrust (0-30)?" + IF F<31 THEN PRINT "Thrust (0-",F,")?" + INPUT B + IF B>=0 THEN IF B<=30 THEN IF B<=F THEN GOTO 120 + GOTO 111 +120 LET W=V-B+5 + LET F=F-B + LET A=A-(V+W)/2 + LET V=W + LET T=T+1 + IF A>0 THEN GOTO 100 + + REM --- End of Game + IF V<5 THEN GOTO 140 + PRINT "You crashed!" + GOTO 160 +140 IF A<0 THEN GOTO 150 + PRINT "Perfect landing!" + GOTO 160 +150 PRINT "Touchdown." +160 IF A<0 THEN LET A=0 + PRINT "Time:",T," Alt:",A," Velocity:",V," Fuel:",F + END diff --git a/programs/develop/tinybasic-1.0.4/bas/mugwump.bas b/programs/develop/tinybasic-1.0.4/bas/mugwump.bas new file mode 100644 index 0000000000..7029de5a3e --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/bas/mugwump.bas @@ -0,0 +1,75 @@ + REM + REM --- Tiny BASIC Interpreter and Compiler Project + REM --- Mugwump Demonstration Game + REM + REM --- Released as Public Domain by Damian Gareth Walker 2019 + REM --- Created: 13-Aug-2019 + REM + + REM --- Variables + REM C: axis diff between player guess and mugwump position + REM D: distance between player guess and mugwump position + REM G: mugwump column + REM H: mugwump row + REM M: moves taken + REM R: random number generator seed + REM X: player guess column + REM Y: player guess row + + REM --- Initialise the random number generator + PRINT "Think of a number." + INPUT R + IF R<0 THEN LET R=0 + IF R>4095 THEN LET R=4095 + + REM --- Initialise the game + GOSUB 200 + LET G=R-(R/10*10) + GOSUB 200 + LET H=R-(R/10*10) + LET M=0 + + REM --- Input player guess + 10 PRINT "Where is the mugwump? Enter column then row." + INPUT X,Y + IF X>=0 THEN IF X<=9 THEN IF Y>=0 THEN IF Y<=9 THEN GOTO 20 + PRINT "That location is off the grid!" + GOTO 10 + + REM --- Process player guess + 20 LET M=M+1 + PRINT "The mugwump is..." + LET D=0 + LET C=G-X + GOSUB 60 + LET C=H-Y + GOSUB 60 + IF D=0 THEN GOTO 40 + PRINT "...",D," cells away." + IF M>10 THEN GOTO 50 + PRINT "You have taken ",M," turns so far." + GOTO 10 + + REM --- Player has won + 40 PRINT "...RIGHT HERE!" + PRINT "You took ",M," turns to find it." + END + + REM --- Player has lost + 50 PRINT "You have taken too long over this. You lose!" + END + + REM --- Helper subroutine to calculate distance from player to mugwump + REM Inputs: C - difference in rows or columns + REM D - running total distance + REM Output: D - running total distance, updated + 60 IF C<0 THEN LET C=-C + LET D=D+C + RETURN + + REM --- Random number generator + REM Input: R - current seed + REM Outputs: R - updated seed +200 LET R=5*R+35 + LET R=R-R/4096*4096 + RETURN \ No newline at end of file diff --git a/programs/develop/tinybasic-1.0.4/bas/tictactoe.bas b/programs/develop/tinybasic-1.0.4/bas/tictactoe.bas new file mode 100644 index 0000000000..d4732b9011 --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/bas/tictactoe.bas @@ -0,0 +1,204 @@ + REM + REM Tiny BASIC Interpreter and Compiler Project + REM Tic-tac-toe Sample Game + REM + REM Released as public domain by Damian Gareth Walker, 2019 + REM Created: 21-Sep-2019 + REM + + REM --- Variables + REM A - first square in line examined + REM B - second square in line examined + REM C - third square in line examined + REM D - player whose pieces to count + REM E - number of D's pieces on a line + REM F - first square of line to examine + REM G - game winner + REM H - which side the human takes + REM I - increment for line to examine + REM L - line to examine + REM M - where to move (various uses) + REM N - piece found in a square + REM P - player currently playing + REM Q - square to examine + REM R-Z - contents of the board + + REM --- Main Program + GOSUB 40 + GOSUB 60 + GOSUB 80 + END + + REM --- Subroutine to initialise the game + REM Outputs: H - Human play order + REM P - Whose turn it is + 40 PRINT "Tic tac toe. Board positions are:" + PRINT " 1 2 3" + PRINT " 4 5 6" + PRINT " 7 8 9" + PRINT "Play first or second (1/2)?" + INPUT H + IF H<1 THEN GOTO 40 + IF H>2 THEN GOTO 40 + LET P=1 + RETURN + + REM --- Subroutine to take turns + REM Inputs: H - who is the human + REM P - whose turn it is + REM Outputs: G - who won the game + 60 IF P=H THEN GOSUB 100 + IF P<>H THEN GOSUB 120 + GOSUB 200 + IF G>0 THEN RETURN + LET P=3-P + IF R=0 THEN GOTO 60 + IF S=0 THEN GOTO 60 + IF T=0 THEN GOTO 60 + IF U=0 THEN GOTO 60 + IF V=0 THEN GOTO 60 + IF W=0 THEN GOTO 60 + IF X=0 THEN GOTO 60 + IF Y=0 THEN GOTO 60 + IF Z=0 THEN GOTO 60 + RETURN + + REM --- Victory + REM Inputs: H - which side was the human + REM P - player who won + 80 IF G=H THEN PRINT "You win!" + IF G<>0 THEN IF G<>H THEN PRINT "Computer wins" + IF G=0 THEN PRINT "A draw" + RETURN + + REM --- Subroutine to allow the player to move + REM Inputs: P - player number + REM Outputs: M - where the player wishes to move +100 PRINT "Move? " + INPUT Q + IF Q<1 THEN GOTO 100 + IF Q>9 THEN GOTO 100 + GOSUB 220 + IF N<>0 THEN GOTO 100 + LET M=Q + GOSUB 240 + RETURN + + REM --- Subroutine to make the computer's move + REM Inputs: P - player number + REM Outputs: M - the move chosen +120 LET M=0 + LET D=3-H + GOSUB 145 + IF M>0 THEN GOTO 135 + LET D=H + GOSUB 145 + IF M=0 THEN IF V=0 THEN LET M=5 + IF M=0 THEN IF R=0 THEN LET M=1 + IF M=0 THEN IF T=0 THEN LET M=3 + IF M=0 THEN IF X=0 THEN LET M=7 + IF M=0 THEN IF Z=0 THEN LET M=9 + IF M=0 THEN IF S=0 THEN LET M=2 + IF M=0 THEN IF U=0 THEN LET M=4 + IF M=0 THEN IF Y=0 THEN LET M=8 + IF M=0 THEN IF W=0 THEN LET M=6 +135 GOSUB 240 + PRINT "Computer move ",M + RETURN + + REM --- Identify moves to win or avoid a loss + REM Inputs: D - player whose pieces we're counting + REM Changes: E - number of pieces on line being scanned + REM F - first square in winning line + REM I - increment of winning line + REM L - line being scanned (counter) +145 LET L=1 +146 GOSUB 170 + IF E<2 THEN GOTO 152 + IF A=0 THEN LET M=F + IF B=0 THEN LET M=F+I + IF C=0 THEN LET M=F+I+I + IF M>0 THEN RETURN +152 LET L=L+1 + IF L<9 THEN GOTO 146 + RETURN + + REM --- Count a player's pieces on a line + REM Inputs: D - player whose pieces we're counting + REM L - line number + REM Changes: F - first square on the line + REM I - increment of the line + REM Q - individual squares to examine + REM Outputs: A - contents of first square + REM B - contents of second square + REM C - contents of third square + REM E - number of the player's pieces +170 IF L>3 THEN GOTO 174 + LET F=3*L-2 + LET I=1 + GOTO 180 +174 IF L>6 THEN GOTO 178 + LET F=L-3 + LET I=3 + GOTO 180 +178 LET F=1+2*(L-7) + LET I=4-2*(L-7) +180 LET E=0 + LET Q=F + GOSUB 220 + LET A=N + IF N=D THEN LET E=E+1 + LET Q=Q+I + GOSUB 220 + LET B=N + IF N=D THEN LET E=E+1 + LET Q=Q+I + GOSUB 220 + LET C=N + IF N=D THEN LET E=E+1 + RETURN + + REM --- Subroutine to check for a win + REM Inputs: R-Z - board squares + REM Outputs: G - the winning player (0 for neither) +200 LET G=0 + IF R>0 THEN IF R=S THEN IF S=T THEN LET G=R + IF U>0 THEN IF U=V THEN IF V=W THEN LET G=U + IF X>0 THEN IF X=Y THEN IF Y=Z THEN LET G=X + IF R>0 THEN IF R=U THEN IF U=X THEN LET G=R + IF S>0 THEN IF S=V THEN IF V=Y THEN LET G=S + IF T>0 THEN IF T=W THEN IF W=Z THEN LET G=T + IF R>0 THEN IF R=V THEN IF V=Z THEN LET G=R + IF T>0 THEN IF T=V THEN IF V=X THEN LET G=T + RETURN + + REM --- Subroutine to see what piece is in a square + REM Inputs: Q - the square to check + REM R-Z - the contents of the squares + REM Outputs: N - the piece in that square +220 LET N=0 + IF Q=1 THEN LET N=R + IF Q=2 THEN LET N=S + IF Q=3 THEN LET N=T + IF Q=4 THEN LET N=U + IF Q=5 THEN LET N=V + IF Q=6 THEN LET N=W + IF Q=7 THEN LET N=X + IF Q=8 THEN LET N=Y + IF Q=9 THEN LET N=Z + RETURN + + REM --- Subroutine to put a piece in a square + REM Inputs: P - the player whose piece should be placed + REM M - the square to put the piece in + REM Changes: R-Z - the contents of the squares +240 IF M=1 THEN LET R=P + IF M=2 THEN LET S=P + IF M=3 THEN LET T=P + IF M=4 THEN LET U=P + IF M=5 THEN LET V=P + IF M=6 THEN LET W=P + IF M=7 THEN LET X=P + IF M=8 THEN LET Y=P + IF M=9 THEN LET Z=P + RETURN diff --git a/programs/develop/tinybasic-1.0.4/bas/wumpus.bas b/programs/develop/tinybasic-1.0.4/bas/wumpus.bas new file mode 100644 index 0000000000..a8847b2cde --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/bas/wumpus.bas @@ -0,0 +1,230 @@ + REM + REM --- Tiny BASIC Interpreter and Compiler Project + REM --- Hunt the Wumpus Demonstration Game + REM + REM --- Released as Public Domain by Damian Gareth Walker 2019 + REM --- Created: 08-Aug-2019 + REM + + REM --- Variable List + REM + REM A - Bat 1 position + REM B - Bat 2 position + REM C - Player position + REM D - Destination to move or shoot + REM E - exit 1 from the current cave + REM F - exit 2 from the current cave + REM G - exit 3 from the current cave + REM H - Hole 1 (bottomless pit) position + REM I - Hole 2 (bottomless pit) position + REM J - Randomised position for player or hazard + REM K - origin location of arrow in motion (before L) + REM L - previous location of arrow in motion + REM M - menu option for move or shoot + REM N - range for arrow shot + REM P - parameter to the 'exits' routine + REM Q - number of arrows in quiver + REM R - random number + REM S - random number generator seed + REM W - Wumpus position + + REM --- Intialise the random number generator + PRINT "Think of a number" + INPUT S + + REM --- Initialise the player and hazard positions + LET A=0 + LET B=0 + LET C=0 + LET H=0 + LET I=0 + LET W=0 + + REM --- Fill the player's quiver + LET Q=5 + + REM --- Distribute the player and hazards across the map + GOSUB 130 + LET A=J + GOSUB 130 + LET B=J + GOSUB 130 + LET C=J + GOSUB 130 + LET H=J + GOSUB 130 + LET I=J + GOSUB 130 + LET W=J + + REM --- Introductory text + PRINT "You enter the caves to Hunt the Wumpus!" + + REM --- Main Game Loop + 30 PRINT "You are in room ",C + LET P=C + GOSUB 200 + GOSUB 50 + PRINT "Exits are ",E,",",F,",",G + 35 PRINT "1:Move or 2:Shoot?" + INPUT M + IF M<1 THEN GOTO 35 + IF M>2 THEN GOTO 35 + IF M=1 THEN GOSUB 120 + IF M=2 THEN GOSUB 150 + GOTO 30 + + REM --- Subroutine to check for hazards + + REM Has the player encountered a bat? + 50 IF C<>A THEN IF C<>B THEN GOTO 60 + PRINT "A bat swoops down and picks you up..." + GOSUB 100 + PRINT "...and drops you down" + LET P=C + GOSUB 200 + + REM Has the player fallen in a pit? + 60 IF C<>H THEN IF C<>I THEN GOTO 65 + PRINT "You fall down a bottomless hole into the abyss!" + END + + REM Has the player startled the wumpus? + 65 IF C<>W THEN GOTO 70 + PRINT "You stumble upon the wumpus!" + GOSUB 90 + PRINT "The wumpus runs away!" + + REM Is there a pit nearby? + 70 IF E<>H THEN IF F<>H THEN IF G<>H THEN GOTO 72 + GOTO 73 + 72 IF E<>I THEN IF F<>I THEN IF G<>I THEN GOTO 75 + 73 PRINT "You feel a cold wind blowing from a nearby cavern." + + REM Is the wumpus nearby? + 75 IF E<>W THEN IF F<>W THEN IF G<>W THEN GOTO 80 + PRINT "You smell something terrible nearby." + + REM Is there a bat nearby? + 80 IF E<>A THEN IF F<>A THEN IF G<>A THEN GOTO 82 + GOTO 83 + 82 IF E<>B THEN IF F<>B THEN IF G<>B THEN GOTO 84 + 83 PRINT "You hear a loud squeaking and a flapping of wings." + 84 RETURN + + REM --- Relocate the Wumpus + 90 GOSUB 140 + LET R=R-(R/4*4) + IF R=1 THEN LET W=E + IF R=2 THEN LET W=F + IF R=3 THEN LET W=G + IF W<>C THEN RETURN + PRINT "The wumpus eats you!" + END + + REM --- Relocate bat and player +100 GOSUB 140 + LET R=R-(R/4*4) + IF R=0 THEN RETURN + LET P=C + GOSUB 200 + IF R=1 THEN LET D=E + IF R=2 THEN LET D=F + IF R=3 THEN LET D=G + IF D<>A THEN IF D<>B THEN GOTO 110 + GOTO 100 +110 PRINT "...moves you to room ",D,"..." + IF A=C THEN LET A=D + IF B=C THEN LET B=D + LET C=D + GOTO 100 + + REM --- Subroutine to move +120 PRINT "Where?" + INPUT D + IF D<>E THEN IF D<>F THEN IF D<>G THEN GOTO 120 + LET C=D + RETURN + + REM -- Find a random unoccupied position +130 GOSUB 140 + LET J=1+R-R/20*20 + IF J<>A THEN IF J<>B THEN IF J<>C THEN GOTO 134 + GOTO 130 +134 IF J<>H THEN IF J<>I THEN IF J<>W THEN RETURN + GOTO 130 + + REM --- Random number generator +140 LET S=5*S+35 + LET S=S-S/4096*4096 + LET R=S + RETURN + + REM --- Subroutine to shoot +150 PRINT "Shoot how far (1-5)?" + INPUT N + IF N<1 THEN GOTO 150 + IF N>5 THEN GOTO 150 + LET P=C + LET L=0 +160 GOSUB 200 + LET K=L + LET L=P + PRINT "Arrow is next to rooms ",E,",",F,",",G +164 PRINT "Shoot where?" + INPUT P + IF P<>E THEN IF P<>F THEN IF P<>G THEN GOTO 180 + IF P=K THEN GOTO 185 + IF P=W THEN GOTO 195 + LET N=N-1 + IF N>0 THEN GOTO 160 + LET Q=Q-1 + PRINT "The arrow startles the wumpus." + LET P=W + GOSUB 200 + GOSUB 90 + IF Q=0 THEN GOTO 190 + PRINT "You have ",Q," arrows left." + RETURN +180 PRINT "The arrow can't reach there." + GOTO 164 +185 PRINT "The arrow can't double back on itself." + GOTO 164 +190 PRINT "You used your last arrow!" + PRINT "Your demise is now inevitable." + END +195 PRINT "You hit the wumpus!" + END + + REM --- Subroutine to set the exits + REM Input: P - current position + REM Outputs: E, F, G (exits) +200 IF P>=1 THEN IF P<=20 THEN GOTO 205 + PRINT "Illegal position ",P + END +205 GOTO 200+10*((14+P)/10) + + REM --- Outer caves +210 LET E=P-1 + IF E=0 THEN LET E=5 + LET F=P+1 + IF F=6 THEN LET F=1 + LET G=4+2*P + RETURN + + REM --- Middle caves +220 LET E=P-1 + IF E=5 THEN LET E=15 + LET F=P+1 + IF F=16 THEN LET F=6 + IF P/2*2<>P THEN LET G=13+P/2 + IF P/2*2=P THEN LET G=P/2-2 + RETURN + + REM --- Inner caves +230 LET E=P-1 + IF E=15 THEN LET E=20 + LET F=P+1 + IF F=21 THEN LET F=16 + LET G=2*P-25 + RETURN \ No newline at end of file diff --git a/programs/develop/tinybasic-1.0.4/doc/specification.bnf b/programs/develop/tinybasic-1.0.4/doc/specification.bnf new file mode 100644 index 0000000000..36b2bb4242 --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/doc/specification.bnf @@ -0,0 +1,61 @@ + ::= + | + + ::= + "REM" | + "PRINT" | + "IF" "THEN" | + "GOTO" | + "INPUT" | + "LET" "=" | + "GOSUB" | + "RETURN" | + "END" + + ::= + | + + ::= + " " .. "~" + + ::= + | "," + + ::= + | + + ::= + | "," + + ::= + | + "+" | + "-" + + ::= + | + "*" | + "/" + +factor ::= + "-" | + "+" | + | + | + "(" ")" + + ::= + "A" .. "Z" + + ::= + | + + ::= + "0" .. "9" + + ::= + "<" | "=" | ">" | "<=" | "<>" | ">=" + + ::= + '"' '"' + diff --git a/programs/develop/tinybasic-1.0.4/doc/tinybasic.man b/programs/develop/tinybasic-1.0.4/doc/tinybasic.man new file mode 100644 index 0000000000..659a4c110d --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/doc/tinybasic.man @@ -0,0 +1,185 @@ +.TH TINYBASIC 1 +.SH NAME +tinybasic \- Tiny BASIC interpreter and compiler +.SH SYNOPSIS +.B tinybasic +[ \fBoptions\fR ] +.IR program-file +.SH DESCRIPTION +.B Tinybasic +is an implementation of the Tiny BASIC language. +It conforms to the specification by Dennis Allison, published in People's Computer Company Vol.4 No.2 and reprinted in Dr. Dobb's Journal, January 1976. +.PP +The package provides both an interpreter and a compiler in the same executable. +Both of these tools are non-interactive, and load their input from a source file written in a text editor. +No interactive interpreter is provided. +.PP +.B Tinybasic +provides a few additional features. Comments with the \fBREM\fR statement were not part of the original specification, but are allowed here. There is support for optional line numbers, and a configurable upper limit for them. Because not all lines need a number, this manual will refer to them as \'line labels.\' Where the phrase \'line number\' appears, it will refer to the actual line count in the source file, as a text editor would show. +.SH OPTIONS +.TP +.BR \-g " " \fIlimit\fR ", " \-\-gosub-limit\=\fIlimit\fR +Specifies the maximum depth of subroutine calls for the interpreter. Calling subroutinnes within subroutines to a level deeper than this will result in the "Too many GOSUBs" runtime error. This does not affect compiled code. +.TP +.BR \-n " " \fIvalue\fR ", " \-\-line\-numbers\=\fIvalue\fR +Determines the handling of line labels. An argument of \fBm\fR or \fBmandatory\fR causes \fBtinybasic\fR to require a line label for every program line, in ascending order. An argument of \fBi\fR or \fBimplied\fR causes \fBtinybasic\fR to supply labels internally for each line that lacks them; care must be taken when labelling lines so that there is room for a sequence of numbers between one line label and the next. An argument of \fBo\fR or \fBoptional\fR makes line labels completely optional; those that are supplied need not be in ascending order. +.TP +.BR \-N " " \fIlimit\fR ", " \-\-line\-number\-limit=\fIlimit\fR +Specifies the largest line label allowed in the BASIC program. The default is 32767, which is the highest value that \fBtinybasic\fR supports. The original Tiny BASIC had a limit of 255. +.TP +.BR \-o " " \fIcomment-option\fR ", " \-\-comments=\fIcomment-option\fR +Enables or disables support for comments and blank lines in programs. +\fIComment-options\fR can be \fBe\fR or \fBenabled\fR to support comments and blank lines, which is the default setting. +It can be \fBd\fR or \fBdisabled\fR to disable support for comments, as per the original Tiny BASIC specification. +.TP +.BR \-O " " [\fIoutput-type\fR] ", " \-\-output[=\fIoutput-type\fR] +Specifies compilation or translation instead of interpretation, and what type of output is desired. +If the option is supplied without an \fIoutput\-type\fR, then the default is \fBlst\fR. +If the option is absent altogether, then the program will be interpreted rather than compiled or translated. +Current \fIoutput\-type\fRs supported are \fBlst\fR for a formatted listing, \fBc\fR for a C program ready to compile, or \fBexe\fR. +Where the output type is \fBlst\fR or \fBc\fR the output filename is the same as the input filename, with an added extension the same as .\fIoutput\-type\fR. +Where the output type is \fBexe\fR, the output file is dependent on the input filename and the \fBTBEXE\fR (see the section on Compilation). +.SH PROGRAM FORMAT +Programs are text files loaded in on invoking \fBtinybasic\fR. +Each line of the file consists of an optional line label, a command keyword, and the command's parameters, if it has any. +Lines may be blank, or the command keyword may be REM, which denotes that the rest of the program line is a comment. +.PP +That describes a program written using \fBtinybasic\fR's additional features. +In a traditional Tiny BASIC program each line has a mandatory line label, a command keyword which may \fInot\fR be \fBREM\fR, and the command's parameters. +The original Tiny BASIC specification did not provide for comments or blank lines, and the line labels were required by the language's interactive line editor. +.SH COMMANDS +.TP +.BR \fBLET\fR " " \fIvariable\fR = \fIexpression\fR +Assigns a value, the result of \fIexpression\fR, to a variable, \fIvariable\fR. \fIVariable\fR must be a single letter, A..Z. +\fIExpression\fR must evaluate to an integer in the range -32768 to 32767. +.TP +.BR \fBIF\fR " " \fIcondition\fR " " \fBTHEN\fR " " \fIstatement\fR +Conditional execution. +If \fIcondition\fR is true, then \fIstatement\fR is executed. +\fIStatement\fR can be another \fBIF\fR, allowing conditions to be chained, effectively mimicking an AND operator. +.TP +.BR \fBGOTO\fR " " \fIexpression\fR +Transfer execution to another part of the program. +\fIExpression\fR is evaluated, and program execution continues at the line marked with the corresponding label. +.TP +.BR \fBGOSUB\fR " " \fIexpression\fR +Calls a subroutine. +\fIExpression\fR is evaluated, and program execution transfers to the line marked with the corresponding label. +The position of the \fBGOSUB\fR is remembered so that a \fBRETURN\fR can bring program execution back to the statement following the \fBGOSUB\fR. +.TP +.BR \fBRETURN\fR +Return from a subroutine. +Program execution returns to the statement following the \fBGOSUB\fR which called the present subroutine. +.TP +.BR \fBEND\fR +Terminates program execution. +.TP +.BR \fBPRINT\fR " " \fIoutput-list\fR +Produces output to the console. +\fIOutput-list\fR is a list of items separated by commas. +Each item can be either a string literal enclosed in double quotation marks, or a numeric expression. +An end of line sequence is output after all the values, so that the next \fBPRINT\fR statement will put its output on a new line. +.TP +.BR \fBINPUT\fR " " \fIvariable-list\fR +Asks for input from the console. +\fIVariable-list\fR is a list of variable names. +For each variable given, a question mark is output and the value typed by the user is stored in that variable. \fBTinybasic\fR allows multiple values to be typed by the user on one line, each separated by any non-numeric character. +.TP +.BR \fBREM\fR " " \fIcomment-text\fR +Provides space for free-format comment text in the program. +Comments have no effect on the execution of a program, and exist only to provide human-readable information to the programmer. +Use of this command will raise an error if support for comments is disabled (see the \fB-o\fR/\fB--comment\fR option above). +.SH EXPRESSIONS +Expressions in Tiny BASIC are purely arithmetic expressions, involving integers only. +The four basic arithmetic operators are supported: multiplication (\fB*\fR), division (\fB/\fR), addition (\fB+\fR) and subtraction (\fB-\fR). +Unary operators for positive (\fB+\fR) and negative (\fB-\fR) are supported, as are parentheses for affecting the order of operations. +.PP +Standard operator precedence evaluates parentheses first, then unary signs, then multiplication and division, with addition and subtraction last. +.SH CONDITIONS +The relational operators are \fB=\fR, \fB>\fR, \fB<\fR, \fB<>\fR or \fB><\fR, \fB>=\fR, and \fB<=\fR. +They are not supported within arithmetic expressions, but can only be used as conditions in \fBIF\fR statements in the form: +.BR \fIexpression\fR " " \fIrelational-operator\fR " " \fIexpression\fR +.SH COMPILATION +\fBTinybasic\fR is capable of compiling programs into executables with the help of a C compiler. +To use this facility, the \fBTBEXE\fR environment variable must be set before invoking \fBtinybasic\fR. +The variable should contain the command that compiles a C program into an executable, and may contain the following tokens: +.PP +\fB$(SOURCE)\fR: the C source filename is substituted here. +.br +\fB$(TARGET)\fR: a target filename is substituted here. +.TP +The C source filename will be the same as the BASIC filename but with the extension \fB.c\fR added. The target filename is the BASIC source filename with the \fB.bas\fR extension removed; if the BASIC source filename has no extension, then \fB.out\fR is added to prevent the source being overwritten by the executable. If your operating system requires an extension like \fB.exe\fR for its executables, then you need to add it explicitly (i.e. \fB$(TARGET).exe\fR) - unless the compiler adds that itself. As an example, the file \fBtest.bas\fR could be compiled on a Unix system with the following commands: +.PP +$ TBEXE='gcc -o $(TARGET) $(SOURCE)' +.br +$ tinybasic -Oexe test.bas +.TP +This would produce the executable file \fBtest\fR, and as a side effect, the C source file \fBtest.bas.c\fR. +.SH ERROR MESSAGES +Program error messages can be in one of two forms: +.PP +Parse error: \fIdescription\fR, line \fIline-number\fR, label \fIline-label\fR +.br +Run-time error: \fIdescription\fR, label \fIline-label\fR +.TP +Parse errors are those that are detected before the program starts. Run-time errors are those that cannot be detected until the program is running. If a parse error is detected on a line without a label, then the label section is omitted from the error message. The error messages and their meanings are as follows. +.TP +.SS Invalid line number +One of the following has occurred: (i) a line label is missing when line numbers are mandatory; (ii) a line label is lower than the previous one when line numbers are mandatory or implied. +.TP +.SS Unrecognised command +The command keyword is not recognised. Note that \fBREM\fR will not be recognised when comments are disabled, and will produce this error. +.TP +.SS Invalid variable +In a \fBLET\fR or \fBINPUT\fR statement, something other than a letter from \fBA\fR to \fBZ\fR was supplied when a variable name was expected. +.TP +.SS Invalid assignment +The \fB=\fR sign was missing from a \fBLET\fR statement. +.TP +.SS Invalid expression +An expression in this line is invalid. It is possibly lacking an operator, variable or value where one is expected. +.TP +.SS Missing ) +An expression contains a left parenthesis and no corresponding right parenthesis. +.TP +.SS Invalid PRINT output +Something is wrong with the output list in a \fBPRINT\fR statement. It could be: (i) completely missing, (ii) missing a separator between two items, or (iii) missing an item between two separators or at the start or end of the list. +.TP +.SS Invalid operator +An unrecognised operator was encountered in an expression or a condition. +.TP +.SS THEN expected +The mandatory \fBTHEN\fR keyword is missing from its expected place in an \fBIF\fR statement. +.TP +.SS Unexpected parameter +A parameter was given to a command that should not have one, such as \fBEND\fR or \fBRETURN\fR. +.TP +.SS RETURN without GOSUB +A \fBRETURN\fR was encountered without having executed a \fBGOSUB\fR. This commonly occurs when a programmer forgets to put an \fBEND\fR or a \fBGOTO\fR before a subroutine, and allows execution to blunder into it. +.TP +.SS Divide by zero +The divisor in an expression was \fB0\fR. If dividing by a variable or an expression, it is advisable to check beforehand that it cannot be zero. An intentional division by zero is not the most graceful way to stop a program. +.TP +.SS Overflow +When given as a parse error, there is a value in the program that is outside the range of \fB-32768\fR to \fB32767\fR. When given as a runtime error, an expression in the program or an input from the user has produced a result outside this range. +.TP +.SS Too Many GOSUBs +Subroutines were called to a level deeper than the \fBGOSUB\fR limit allows. Often encountered because of runaway recursion, or because an incorrect label was given in a \fBGOSUB\fR statement causing a subroutine to unintentionally call itself. +.SH VERSION INFORMATION +This manual page documents \fBtinybasic\fR, version 1.0.4. +.SH AUTHORS +Tiny BASIC was originally designed by Dennis Allison. This implementation was written by Damian Gareth Walker. +.SH EXAMPLE +This program prints out all of the numbers in the Fibonnaci series between 0 and 1000. +.PP +.nf +.ft B + LET A=0 + LET B=1 + PRINT A + 100 PRINT B + LET B=A+B + LET A=B-A + IF B<=1000 THEN GOTO 100 + END +.ft R diff --git a/programs/develop/tinybasic-1.0.4/inc/common.h b/programs/develop/tinybasic-1.0.4/inc/common.h new file mode 100644 index 0000000000..dfef169c9a --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/inc/common.h @@ -0,0 +1,30 @@ +/* + * Tiny BASIC Interpreter and Compiler Project + * Common service routines module + * + * Released as Public Domain by Damian Gareth Walker 2019 + * Created: 20-Sep-2019 + */ + + +#ifndef __COMMON_H__ +#define __COMMON_H__ + + +/* + * Function Declarations + */ + + +/* + * Portable case-insensitive comparison + * params: + * char* a string to compare + * char* b string to compare to + * returns: + * int -1 if ab + */ +int tinybasic_strcmp (char *a, char *b); + + +#endif diff --git a/programs/develop/tinybasic-1.0.4/inc/errors.h b/programs/develop/tinybasic-1.0.4/inc/errors.h new file mode 100644 index 0000000000..40b0d41dec --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/inc/errors.h @@ -0,0 +1,67 @@ +/* + * Tiny BASIC + * Error Handling Header + * + * Copyright (C) Damian Gareth Walker 2019 + * Created: 15-Aug-2019 + */ + + +#ifndef __ERRORS_H__ +#define __ERRORS_H__ + + +/* + * Data Definitions + */ + +/* error codes */ +typedef enum { + E_NONE, /* no error; everything is fine */ + E_INVALID_LINE_NUMBER, /* line number is invalid */ + E_UNRECOGNISED_COMMAND, /* command was not recognised */ + E_INVALID_VARIABLE, /* variable expected but something else encountered */ + E_INVALID_ASSIGNMENT, /* = expected but something else encountered */ + E_INVALID_EXPRESSION, /* an invalid expression was encountered */ + E_MISSING_RIGHT_PARENTHESIS, /* Encountered "(" without corresponding ")" */ + E_INVALID_PRINT_OUTPUT, /* failed to parse print output */ + E_BAD_COMMAND_LINE, /* error on invocation */ + E_FILE_NOT_FOUND, /* cannot open source file */ + E_INVALID_OPERATOR, /* unrecognised operator */ + E_THEN_EXPECTED, /* didn't find the expected THEN after an IF */ + E_UNEXPECTED_PARAMETER, /* more parameters encountered than expected */ + E_RETURN_WITHOUT_GOSUB, /* return encountered without a GOSUB */ + E_DIVIDE_BY_ZERO, /* an attempt to divide by zero */ + E_OVERFLOW, /* integer is out of range */ + E_MEMORY, /* out of memory */ + E_TOO_MANY_GOSUBS, /* recursive GOSUBs exceeded the stack size */ + E_LAST /* placeholder */ +} ErrorCode; + +/* error handler structure */ +typedef struct error_handler ErrorHandler; +typedef struct error_handler { + void *data; /* private data */ + void (*set_code) (ErrorHandler *, ErrorCode, int, int); + ErrorCode (*get_code) (ErrorHandler *); + int (*get_line) (ErrorHandler *); + int (*get_label) (ErrorHandler *); + char *(*get_text) (ErrorHandler *); + void (*destroy) (ErrorHandler *); +} ErrorHandler; + + +/* + * Constructors + */ + + +/* + * Principal constructor + * returns: + * ErrorHandler* the new error handler object + */ +ErrorHandler *new_ErrorHandler (void); + + +#endif diff --git a/programs/develop/tinybasic-1.0.4/inc/expression.h b/programs/develop/tinybasic-1.0.4/inc/expression.h new file mode 100644 index 0000000000..bd47314128 --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/inc/expression.h @@ -0,0 +1,176 @@ +/* + * Tiny BASIC + * Expression Handling Header + * + * Released as Public Domain by Damian Gareth Walker 2019 + * Created: 16-Aug-2019 + */ + + +#ifndef __EXPRESSION_H__ +#define __EXPRESSION_H__ + + +/* Forward Declarations */ +typedef struct expression_node ExpressionNode; +typedef struct right_hand_term RightHandTerm; +typedef struct term_node TermNode; +typedef struct right_hand_factor RightHandFactor; + + +/* + * Data Definitions - Factors + */ + + +/* The different classes of factor */ +typedef enum { + FACTOR_NONE, /* class as yet undetermined */ + FACTOR_VARIABLE, /* a variable */ + FACTOR_VALUE, /* an integer value */ + FACTOR_EXPRESSION /* a bracketed expression */ +} FactorClass; + +/* The signs */ +typedef enum { + SIGN_POSITIVE, + SIGN_NEGATIVE +} SignClass; + +/* A factor */ +typedef struct { + FactorClass class; /* what kind of factor is this? */ + SignClass sign; /* which sign, positive or negative? */ + union { + int variable; + int value; + ExpressionNode *expression; + } data; +} FactorNode; + + +/* The term operators */ +typedef enum { + TERM_OPERATOR_NONE, /* single-factor term */ + TERM_OPERATOR_MULTIPLY, /* multiply */ + TERM_OPERATOR_DIVIDE /* divide */ +} TermOperator; + +/* the operator and right-hand part of a term */ +typedef struct right_hand_factor { + TermOperator op; /* the operator to apply: muliply or divide */ + FactorNode *factor; /* the factor to multiply or divide by */ + RightHandFactor *next; /* the next part of the term, if any */ +} RightHandFactor; + +/* A term */ +typedef struct term_node { + FactorNode *factor; /* the first factor of an expression */ + RightHandFactor *next; /* the right side term, if any */ +} TermNode; + +/* Expression Operator */ +typedef enum { + EXPRESSION_OPERATOR_NONE, /* single-term expression */ + EXPRESSION_OPERATOR_PLUS, /* addition */ + EXPRESSION_OPERATOR_MINUS /* substraction */ +} ExpressionOperator; + +/* the operator and right-hand part of an expression */ +typedef struct right_hand_term { + ExpressionOperator op; /* the operator to apply: plus or minus */ + TermNode *term; /* the term to add or subtract */ + RightHandTerm *next; /* next part of the expression, if any */ +} RightHandTerm; + +/* An expression */ +typedef struct expression_node { + TermNode *term; /* The first term of an expression */ + RightHandTerm *next; /* the right side expression, if any */ +} ExpressionNode; + + +/* + * Function Declarations + */ + + +/* + * Constructor for a factor + * returns: + * FactorNode* the new factor + */ +FactorNode *factor_create (void); + +/* + * Destructor for a factor + * params: + * FactorNode* factor the doomed factor + */ +void factor_destroy (FactorNode *factor); + +/* + * Constructor for a right-hand factor of a term + * returns: + * RightHandFactor* the new RH factor of a term + */ +RightHandFactor *rhfactor_create (void); + +/* + * Recursive destructor for a right-hand factor of a term + * params: + * RightHandFactor* rhterm the doomed RH factor of a term + */ +void rhfactor_destroy (RightHandFactor *rhterm); + +/* + * Constructor for a term + * returns: + * TermNode* the new term + */ +TermNode *term_create (void); + +/* + * Destructor for a term + * params: + * TermNode* term the doomed term + */ +void term_destroy (TermNode *term); + +/* + * Constructor for a right-hand expression + * returns: + * RightHandTerm* the RH term of an expression + */ +RightHandTerm *rhterm_create (void); + +/* + * Recursive destructor for a right-hand term of an expression + * params: + * RightHandTerm* rhexpression the doomed RH expression + */ +void rhterm_destroy (RightHandTerm *rhterm); + +/* + * Constructor for an expression + * returns: + * ExpressionNode* the new expression + */ +ExpressionNode *expression_create (void); + +/* + * Destructor for a factor + * params: + * FactorNode* factor the doomed factor + */ +void factor_destroy (FactorNode *factor); + +/* + * Destructor for a expression + * params: + * ExpressionNode* expression the doomed expression + */ +void expression_destroy (ExpressionNode *expression); + + +#endif diff --git a/programs/develop/tinybasic-1.0.4/inc/formatter.h b/programs/develop/tinybasic-1.0.4/inc/formatter.h new file mode 100644 index 0000000000..887d86d81e --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/inc/formatter.h @@ -0,0 +1,66 @@ +/* + * Tiny BASIC + * Listing Output Header + * + * Released as Public Domain by Damian Gareth Walker, 2019 + * Created: 18-Sep-2019 + */ + + +#ifndef __FORMATTER_H__ +#define __FORMATTER_H__ + + +/* included headers */ +#include "errors.h" +#include "statement.h" + + +/* + * Data Declarations + */ + + +/* Formatter Class */ +typedef struct formatter_data FormatterData; +typedef struct formatter Formatter; +typedef struct formatter { + + /* Properties */ + FormatterData *priv; /* private data */ + char *output; /* the formatted output */ + + /* + * Create a formatted version of the program + * params: + * Formatter* the formatter + * ProgramNode* the syntax tree + * returns: + * char* the formatted BASIC program + */ + void (*generate) (Formatter *, ProgramNode *); + + /* + * Destroy the formatter when no longer needed + * params: + * Formatter* the doomed formatter + */ + void (*destroy) (Formatter *); + +} Formatter; + + +/* + * Function Declarations + */ + + +/* + * The Formatter constructor + * returns: + * Formatter* the new formatter + */ +Formatter *new_Formatter (ErrorHandler *errors); + + +#endif diff --git a/programs/develop/tinybasic-1.0.4/inc/generatec.h b/programs/develop/tinybasic-1.0.4/inc/generatec.h new file mode 100644 index 0000000000..8a7d0ae0a0 --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/inc/generatec.h @@ -0,0 +1,50 @@ +/* + * Tiny BASIC Interpreter and Compiler Project + * C Output Header + * + * Copyright (C) Damian Gareth Walker 2019 + * Created: 03-Oct-2019 + */ + + +#ifndef __GENERATEC_H__ +#define __GENERATEC_H__ + + +/* included headers */ +#include "errors.h" +#include "options.h" + +/* forward references */ +typedef struct c_program CProgram; + +/* object structure */ +typedef struct c_program { + void *private_data; /* private data */ + char *c_output; /* the generated C code */ + void (*generate) (CProgram *, ProgramNode *); /* generate function */ + void (*destroy) (CProgram *); /* destructor */ +} CProgram; + + +/* + * Function Declarations + */ + + +/* + * Constructor + * params: + * ErrorHandler* compiler_errors the error handler + * LanguageOptions* compiler_options language options + * changes: + * CProgram* this the object being created + * Private* data the object's private data + * returns: + * CProgram* the created object + */ +CProgram *new_CProgram (ErrorHandler *compiler_errors, + LanguageOptions *compiler_options); + + +#endif \ No newline at end of file diff --git a/programs/develop/tinybasic-1.0.4/inc/interpret.h b/programs/develop/tinybasic-1.0.4/inc/interpret.h new file mode 100644 index 0000000000..a5ace8764f --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/inc/interpret.h @@ -0,0 +1,64 @@ +/* + * Tiny BASIC Interpreter and Compiler Project + * Interpreter header + * + * Released as Public Domain by Damian Gareth Walker 2019 + * Created: 23-Aug-2019 + */ + + +#ifndef __INTERPRET_H__ +#define __INTERPRET_H__ + + +/* included headers */ +#include "errors.h" +#include "options.h" +#include "statement.h" + + +/* + * Data Declarations + */ + + +/* the interpreter object */ +typedef struct interpreter_data InterpreterData; +typedef struct interpreter Interpreter; +typedef struct interpreter { + + /* Properties */ + InterpreterData *priv; /* private data */ + + /* + * Interpret the program + * params: + * Interpreter* the interpreter to use + * ProgramNode* the program to interpret + */ + void (*interpret) (Interpreter *, ProgramNode *); + + /* + * Destructor + * params: + * Interpreter* the doomed interpreter + */ + void (*destroy) (Interpreter *); + +} Interpreter; + + +/* + * Function Declarations + */ + + +/* + * Constructor + * returns: + * Interpreter* the new interpreter + */ +Interpreter *new_Interpreter (ErrorHandler *errors, LanguageOptions *options); + + +#endif diff --git a/programs/develop/tinybasic-1.0.4/inc/options.h b/programs/develop/tinybasic-1.0.4/inc/options.h new file mode 100644 index 0000000000..0c6e642f8a --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/inc/options.h @@ -0,0 +1,61 @@ +/* + * Tiny BASIC Interpreter and Compiler Project + * Language Options Header + * + * Released as Public Domain by Damian Gareth Walker 2019 + * Created: 18-Aug-2019 + */ + + +#ifndef __OPTIONS_H__ +#define __OPTIONS_H__ + + +/* + * Data Definitions + */ + + +/* line number options */ +typedef enum { + LINE_NUMBERS_OPTIONAL, /* they are optional numeric labels */ + LINE_NUMBERS_IMPLIED, /* missing line numbers are implied */ + LINE_NUMBERS_MANDATORY /* every line requires a number in sequence */ +} LineNumberOption; + +/* comment options */ +typedef enum { + COMMENTS_ENABLED, /* comments and blank lines are allowed */ + COMMENTS_DISABLED /* comments and blank lines are not allowed */ +} CommentOption; + +/* language options */ +typedef struct language_options LanguageOptions; +typedef struct language_options { + void *data; /* private data */ + void (*set_line_numbers) (LanguageOptions *, LineNumberOption); + void (*set_line_limit) (LanguageOptions *, int); + void (*set_comments) (LanguageOptions *, CommentOption); + void (*set_gosub_limit) (LanguageOptions *, int); + LineNumberOption (*get_line_numbers) (LanguageOptions *); + int (*get_line_limit) (LanguageOptions *); + CommentOption (*get_comments) (LanguageOptions *); + int (*get_gosub_limit) (LanguageOptions *); + void (*destroy) (LanguageOptions *); +} LanguageOptions; + + +/* + * Function Declarations + */ + + +/* + * Constructor for language options + * returns: + * LanguageOptions* the new language options object + */ +LanguageOptions *new_LanguageOptions (void); + + +#endif diff --git a/programs/develop/tinybasic-1.0.4/inc/parser.h b/programs/develop/tinybasic-1.0.4/inc/parser.h new file mode 100644 index 0000000000..f8dfe823b1 --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/inc/parser.h @@ -0,0 +1,93 @@ +/* + * Tiny BASIC + * Parser Header + * + * Released as Public Domain by Damian Gareth Walker 2019 + * Created: 08-Aug-2019 + */ + + +#ifndef __PARSER_H__ +#define __PARSER_H__ + + +/* pre-requisite headers */ +#include "statement.h" +#include "errors.h" +#include "options.h" +#include "tokeniser.h" + + +/* + * Data Declarations + */ + + +/* the parser */ +typedef struct parser_data ParserData; +typedef struct parser Parser; +typedef struct parser { + + /* Properties */ + ParserData *priv; /* parser's private data */ + + /* + * Public methods + */ + + /* + * Parse the whole program + * params: + * Parser* The parser to use + * INPUT* The input file to parse + * returns: + * ProgramNode* The parsed program + */ + ProgramNode *(*parse) (Parser *); + + /* + * Return the current source line we're parsing + * params: + * Parser* The parser to use + * returns: + * int the line returned + */ + int (*get_line) (Parser *); + + /* + * Return the label of the source line we're parsing + * params: + * Parser* The parser to use + * returns: + * int the label returned + */ + int (*get_label) (Parser *); + + /* + * Destroy this parser object + * params: + * Parser* the doomed parser + */ + void (*destroy) (Parser *); + +} Parser; + + +/* + * Function Declarations + */ + + +/* + * Constructor + * params: + * ErrorHandler* the error handler to use + * LanguageOptions* the language options to use + * FILE* the input file + * returns: + * Parser* the new parser + */ +Parser *new_Parser (ErrorHandler *, LanguageOptions *, FILE *); + + +#endif diff --git a/programs/develop/tinybasic-1.0.4/inc/statement.h b/programs/develop/tinybasic-1.0.4/inc/statement.h new file mode 100644 index 0000000000..0c38ebf419 --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/inc/statement.h @@ -0,0 +1,233 @@ +/* + * Tiny BASIC + * Statement Handling Header + * + * Released as Public Domain by Damian Gareth Walker 2019 + * Created: 15-Aug-2019 + */ + + +#ifndef __STATEMENT_H__ +#define __STATEMENT_H__ + + +/* Pre-requisite headers */ +#include "expression.h" + +/* Forward Declarations */ +typedef struct program_line_node ProgramLineNode; +typedef struct statement_node StatementNode; +typedef struct output_node OutputNode; +typedef struct variable_list_node VariableListNode; + + +/* + * Data Definitions + */ + + +/* The types of output allowed in a PRINT statement */ +typedef enum { + OUTPUT_STRING, /* a literal string */ + OUTPUT_EXPRESSION /* an expression */ +} OutputClass; + +/* The relational operators */ +typedef enum { + RELOP_EQUAL, /* = */ + RELOP_UNEQUAL, /* <> or >< */ + RELOP_LESSTHAN, /* < */ + RELOP_LESSOREQUAL, /* <= */ + RELOP_GREATERTHAN, /* > */ + RELOP_GREATEROREQUAL /* >= */ +} RelationalOperator; + +/* Expressions or strings to output */ +typedef struct output_node { + OutputClass class; /* string or expression */ + union { + char *string; /* a literal string to output */ + ExpressionNode *expression; /* an expression to output */ + } output; + OutputNode *next; /* the next output node, if any */ +} OutputNode; + +/* List of variables to input */ +typedef struct variable_list_node { + int variable; /* the variable */ + VariableListNode *next; /* next variable, if any */ +} VariableListNode; + +/* + * Structures for statements in general + */ + +/* Let Statement Node */ +typedef struct { + int variable; /* the variable to assign, 1..26 for A..Z */ + ExpressionNode *expression; /* the expression to assign to it */ +} LetStatementNode; + +/* If Statement Node */ +typedef struct { + ExpressionNode *left; /* the left-hand expression */ + RelationalOperator op; /* the comparison operator used */ + ExpressionNode *right; /* the right-hand expression */ + StatementNode *statement; /* statement to execute if condition is true */ +} IfStatementNode; + +/* Print Statement Node */ +typedef struct { + OutputNode *first; /* the first expression to print */ +} PrintStatementNode; + +/* Input Statement Node */ +typedef struct { + VariableListNode *first; /* the first variable to input */ +} InputStatementNode; + +/* Goto Statement Node */ +typedef struct { + ExpressionNode *label; /* an expression that computes the label */ +} GotoStatementNode; + +/* Gosub Statement Node */ +typedef struct { + ExpressionNode *label; /* an expression that computes the label */ +} GosubStatementNode; + +/* Statement classes */ +typedef enum { + STATEMENT_NONE, /* unknown statement */ + STATEMENT_LET, /* LET variable=expression */ + STATEMENT_IF, /* IF condition THEN statement */ + STATEMENT_GOTO, /* GOTO expression */ + STATEMENT_GOSUB, /* GOSUB expression */ + STATEMENT_RETURN, /* RETURN */ + STATEMENT_END, /* END */ + STATEMENT_PRINT, /* PRINT print-list */ + STATEMENT_INPUT /* INPUT var-list */ +} StatementClass; + +/* Common Statement Node */ +typedef struct statement_node { + StatementClass class; /* which type of statement this is */ + union { + LetStatementNode *letn; /* a LET statement */ + IfStatementNode *ifn; /* an IF statement */ + GotoStatementNode *goton; /* a GOTO statement */ + GosubStatementNode *gosubn; /* a GOSUB statement */ + /* a RETURN statement requires no extra data */ + /* an END statement requires no extra data */ + PrintStatementNode *printn; /* a PRINT statement */ + InputStatementNode *inputn; /* an INPUT statement */ + } statement; +} StatementNode; + +/* a program line */ +typedef struct program_line_node { + int label; /* line label */ + StatementNode *statement; /* the current statement */ + ProgramLineNode *next; /* the next statement */ +} ProgramLineNode; + + +/* the program */ +typedef struct { + ProgramLineNode *first; /* first program statement */ +} ProgramNode; + + +/* + * Function Declarations + */ + + +/* + * LET statement constructor + * returns: + * LetStatementNode* the created LET statement + */ +LetStatementNode *statement_create_let (void); + +/* + * IF statement constructor + * returns: + * IfStatementNode* the created IF statement + */ +IfStatementNode *statement_create_if (void); + +/* + * GOTO Statement Constructor + * returns: + * GotoStatementNode* the new GOTO statement + */ +GotoStatementNode *statement_create_goto (void); + +/* + * GOSUB Statement Constructor + * returns: + * GosubStatementNode* the new GOSUB statement + */ +GosubStatementNode *statement_create_gosub (void); + +/* + * PRINT statement constructor + * returns: + * PrintStatementNode* the created PRINT statement + */ +PrintStatementNode *statement_create_print (void); + +/* + * INPUT statement constructor + * returns: + * InputStatementNode* initialised INPUT statement data + */ +InputStatementNode *statement_create_input (void); + +/* + * Statement constructor + * returns: + * StatementNode* the newly-created blank statement + */ +StatementNode *statement_create (void); + +/* + * Statement destructor + * params: + * StatementNode* statement the doomed statement + */ +void statement_destroy (StatementNode *statement); + +/* + * Program Line Constructor + * returns: + * ProgramLineNode* the new program line + */ +ProgramLineNode *program_line_create (void); + +/* + * Program Line Destructor + * params: + * ProgramLineNode* program_line the doomed program line + * params: + * ProgramLineNode* the next program line + */ +ProgramLineNode *program_line_destroy (ProgramLineNode *program_line); + +/* + * Program Constructor + * returns: + * ProgramNode* the constructed program + */ +ProgramNode *program_create (void); + +/* + * Program Destructor + * params: + * ProgramNode* program the doomed program + */ +void program_destroy (ProgramNode *program); + + +#endif diff --git a/programs/develop/tinybasic-1.0.4/inc/token.h b/programs/develop/tinybasic-1.0.4/inc/token.h new file mode 100644 index 0000000000..d832b67406 --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/inc/token.h @@ -0,0 +1,98 @@ +/* + * Tiny BASIC + * Token Handling Header + * + * Copyright (C) Damian Walker 2019 + * Created: 15-Aug-2019 + */ + + +#ifndef __TOKEN_H__ +#define __TOKEN_H__ + + +/* + * Type Declarations + */ + + +/* token classes */ +typedef enum + { + TOKEN_NONE, /* no token has yet been identified */ + TOKEN_EOF, /* end of file */ + TOKEN_EOL, /* end of line */ + TOKEN_WORD, /* an identifier or keyword - token to be removed */ + TOKEN_NUMBER, /* a numeric constant */ + TOKEN_SYMBOL, /* a legal symbol - token to be removed */ + TOKEN_STRING, /* a string constant */ + TOKEN_LET, /* the LET keyword */ + TOKEN_IF, /* the IF keyword */ + TOKEN_THEN, /* the THEN keyword */ + TOKEN_GOTO, /* the GOTO keyword */ + TOKEN_GOSUB, /* the GOSUB keyword */ + TOKEN_RETURN, /* the RETURN keyword */ + TOKEN_END, /* the END keyword */ + TOKEN_PRINT, /* the PRINT keyword */ + TOKEN_INPUT, /* the INPUT keyword */ + TOKEN_REM, /* the REM keyword */ + TOKEN_VARIABLE, /* a single letter A..Z */ + TOKEN_PLUS, /* addition or unary positive */ + TOKEN_MINUS, /* subtraction or unary negative */ + TOKEN_MULTIPLY, /* multiplication */ + TOKEN_DIVIDE, /* division */ + TOKEN_LEFT_PARENTHESIS, /* open parenthesis */ + TOKEN_RIGHT_PARENTHESIS, /* close parenthesis */ + TOKEN_EQUAL, /* = */ + TOKEN_UNEQUAL, /* <> or >< */ + TOKEN_LESSTHAN, /* < */ + TOKEN_LESSOREQUAL, /* <= */ + TOKEN_GREATERTHAN, /* > */ + TOKEN_GREATEROREQUAL, /* >= */ + TOKEN_COMMA, /* comma separator */ + TOKEN_ILLEGAL /* unrecognised characters */ + } TokenClass; + +/* token structure */ +typedef struct token Token; +typedef struct token +{ + void *data; /* private data */ + TokenClass (*get_class) (Token *); + int (*get_line) (Token *); + int (*get_pos) (Token *); + char *(*get_content) (Token *); + void (*set_class) (Token *, TokenClass); + void (*set_line_pos) (Token *, int, int); + void (*set_content) (Token *, char *); + void (*initialise) (Token *, TokenClass, int, int, char *); + void (*destroy) (Token *); /* destructor */ +} Token; + + +/* + * Function Declarations + */ + + +/* + * Token constructor without values to initialise + * returns: + * Token* the created token + */ +Token *new_Token (void); + +/* + * Token constructor with values to initialise + * params: + * TokenClass class class of token to initialise + * int line line on which the token occurred + * int pos character position on which the token occurred + * char* content the textual content of the token + * returns: + * Token* the created token + */ +Token *new_Token_init (TokenClass class, int line, int pos, char *content); + + +#endif diff --git a/programs/develop/tinybasic-1.0.4/inc/tokeniser.h b/programs/develop/tinybasic-1.0.4/inc/tokeniser.h new file mode 100644 index 0000000000..95af426885 --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/inc/tokeniser.h @@ -0,0 +1,48 @@ +/* + * Tiny BASIC + * Tokenisation Header + * + * Copyright (C) Damian Walker 2019 + * Created: 04-Aug-2019 + */ + + +#ifndef __TOKENISER_H__ +#define __TOKENISER_H__ + + +/* pre-requisite headers */ +#include "token.h" + + +/* + * Structure Defnitions + */ + + +/* Token stream */ +typedef struct token_stream TokenStream; +typedef struct token_stream { + void *data; /* private data */ + Token *(*next) (TokenStream *); + int (*get_line) (TokenStream *); + void (*destroy) (TokenStream *); +} TokenStream; + + +/* + * Constructor Declarations + */ + + +/* + * Constructor for TokenStream + * params: + * FILE* input Input file + * returns: + * TokenStream* The new token stream + */ +TokenStream *new_TokenStream (FILE *input); + + +#endif diff --git a/programs/develop/tinybasic-1.0.4/src/common.c b/programs/develop/tinybasic-1.0.4/src/common.c new file mode 100644 index 0000000000..b861413e27 --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/src/common.c @@ -0,0 +1,43 @@ +/* + * Tiny BASIC Interpreter and Compiler Project + * Common service routines module + * + * Released as Public Domain by Damian Gareth Walker 2019 + * Created: 20-Sep-2019 + */ + + +/* included headers */ +#include +#include +#include +#include +#include "common.h" + + +/* + * Top Level Routines + */ + + +/* + * Portable case-insensitive comparison + * params: + * char* a string to compare + * char* b string to compare to + * returns: + * int -1 if ab + */ +int tinybasic_strcmp (char *a, char *b) { + do { + if (toupper (*a) != toupper (*b)) + return (toupper (*a) > toupper (*b)) + - (toupper (*a) < toupper (*b)); + else { + a++; + b++; + } + } while (*a && *b); + return 0; +} + diff --git a/programs/develop/tinybasic-1.0.4/src/errors.c b/programs/develop/tinybasic-1.0.4/src/errors.c new file mode 100644 index 0000000000..63423cc217 --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/src/errors.c @@ -0,0 +1,223 @@ +/* + * Tiny BASIC + * Error Handling Module + * + * Released as Public Domain by Damian Gareth Walker 2019 + * Created: 18-Aug-2019 + */ + + +/* included headers */ +#include +#include +#include +#include "errors.h" + + +/* + * Internal Data Structures + */ + + +/* Private data */ +typedef struct { + ErrorCode error; /* the last error encountered */ + int line; /* the source line on which the error occurred */ + int label; /* the label for the source line */ +} Private; + + +/* + * Internal Data + */ + + +/* convenience variables */ +ErrorHandler *this; /* object being worked on */ +Private *data; /* private data of object being worked on */ + +/* global variables */ +static char *messages[E_LAST] = { /* the error messages */ + "Successful", + "Invalid line number", + "Unrecognised command", + "Invalid variable", + "Invalid assignment", + "Invalid expression", + "Missing )", + "Invalid PRINT output", + "Bad command line", + "File not found", + "Invalid operator", + "THEN expected", + "Unexpected parameter", + "RETURN without GOSUB", + "Divide by zero", + "Overflow", + "Out of memory", + "Too many gosubs" +}; + + +/* + * Public Methods + */ + + +/* + * Record an error encountered + * globals: + * ErrorCode error the last error encountered + * int line the source line + * int label the line's label + * params: + * ErrorCode new_error the error code to set + * int new_line the source line to set + * int new_label the label to set + */ +static void set_code (ErrorHandler *errors, ErrorCode new_error, int new_line, + int new_label) { + + /* initialise */ + this = errors; + data = this->data; + + /* set the properties */ + data->error = new_error; + data->line = new_line; + data->label = new_label; +} + +/* + * Return the last error code encountered + * params: + * ErrorHandler* errors the error handler + * returns: + * ErrorCode the last error encountered + */ +static ErrorCode get_code (ErrorHandler *errors) { + this = errors; + data = this->data; + return data->error; +} + +/* + * Return the last error line encountered + * params: + * ErrorHandler* errors the error handler + * returns: + * int the source line of the last error + */ +static int get_line (ErrorHandler *errors) { + this = errors; + data = this->data; + return data->line; +} + +/* + * Return the last error label encountered + * params: + * ErrorHandler* errors the error handler + * returns: + * int the line label of the last error + */ +static int get_label (ErrorHandler *errors) { + this = errors; + data = this->data; + return data->label; +} + +/* + * Generate an error message + * params: + * ErrorHandler* errors the error handler + * globals: + * char* messages a list of error messages + * returns: + * char* the full error message + */ +static char *get_text (ErrorHandler *errors) { + + /* local variables */ + char + *message, /* the complete message */ + *line_text, /* source line N */ + *label_text; /* label N */ + + /* initialise the error object */ + this = errors; + data = this->data; + + /* get the source line, if there is one */ + line_text = malloc (20); + if (data->line) + sprintf (line_text, ", source line %d", data->line); + else + strcpy (line_text, ""); + + /* get the source label, if there is one */ + label_text = malloc (19); + if (data->label) + sprintf (label_text, ", line label %d", data->label); + else + strcpy (label_text, ""); + + /* put the error message together */ + message = malloc (strlen (messages[data->error]) + strlen (line_text) + + strlen (label_text) + 1); + strcpy (message, messages[data->error]); + strcat (message, line_text); + strcat (message, label_text); + free (line_text); + free (label_text); + + /* return the assembled error message */ + return message; +} + +/* + * ErrorHandler destructor + * params: + * ErrorHandler* errors the doomed error handler + */ +static void destroy (ErrorHandler *errors) { + if ((this = errors)) { + data = this->data; + free (data); + free (this); + } +} + + +/* + * Constructors + */ + + +/* + * Principal constructor + * returns: + * ErrorHandler* the new error handler object + */ +ErrorHandler *new_ErrorHandler (void) { + + /* allocate memory */ + this = malloc (sizeof (ErrorHandler)); + this->data = data = malloc (sizeof (Private)); + + /* initialise the methods */ + this->set_code = set_code; + this->get_code = get_code; + this->get_line = get_line; + this->get_label = get_label; + this->get_text = get_text; + this->destroy = destroy; + + /* initialise the properties */ + data->error = E_NONE; + data->line = 0; + data->label = 0; + + /* return the new object */ + return this; +} \ No newline at end of file diff --git a/programs/develop/tinybasic-1.0.4/src/expression.c b/programs/develop/tinybasic-1.0.4/src/expression.c new file mode 100644 index 0000000000..29665f924b --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/src/expression.c @@ -0,0 +1,203 @@ +/* + * Tiny BASIC + * Expression Handling Module + * + * Released as Public Domain by Damian Gareth Walker 2019 + * Created: 16-Aug-2019 + */ + + +/* included headers */ +#include +#include +#include +#include "parser.h" +#include "expression.h" +#include "errors.h" + + +/* + * Functions for Dealing with Factors + */ + + +/* + * Constructor for a factor + * returns: + * FactorNode* the new factor + */ +FactorNode *factor_create (void) { + + /* local variables */ + FactorNode *factor; /* the new factor */ + + /* allocate memory and initialise members */ + factor = malloc (sizeof (FactorNode)); + factor->class = FACTOR_NONE; + factor->sign = SIGN_POSITIVE; + + /* return the factor */ + return factor; +} + +/* + * Destructor for a factor + * params: + * FactorNode* factor the doomed factor + */ +void factor_destroy (FactorNode *factor) { + if (factor->class == FACTOR_EXPRESSION && factor->data.expression) { + expression_destroy (factor->data.expression); + } + free (factor); +} + + +/* + * Functions for Dealing with Terms + */ + + +/* + * Constructor for a right-hand factor of a term + * returns: + * RightHandFactor* the new RH factor of a term + */ +RightHandFactor *rhfactor_create (void) { + + /* local variables */ + RightHandFactor *rhfactor; /* the RH factor of a term to create */ + + /* allocate memory and initialise members */ + rhfactor = malloc (sizeof (RightHandFactor)); + rhfactor->op = TERM_OPERATOR_NONE; + rhfactor->factor = NULL; + rhfactor->next = NULL; + + /* return the new RH term */ + return rhfactor; +} + +/* + * Recursive destructor for a right-hand factor of a term + * params: + * RightHandFactor* rhfactor the doomed RH factor of a term + */ +void rhfactor_destroy (RightHandFactor *rhfactor) { + if (rhfactor->next) + rhfactor_destroy (rhfactor->next); + if (rhfactor->factor) + factor_destroy (rhfactor->factor); + free (rhfactor); +} + +/* + * Constructor for a term + * returns: + * TermNode* the new term + */ +TermNode *term_create (void) { + + /* local variables */ + TermNode *term; /* the new term */ + + /* allocate memory and initialise members */ + term = malloc (sizeof (TermNode)); + term->factor = NULL; + term->next = NULL; + + /* return the new term */ + return term; +} + +/* + * Destructor for a term + * params: + * TermNode* term the doomed term + */ +void term_destroy (TermNode *term) { + + /* destroy the consituent parts of the term */ + if (term->factor) + factor_destroy (term->factor); + if (term->next) + rhfactor_destroy (term->next); + + /* destroy the term itself */ + free (term); +} + + +/* + * Functions for dealing with Expressions + */ + + +/* + * Constructor for a right-hand expression + * returns: + * RightHandTerm* the RH term of an expression + */ +RightHandTerm *rhterm_create (void) { + + /* local variables */ + RightHandTerm *rhterm; /* the new RH expression */ + + /* allocate memory and initialise members */ + rhterm = malloc (sizeof (RightHandTerm)); + rhterm->op = EXPRESSION_OPERATOR_NONE; + rhterm->term = NULL; + rhterm->next = NULL; + + /* return the new right-hand expression */ + return rhterm; +} + +/* + * Recursive destructor for a right-hand term of an expression + * params: + * RightHandTerm* rhterm the doomed RH expression + */ +void rhterm_destroy (RightHandTerm *rhterm) { + if (rhterm->next) + rhterm_destroy (rhterm->next); + if (rhterm->term) + term_destroy (rhterm->term); + free (rhterm); +} + +/* + * Constructor for an expression + * returns: + * ExpressionNode* the new expression + */ +ExpressionNode *expression_create (void) { + + /* local variables */ + ExpressionNode *expression; /* the new expression */ + + /* allocate memory and initialise members */ + expression = malloc (sizeof (ExpressionNode)); + expression->term = NULL; + expression->next = NULL; + + /* return the new expression */ + return expression; +} + +/* + * Destructor for a expression + * params: + * ExpressionNode* expression the doomed expression + */ +void expression_destroy (ExpressionNode *expression) { + + /* destroy the consituent parts of the expression */ + if (expression->term) + term_destroy (expression->term); + if (expression->next) + rhterm_destroy (expression->next); + + /* destroy the expression itself */ + free (expression); +} diff --git a/programs/develop/tinybasic-1.0.4/src/formatter.c b/programs/develop/tinybasic-1.0.4/src/formatter.c new file mode 100644 index 0000000000..79c395a155 --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/src/formatter.c @@ -0,0 +1,619 @@ +/* + * Tiny BASIC + * Listing Output Module + * + * Released as Public Domain by Damian Gareth Walker, 2019 + * Created: 18-Sep-2019 + */ + + +/* included headers */ +#include +#include +#include +#include +#include "formatter.h" +#include "expression.h" +#include "errors.h" +#include "parser.h" + + +/* + * Data Definitions + */ + + +/* private formatter data */ +typedef struct formatter_data { + ErrorHandler *errors; /* the error handler */ +} FormatterData; + +/* convenience variables */ +static Formatter *this; /* the object being worked on */ + +/* + * Forward References + */ + + +/* factor_output() has a forward reference to output_expression() */ +static char *output_expression (ExpressionNode *expression); + +/* output_statement() has a forward reference from output_if() */ +static char *output_statement (StatementNode *statement); + + +/* + * Functions + */ + + +/* + * Output a factor + * params: + * FactorNode* factor the factor to output + * return: + * char* the text representation of the factor + */ +static char *output_factor (FactorNode *factor) { + + /* local variables */ + char *factor_text = NULL, /* the text of the whole factor */ + *factor_buffer = NULL, /* temporary buffer for prepending to factor_text */ + *expression_text = NULL; /* the text of a subexpression */ + + /* work out the main factor text */ + switch (factor->class) { + case FACTOR_VARIABLE: + factor_text = malloc (2); + sprintf (factor_text, "%c", factor->data.variable + 'A' - 1); + break; + case FACTOR_VALUE: + factor_text = malloc (7); + sprintf (factor_text, "%d", factor->data.value); + break; + case FACTOR_EXPRESSION: + if ((expression_text = output_expression (factor->data.expression))) { + factor_text = malloc (strlen (expression_text) + 3); + sprintf (factor_text, "(%s)", expression_text); + free (expression_text); + } + break; + default: + this->priv->errors->set_code + (this->priv->errors, E_INVALID_EXPRESSION, 0, 0); + } + + /* apply a negative sign, if necessary */ + if (factor_text && factor->sign == SIGN_NEGATIVE) { + factor_buffer = malloc (strlen (factor_text) + 2); + sprintf (factor_buffer, "-%s", factor_text); + free (factor_text); + factor_text = factor_buffer; + } + + /* return the final factor representation */ + return factor_text; +} + +/* + * Output a term + * params: + * TermNode* term the term to output + * returns: + * char* the text representation of the term + */ +static char *output_term (TermNode *term) { + + /* local variables */ + char + *term_text = NULL, /* the text of the whole term */ + *factor_text = NULL, /* the text of each factor */ + operator_char; /* the operator that joins the righthand factor */ + RightHandFactor *rhfactor; /* right hand factors of the expression */ + + /* begin with the initial factor */ + if ((term_text = output_factor (term->factor))) { + rhfactor = term->next; + while (! this->priv->errors->get_code (this->priv->errors) && rhfactor) { + + /* ascertain the operator text */ + switch (rhfactor->op) { + case TERM_OPERATOR_MULTIPLY: + operator_char = '*'; + break; + case TERM_OPERATOR_DIVIDE: + operator_char = '/'; + break; + default: + this->priv->errors->set_code + (this->priv->errors, E_INVALID_EXPRESSION, 0, 0); + free (term_text); + term_text = NULL; + } + + /* get the factor that follows the operator */ + if (! this->priv->errors->get_code (this->priv->errors) + && (factor_text = output_factor (rhfactor->factor))) { + term_text = realloc (term_text, + strlen (term_text) + strlen (factor_text) + 2); + sprintf (term_text, "%s%c%s", term_text, operator_char, factor_text); + free (factor_text); + } + + /* look for another term on the right of the expression */ + rhfactor = rhfactor->next; + } + } + + /* return the expression text */ + return term_text; + +} + +/* + * Output an expression for a program listing + * params: + * ExpressionNode* expression the expression to output + * returns: + * char* new string containint the expression text + */ +static char *output_expression (ExpressionNode *expression) { + + /* local variables */ + char + *expression_text = NULL, /* the text of the whole expression */ + *term_text = NULL, /* the text of each term */ + operator_char; /* the operator that joins the righthand term */ + RightHandTerm *rhterm; /* right hand terms of the expression */ + + /* begin with the initial term */ + if ((expression_text = output_term (expression->term))) { + rhterm = expression->next; + while (! this->priv->errors->get_code (this->priv->errors) && rhterm) { + + /* ascertain the operator text */ + switch (rhterm->op) { + case EXPRESSION_OPERATOR_PLUS: + operator_char = '+'; + break; + case EXPRESSION_OPERATOR_MINUS: + operator_char = '-'; + break; + default: + this->priv->errors->set_code + (this->priv->errors, E_INVALID_EXPRESSION, 0, 0); + free (expression_text); + expression_text = NULL; + } + + /* get the terms that follow the operators */ + if (! this->priv->errors->get_code (this->priv->errors) + && (term_text = output_term (rhterm->term))) { + expression_text = realloc (expression_text, + strlen (expression_text) + strlen (term_text) + 2); + sprintf (expression_text, "%s%c%s", expression_text, operator_char, + term_text); + free (term_text); + } + + /* look for another term on the right of the expression */ + rhterm = rhterm->next; + } + } + + /* return the expression text */ + return expression_text; + +} + +/* + * LET statement output + * params: + * LetStatementNode* letn data for the LET statement + * returns: + * char* the LET statement text + */ +static char *output_let (LetStatementNode *letn) { + + /* local variables */ + char + *let_text = NULL, /* the LET text to be assembled */ + *expression_text = NULL; /* the text of the expression */ + + /* assemble the expression */ + expression_text = output_expression (letn->expression); + + /* assemble the final LET text, if we have an expression */ + if (expression_text) { + let_text = malloc (7 + strlen (expression_text)); + sprintf (let_text, "LET %c=%s", 'A' - 1 + letn->variable, expression_text); + free (expression_text); + } + + /* return it */ + return let_text; +} + + +/* + * IF statement output + * params: + * IfStatementNode* ifn data for the IF statement + * returns: + * char* the IF statement text + */ +static char *output_if (IfStatementNode *ifn) { + + /* local variables */ + char + *if_text = NULL, /* the LET text to be assembled */ + *left_text = NULL, /* the text of the left expression */ + *op_text = NULL, /* the operator text */ + *right_text = NULL, /* the text of the right expression */ + *statement_text = NULL; /* the text of the conditional statement */ + + /* assemble the expressions and conditional statement */ + left_text = output_expression (ifn->left); + right_text = output_expression (ifn->right); + statement_text = output_statement (ifn->statement); + + /* work out the operator text */ + op_text = malloc (3); + switch (ifn->op) { + case RELOP_EQUAL: strcpy (op_text, "="); break; + case RELOP_UNEQUAL: strcpy (op_text, "<>"); break; + case RELOP_LESSTHAN: strcpy (op_text, "<"); break; + case RELOP_LESSOREQUAL: strcpy (op_text, "<="); break; + case RELOP_GREATERTHAN: strcpy (op_text, ">"); break; + case RELOP_GREATEROREQUAL: strcpy (op_text, ">="); break; + } + + /* assemble the final IF text, if we have everything we need */ + if (left_text && op_text && right_text && statement_text) { + if_text = malloc (3 + strlen (left_text) + strlen (op_text) + + strlen (right_text) + 6 + strlen (statement_text) + 1); + sprintf (if_text, "IF %s%s%s THEN %s", left_text, op_text, right_text, + statement_text); + } + + /* free up the temporary bits of memory we've reserved */ + if (left_text) free (left_text); + if (op_text) free (op_text); + if (right_text) free (right_text); + if (statement_text) free (statement_text); + + /* return it */ + return if_text; +} + + +/* + * GOTO statement output + * params: + * GotoStatementNode* goton data for the GOTO statement + * returns: + * char* the GOTO statement text + */ +static char *output_goto (GotoStatementNode *goton) { + + /* local variables */ + char + *goto_text = NULL, /* the GOTO text to be assembled */ + *expression_text = NULL; /* the text of the expression */ + + /* assemble the expression */ + expression_text = output_expression (goton->label); + + /* assemble the final LET text, if we have an expression */ + if (expression_text) { + goto_text = malloc (6 + strlen (expression_text)); + sprintf (goto_text, "GOTO %s", expression_text); + free (expression_text); + } + + /* return it */ + return goto_text; +} + + +/* + * GOSUB statement output + * params: + * GosubStatementNode* gosubn data for the GOSUB statement + * returns: + * char* the GOSUB statement text + */ +static char *output_gosub (GosubStatementNode *gosubn) { + + /* local variables */ + char + *gosub_text = NULL, /* the GOSUB text to be assembled */ + *expression_text = NULL; /* the text of the expression */ + + /* assemble the expression */ + expression_text = output_expression (gosubn->label); + + /* assemble the final LET text, if we have an expression */ + if (expression_text) { + gosub_text = malloc (7 + strlen (expression_text)); + sprintf (gosub_text, "GOSUB %s", expression_text); + free (expression_text); + } + + /* return it */ + return gosub_text; +} + + +/* + * END statement output + * returns: + * char* A new string with the text "END" + */ +static char *output_end (void) { + char *end_text; /* the full text of the END command */ + end_text = malloc (4); + strcpy (end_text, "END"); + return end_text; +} + + +/* + * RETURN statement output + * returns: + * char* A new string with the text "RETURN" + */ +static char *output_return (void) { + char *return_text; /* the full text of the RETURN command */ + return_text = malloc (7); + strcpy (return_text, "RETURN"); + return return_text; +} + + +/* + * PRINT statement output + * params: + * PrintStatementNode* printn data for the PRINT statement + * returns: + * char* the PRINT statement text + */ +static char *output_print (PrintStatementNode *printn) { + + /* local variables */ + char + *print_text, /* the PRINT text to be assembled */ + *output_text = NULL; /* the text of the current output item */ + OutputNode *output; /* the current output item */ + + /* initialise the PRINT statement */ + print_text = malloc (6); + strcpy (print_text, "PRINT"); + + /* add the output items */ + if ((output = printn->first)) { + do { + + /* add the separator */ + print_text = realloc (print_text, strlen (print_text) + 2); + strcat (print_text, output == printn->first ? " " : ","); + + /* format the output item */ + switch (output->class) { + case OUTPUT_STRING: + output_text = malloc (strlen (output->output.string) + 3); + sprintf (output_text, "%c%s%c", '"', output->output.string, '"'); + break; + case OUTPUT_EXPRESSION: + output_text = output_expression (output->output.expression); + break; + } + + /* add the output item */ + print_text = realloc (print_text, + strlen (print_text) + strlen (output_text) + 1); + strcat (print_text, output_text); + free (output_text); + + /* look for the next output item */ + } while ((output = output->next)); + } + + /* return the assembled text */ + return print_text; +} + +/* + * INPUT statement output + * params: + * InputStatementNode* inputn the input statement node to show + * returns: + * char * the text of the INPUT statement + */ +static char *output_input (InputStatementNode *inputn) { + + /* local variables */ + char + *input_text, /* the INPUT text to be assembled */ + var_text[3]; /* text representation of each variable with separator */ + VariableListNode *variable; /* the current output item */ + + /* initialise the INPUT statement */ + input_text = malloc (6); + strcpy (input_text, "INPUT"); + + /* add the output items */ + if ((variable = inputn->first)) { + do { + sprintf (var_text, "%c%c", + (variable == inputn->first) ? ' ' : ',', + variable->variable + 'A' - 1); + input_text = realloc (input_text, strlen (input_text) + 3); + strcat (input_text, var_text); + } while ((variable = variable->next)); + } + + /* return the assembled text */ + return input_text; +} + +/* + * Statement output + * params: + * StatementNode* statement the statement to output + * returns: + * char* a string containing the statement line + */ +static char *output_statement (StatementNode *statement) { + + /* local variables */ + char *output = NULL; /* the text output */ + + /* return null output for comments */ + if (! statement) + return NULL; + + /* build the statement itself */ + switch (statement->class) { + case STATEMENT_LET: + output = output_let (statement->statement.letn); + break; + case STATEMENT_IF: + output = output_if (statement->statement.ifn); + break; + case STATEMENT_GOTO: + output = output_goto (statement->statement.goton); + break; + case STATEMENT_GOSUB: + output = output_gosub (statement->statement.gosubn); + break; + case STATEMENT_RETURN: + output = output_return (); + break; + case STATEMENT_END: + output = output_end (); + break; + case STATEMENT_PRINT: + output = output_print (statement->statement.printn); + break; + case STATEMENT_INPUT: + output = output_input (statement->statement.inputn); + break; + default: + output = malloc (24); + strcpy (output, "Unrecognised statement."); + } + + /* return the listing line */ + return output; +} + +/* + * Program Line Output + * params: + * ProgramLineNode* program_line the line to output + */ +static void generate_line (ProgramLineNode *program_line) { + + /* local variables */ + char + label_text [7], /* line label text */ + *output = NULL, /* the rest of the output */ + *line_text = NULL; /* the assembled line */ + + /* initialise the line label */ + if (program_line->label) + sprintf (label_text, "%5d ", program_line->label); + else + strcpy (label_text, " "); + + /* build the statement itself */ + output = output_statement (program_line->statement); + + /* if this wasn't a comment, add it to the program */ + if (output) { + line_text = malloc (strlen (label_text) + strlen (output) + 2); + sprintf (line_text, "%s%s\n", label_text, output); + free (output); + this->output = realloc (this->output, + strlen (this->output) + strlen (line_text) + 1); + strcat (this->output, line_text); + free (line_text); + } +} + + +/* + * Public Methods + */ + + +/* + * Create a formatted version of the program + * params: + * Formatter* fomatter the formatter + * ProgramNode* program the syntax tree + */ +static void generate (Formatter *formatter, ProgramNode *program) { + + /* local variables */ + ProgramLineNode *program_line; /* line to process */ + + /* initialise this object */ + this = formatter; + + /* generate the code for the lines */ + program_line = program->first; + while (program_line) { + generate_line (program_line); + program_line = program_line->next; + } +} + +/* + * Destroy the formatter when no longer needed + * params: + * Formatter* formatter the doomed formatter + */ +static void destroy (Formatter *formatter) { + if (formatter) { + if (formatter->output) + free (formatter->output); + if (formatter->priv) + free (formatter->priv); + free (formatter); + } +} + + +/* + * Constructors + */ + + +/* + * The Formatter constructor + * params: + * ErrorHandler *errors the error handler object + * returns: + * Formatter* the new formatter + */ +Formatter *new_Formatter (ErrorHandler *errors) { + + /* allocate memory */ + this = malloc (sizeof (Formatter)); + this->priv = malloc (sizeof (FormatterData)); + + /* initialise methods */ + this->generate = generate; + this->destroy = destroy; + + /* initialise properties */ + this->output = malloc (sizeof (char)); + *this->output = '\0'; + this->priv->errors = errors; + + /* return the new object */ + return this; +} diff --git a/programs/develop/tinybasic-1.0.4/src/generatec.c b/programs/develop/tinybasic-1.0.4/src/generatec.c new file mode 100644 index 0000000000..91603e4cae --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/src/generatec.c @@ -0,0 +1,882 @@ +/* + * Tiny BASIC Interpreter and Compiler Project + * C Output Module + * + * Copyright (C) Damian Gareth Walker 2019 + * Created: 03-Oct-2019 + */ + + +/* included headers */ +#include +#include +#include +#include "statement.h" +#include "expression.h" +#include "errors.h" +#include "parser.h" +#include "options.h" +#include "generatec.h" + + +/* + * Internal Data + */ + + +/* label list */ +typedef struct label { + int number; /* the label number */ + struct label *next; /* the next label */ +} CLabel; + +/* private data */ +typedef struct { + unsigned int input_used:1; /* true if we need the input routine */ + unsigned long int vars_used:26; /* true for each variable used */ + CLabel *first_label; /* the start of a list of labels */ + char *code; /* the main block of generated code */ + ErrorHandler *errors; /* error handler for compilation */ + LanguageOptions *options; /* the language options for compilation */ +} Private; + +/* convenience variables */ +static CProgram *this; /* the object being worked on */ +static Private *data; /* the private data of the object */ +static ErrorHandler *errors; /* the error handler */ +static LanguageOptions *options; /* the language options */ + + +/* + * Forward References + */ + + +/* factor_output() has a forward reference to output_expression() */ +static char *output_expression (ExpressionNode *expression); + +/* output_statement() has a forward reference from output_if() */ +static char *output_statement (StatementNode *statement); + + +/* + * Level 6 Functions + */ + + +/* + * Output a factor + * params: + * FactorNode* factor the factor to output + * return: + * char* the text representation of the factor + */ +static char *output_factor (FactorNode *factor) { + + /* local variables */ + char *factor_text = NULL, /* the text of the whole factor */ + *factor_buffer = NULL, /* temporary buffer for prepending to factor_text */ + *expression_text = NULL; /* the text of a subexpression */ + + /* work out the main factor text */ + switch (factor->class) { + case FACTOR_VARIABLE: + factor_text = malloc (2); + sprintf (factor_text, "%c", factor->data.variable + 'a' - 1); + data->vars_used |= 1 << (factor->data.variable - 1); + break; + case FACTOR_VALUE: + factor_text = malloc (7); + sprintf (factor_text, "%d", factor->data.value); + break; + case FACTOR_EXPRESSION: + if ((expression_text = output_expression (factor->data.expression))) { + factor_text = malloc (strlen (expression_text) + 3); + sprintf (factor_text, "(%s)", expression_text); + free (expression_text); + } + break; + default: + errors->set_code (errors, E_INVALID_EXPRESSION, 0, 0); + } + + /* apply a negative sign, if necessary */ + if (factor_text && factor->sign == SIGN_NEGATIVE) { + factor_buffer = malloc (strlen (factor_text) + 2); + sprintf (factor_buffer, "-%s", factor_text); + free (factor_text); + factor_text = factor_buffer; + } + + /* return the final factor representation */ + return factor_text; +} + + +/* + * Level 5 Functions + */ + + +/* + * Output a term + * params: + * TermNode* term the term to output + * returns: + * char* the text representation of the term + */ +static char *output_term (TermNode *term) { + + /* local variables */ + char + *term_text = NULL, /* the text of the whole term */ + *factor_text = NULL, /* the text of each factor */ + operator_char; /* the operator that joins the righthand factor */ + RightHandFactor *rhfactor; /* right hand factors of the expression */ + + /* begin with the initial factor */ + if ((term_text = output_factor (term->factor))) { + rhfactor = term->next; + while (! errors->get_code (errors) && rhfactor) { + + /* ascertain the operator text */ + switch (rhfactor->op) { + case TERM_OPERATOR_MULTIPLY: + operator_char = '*'; + break; + case TERM_OPERATOR_DIVIDE: + operator_char = '/'; + break; + default: + errors->set_code (errors, E_INVALID_EXPRESSION, 0, 0); + free (term_text); + term_text = NULL; + } + + /* get the factor that follows the operator */ + if (! errors->get_code (errors) + && (factor_text = output_factor (rhfactor->factor))) { + term_text = realloc (term_text, + strlen (term_text) + strlen (factor_text) + 2); + sprintf (term_text, "%s%c%s", term_text, operator_char, factor_text); + free (factor_text); + } + + /* look for another term on the right of the expression */ + rhfactor = rhfactor->next; + } + } + + /* return the expression text */ + return term_text; +} + + +/* + * Level 4 Functions + */ + + +/* + * Output an expression for a program listing + * params: + * ExpressionNode* expression the expression to output + * returns: + * char* new string containint the expression text + */ +static char *output_expression (ExpressionNode *expression) { + + /* local variables */ + char + *expression_text = NULL, /* the text of the whole expression */ + *term_text = NULL, /* the text of each term */ + operator_char; /* the operator that joins the righthand term */ + RightHandTerm *rhterm; /* right hand terms of the expression */ + + /* begin with the initial term */ + if ((expression_text = output_term (expression->term))) { + rhterm = expression->next; + while (! errors->get_code (errors) && rhterm) { + + /* ascertain the operator text */ + switch (rhterm->op) { + case EXPRESSION_OPERATOR_PLUS: + operator_char = '+'; + break; + case EXPRESSION_OPERATOR_MINUS: + operator_char = '-'; + break; + default: + errors->set_code (errors, E_INVALID_EXPRESSION, 0, 0); + free (expression_text); + expression_text = NULL; + } + + /* get the terms that follow the operators */ + if (! errors->get_code (errors) + && (term_text = output_term (rhterm->term))) { + expression_text = realloc (expression_text, + strlen (expression_text) + strlen (term_text) + 2); + sprintf (expression_text, "%s%c%s", expression_text, operator_char, + term_text); + free (term_text); + } + + /* look for another term on the right of the expression */ + rhterm = rhterm->next; + } + } + + /* return the expression text */ + return expression_text; + +} + + +/* + * Level 3 Functions + */ + + +/* + * LET statement output + * params: + * LetStatementNode* letn data for the LET statement + * returns: + * char* the LET statement text + */ +static char *output_let (LetStatementNode *letn) { + + /* local variables */ + char + *let_text = NULL, /* the LET text to be assembled */ + *expression_text = NULL; /* the text of the expression */ + + /* assemble the expression */ + expression_text = output_expression (letn->expression); + + /* assemble the final LET text, if we have an expression */ + if (expression_text) { + let_text = malloc (4 + strlen (expression_text)); + sprintf (let_text, "%c=%s;", 'a' - 1 + letn->variable, expression_text); + free (expression_text); + data->vars_used |= 1 << (letn->variable - 1); + } + + /* return it */ + return let_text; +} + +/* + * IF statement output + * params: + * IfStatementNode* ifn data for the IF statement + * returns: + * char* the IF statement text + */ +static char *output_if (IfStatementNode *ifn) { + + /* local variables */ + char + *if_text = NULL, /* the LET text to be assembled */ + *left_text = NULL, /* the text of the left expression */ + *op_text = NULL, /* the operator text */ + *right_text = NULL, /* the text of the right expression */ + *statement_text = NULL; /* the text of the conditional statement */ + + /* assemble the expressions and conditional statement */ + left_text = output_expression (ifn->left); + right_text = output_expression (ifn->right); + statement_text = output_statement (ifn->statement); + + /* work out the operator text */ + op_text = malloc (3); + switch (ifn->op) { + case RELOP_EQUAL: strcpy (op_text, "=="); break; + case RELOP_UNEQUAL: strcpy (op_text, "!="); break; + case RELOP_LESSTHAN: strcpy (op_text, "<"); break; + case RELOP_LESSOREQUAL: strcpy (op_text, "<="); break; + case RELOP_GREATERTHAN: strcpy (op_text, ">"); break; + case RELOP_GREATEROREQUAL: strcpy (op_text, ">="); break; + } + + /* assemble the final IF text, if we have everything we need */ + if (left_text && op_text && right_text && statement_text) { + if_text = malloc (4 + strlen (left_text) + strlen (op_text) + + strlen (right_text) + 3 + strlen (statement_text) + 2); + sprintf (if_text, "if (%s%s%s) {%s}", left_text, op_text, right_text, + statement_text); + } + + /* free up the temporary bits of memory we've reserved */ + if (left_text) free (left_text); + if (op_text) free (op_text); + if (right_text) free (right_text); + if (statement_text) free (statement_text); + + /* return it */ + return if_text; +} + +/* + * GOTO statement output + * params: + * GotoStatementNode* goton data for the GOTO statement + * returns: + * char* the GOTO statement text + */ +static char *output_goto (GotoStatementNode *goton) { + + /* local variables */ + char + *goto_text = NULL, /* the GOTO text to be assembled */ + *expression_text = NULL; /* the text of the expression */ + + /* assemble the expression */ + expression_text = output_expression (goton->label); + + /* assemble the final LET text, if we have an expression */ + if (expression_text) { + goto_text = malloc (27 + strlen (expression_text)); + sprintf (goto_text, "label=%s; goto goto_block;", expression_text); + free (expression_text); + } + + /* return it */ + return goto_text; +} + +/* + * GOSUB statement output + * params: + * GosubStatementNode* gosubn data for the GOSUB statement + * returns: + * char* the GOSUB statement text + */ +static char *output_gosub (GosubStatementNode *gosubn) { + + /* local variables */ + char + *gosub_text = NULL, /* the GOSUB text to be assembled */ + *expression_text = NULL; /* the text of the expression */ + + /* assemble the expression */ + expression_text = output_expression (gosubn->label); + + /* assemble the final LET text, if we have an expression */ + if (expression_text) { + gosub_text = malloc (12 + strlen (expression_text)); + sprintf (gosub_text, "bas_exec(%s);", expression_text); + free (expression_text); + } + + /* return it */ + return gosub_text; +} + +/* + * END statement output + * returns: + * char* A new string with the text "END" + */ +static char *output_end (void) { + char *end_text; /* the full text of the END command */ + end_text = malloc (9); + strcpy (end_text, "exit(0);"); + return end_text; +} + +/* + * RETURN statement output + * returns: + * char* A new string with the text "RETURN" + */ +static char *output_return (void) { + char *return_text; /* the full text of the RETURN command */ + return_text = malloc (8); + strcpy (return_text, "return;"); + return return_text; +} + +/* + * PRINT statement output + * params: + * PrintStatementNode* printn data for the PRINT statement + * returns: + * char* the PRINT statement text + */ +static char *output_print (PrintStatementNode *printn) { + + /* local variables */ + char + *format_text = NULL, /* the printf format string */ + *output_list = NULL, /* the printf output list */ + *output_text = NULL, /* a single output item */ + *print_text = NULL; /* the PRINT text to be assembled */ + OutputNode *output; /* the current output item */ + + /* initialise format and output text */ + format_text = malloc (1); + *format_text = '\0'; + output_list = malloc (1); + *output_list = '\0'; + + /* build the format and output text */ + if ((output = printn->first)) { + do { + + /* format the output item */ + switch (output->class) { + case OUTPUT_STRING: + format_text = realloc (format_text, + strlen (format_text) + strlen (output->output.string) + 1); + strcat (format_text, output->output.string); + break; + case OUTPUT_EXPRESSION: + format_text = realloc (format_text, strlen (format_text) + 3); + strcat (format_text, "%d"); + output_text = output_expression (output->output.expression); + output_list = realloc (output_list, + strlen (output_list) + 1 + strlen (output_text) + 1); + strcat (output_list, ","); + strcat (output_list, output_text); + free (output_text); + break; + } + + /* look for the next output item */ + } while ((output = output->next)); + } + + /* assemble the whole print text and return it */ + print_text = malloc (8 + strlen (format_text) + 3 + strlen (output_list) + 3); + sprintf (print_text, "printf(\"%s\\n\"%s);", format_text, output_list); + free (format_text); + free (output_list); + return print_text; +} + +/* + * INPUT statement output + * params: + * InputStatementNode* inputn the input statement node to show + * returns: + * char * the text of the INPUT statement + */ +static char *output_input (InputStatementNode *inputn) { + + /* local variables */ + char + *var_text, /* input text for a single variable */ + *input_text; /* the INPUT text to be assembled */ + VariableListNode *variable; /* the current output item */ + + /* generate an input line for each variable listed */ + input_text = malloc (1); + *input_text = '\0'; + if ((variable = inputn->first)) { + do { + var_text = malloc (18); + sprintf (var_text, "%s%c = bas_input();", + (variable == inputn->first) ? "" : "\n", + variable->variable + 'a' - 1); + input_text = realloc (input_text, + strlen (input_text) + strlen (var_text) + 1); + strcat (input_text, var_text); + free (var_text); + data->vars_used |= 1 << (variable->variable - 1); + } while ((variable = variable->next)); + } + data->input_used = 1; + + /* return the assembled text */ + return input_text; +} + + +/* + * Level 2 Functions + */ + + +/* + * Statement output + * params: + * StatementNode* statement the statement to output + * returns: + * char* a string containing the statement line + */ +static char *output_statement (StatementNode *statement) { + + /* local variables */ + char *output = NULL; /* the text output */ + + /* return null output for comments */ + if (! statement) + return NULL; + + /* build the statement itself */ + switch (statement->class) { + case STATEMENT_LET: + output = output_let (statement->statement.letn); + break; + case STATEMENT_IF: + output = output_if (statement->statement.ifn); + break; + case STATEMENT_GOTO: + output = output_goto (statement->statement.goton); + break; + case STATEMENT_GOSUB: + output = output_gosub (statement->statement.gosubn); + break; + case STATEMENT_RETURN: + output = output_return (); + break; + case STATEMENT_END: + output = output_end (); + break; + case STATEMENT_PRINT: + output = output_print (statement->statement.printn); + break; + case STATEMENT_INPUT: + output = output_input (statement->statement.inputn); + break; + default: + output = malloc (24); + strcpy (output, "Unrecognised statement."); + } + + /* return the listing line */ + return output; +} + + +/* + * Level 1 Functions + */ + + +/* + * Program Line Generation + * params: + * ProgramLineNode* program_line the program line to convert + */ +static void generate_line (ProgramLineNode *program_line) { + + /* local variables */ + CLabel + *prior_label, /* label before potential insertion point */ + *next_label, /* label after potential insertion point */ + *new_label; /* a label to insert */ + char + label_text[12], /* text of a line label */ + *statement_text; /* the text of a statement */ + + /* generate a line label */ + if (program_line->label) { + + /* insert the label into the label list */ + new_label = malloc (sizeof (CLabel)); + new_label->number = program_line->label; + new_label->next = NULL; + prior_label = NULL; + next_label = data->first_label; + while (next_label && next_label->number < new_label->number) { + prior_label = next_label; + next_label = prior_label->next; + } + new_label->next = next_label; + if (prior_label) + prior_label->next = new_label; + else + data->first_label = new_label; + + /* append the label to the code block */ + sprintf (label_text, "lbl_%d:\n", program_line->label); + data->code = realloc (data->code, + strlen (data->code) + strlen (label_text) + 1); + strcat (data->code, label_text); + } + + /* generate the statement, and append it if it is not a comment */ + statement_text = output_statement (program_line->statement); + if (statement_text) { + data->code = realloc (data->code, + strlen (data->code) + strlen (statement_text) + 2); + strcat (data->code, statement_text); + strcat (data->code, "\n"); + free (statement_text); + } +} + +/* + * Generate the #include lines and #defines + * changes: + * Private* data appends headers to the output + */ +static void generate_includes (void) { + + /* local variables */ + char + include_text[1024], /* the whole include and #define text */ + define_text[80]; /* a single #define line */ + + /* build up includes and defines */ + strcpy (include_text, "#include \n"); + strcat (include_text, "#include \n"); + sprintf (define_text, "#define E_RETURN_WITHOUT_GOSUB %d\n", + E_RETURN_WITHOUT_GOSUB); + strcat (include_text, define_text); + + /* add the #includes and #defines to the output */ + this->c_output = realloc (this->c_output, strlen (this->c_output) + + strlen (include_text) + 1); + strcat (this->c_output, include_text); +} + +/* + * Generate the variable declarations + * changes: + * Private* data appends declaration to the output + */ +static void generate_variables (void) { + + /* local variables */ + int vcount; /* variable counter */ + char + var_text [12], /* individual variable text */ + declaration[60]; /* declaration text */ + + /* build the declaration */ + *declaration = '\0'; + for (vcount = 0; vcount < 26; ++vcount) { + if (data->vars_used & 1 << vcount) { + if (*declaration) + sprintf (var_text, ",%c", 'a' + vcount); + else + sprintf (var_text, "short int %c", 'a' + vcount); + strcat (declaration, var_text); + } + } + + /* if there are any variables, add the declaration to the output */ + if (*declaration) { + strcat (declaration, ";\n"); + this->c_output = realloc (this->c_output, strlen (this->c_output) + + strlen (declaration) + 1); + strcat (this->c_output, declaration); + } +} + +/* + * Generate the bas_input function + * changes: + * Private* data appends declaration to the output + */ +static void generate_bas_input (void) { + + /* local variables */ + char function_text[1024]; /* the entire function */ + + /* construct the function text */ + strcpy (function_text, "short int bas_input (void) {\n"); + strcat (function_text, "short int ch, sign, value;\n"); + strcat (function_text, "do {\n"); + strcat (function_text, "if (ch == '-') sign = -1; else sign = 1;\n"); + strcat (function_text, "ch = getchar ();\n"); + strcat (function_text, "} while (ch < '0' || ch > '9');\n"); + strcat (function_text, "value = 0;\n"); + strcat (function_text, "do {\n"); + strcat (function_text, "value = 10 * value + (ch - '0');\n"); + strcat (function_text, "ch = getchar ();\n"); + strcat (function_text, "} while (ch >= '0' && ch <= '9');\n"); + strcat (function_text, "return sign * value;\n"); + strcat (function_text, "}\n"); + + /* add the function text to the output */ + this->c_output = realloc (this->c_output, strlen (this->c_output) + + strlen (function_text) + 1); + strcat (this->c_output, function_text); +} + +/* + * Generate the bas_exec function + * changes: + * Private* data appends declaration to the output + */ +static void generate_bas_exec (void) { + + /* local variables */ + char + *op, /* comparison operator to use for line numbers */ + goto_line[80], /* a line in the goto block */ + *goto_block, /* the goto block */ + *function_text; /* the complete function text */ + CLabel *label; /* label pointer for construction goto block */ + + /* decide which operator to use for comparison */ + op = (options->get_line_numbers (options) == LINE_NUMBERS_OPTIONAL) + ? "==" + : "<="; + + /* create the goto block */ + goto_block = malloc (128); + strcpy (goto_block, "goto_block:\n"); + strcat (goto_block, "if (!label) goto lbl_start;\n"); + label = data->first_label; + while (label) { + sprintf (goto_line, "if (label%s%d) goto lbl_%d;\n", + op, label->number, label->number); + goto_block = realloc (goto_block, + strlen (goto_block) + strlen (goto_line) + 1); + strcat (goto_block, goto_line); + label = label->next; + } + goto_block = realloc (goto_block, strlen (goto_block) + 12); + strcat (goto_block, "lbl_start:\n"); + + /* put the function together */ + function_text = malloc (28 + strlen (goto_block) + strlen (data->code) + 3); + strcpy (function_text, "void bas_exec (int label) {\n"); + strcat (function_text, goto_block); + strcat (function_text, data->code); + strcat (function_text, "}\n"); + + /* add the function text to the output */ + this->c_output = realloc (this->c_output, strlen (this->c_output) + + strlen (function_text) + 1); + strcat (this->c_output, function_text); + free (goto_block); + free (function_text); +} + +/* + * Generate the main function + * changes: + * Private* data appends declaration to the output + */ +void generate_main (void) { + + /* local variables */ + char function_text[1024]; /* the entire function */ + + /* construct the function text */ + strcpy (function_text, "int main (void) {\n"); + strcat (function_text, "bas_exec (0);\n"); + strcat (function_text, "exit (E_RETURN_WITHOUT_GOSUB);\n"); + strcat (function_text, "}\n"); + + /* add the function text to the output */ + this->c_output = realloc (this->c_output, strlen (this->c_output) + + strlen (function_text) + 1); + strcat (this->c_output, function_text); +} + + +/* + * Top Level Functions + */ + + +/* + * Program Generation + * params: + * ProgramNode* program the program parse tree to convert to C + * returns: + * char* the program output + */ +static void generate (CProgram *c_program, ProgramNode *program) { + + /* local variables */ + ProgramLineNode *program_line; /* line to process */ + + /* initialise this object */ + this = c_program; + data = (Private *) c_program->private_data; + + /* generate the code for the lines */ + program_line = program->first; + while (program_line) { + generate_line (program_line); + program_line = program_line->next; + } + + /* put the code together */ + generate_includes (); + generate_variables (); + if (data->input_used) + generate_bas_input (); + generate_bas_exec (); + generate_main (); +} + +/* + * Destructor + * params: + * CProgram* c_program The C program to destroy + */ +static void destroy (CProgram *c_program) { + + /* local variables */ + CLabel + *current_label, /* pointer to label to destroy */ + *next_label; /* pointer to next label to destroy */ + + /* destroy the private data */ + this = c_program; + if (this->private_data) { + data = (Private *) c_program->private_data; + next_label = data->first_label; + while (next_label) { + current_label = next_label; + next_label = current_label->next; + free (current_label); + } + if (data->code) + free (data->code); + free (data); + } + + /* destroy the generated output */ + if (this->c_output) + free (this->c_output); + + /* destroy the containing structure */ + free (c_program); +} + +/* + * Constructor + * params: + * ErrorHandler* compiler_errors the error handler + * changes: + * CProgram* this the object being created + * Private* data the object's private data + * returns: + * CProgram* the created object + */ +CProgram *new_CProgram (ErrorHandler *compiler_errors, + LanguageOptions *compiler_options) { + + /* allocate space */ + this = malloc (sizeof (CProgram)); + this->private_data = data = malloc (sizeof (Private)); + + /* initialise methods */ + this->generate = generate; + this->destroy = destroy; + + /* initialise properties */ + errors = data->errors = compiler_errors; + options = data->options = compiler_options; + data->input_used = 0; + data->vars_used = 0; + data->first_label = NULL; + data->code = malloc (1); + *data->code = '\0'; + this->c_output = malloc (1); + *this->c_output = '\0'; + + /* return the created structure */ + return this; +} diff --git a/programs/develop/tinybasic-1.0.4/src/interpret.c b/programs/develop/tinybasic-1.0.4/src/interpret.c new file mode 100644 index 0000000000..ad23ce390d --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/src/interpret.c @@ -0,0 +1,535 @@ +/* + * Tiny BASIC Interpreter and Compiler Project + * Interpreter module + * + * Released as Public Domain by Damian Gareth Walker 2019 + * Created: 23-Aug-2019 + */ + + +/* included headers */ +#include +#include +#include +#include "interpret.h" +#include "errors.h" +#include "options.h" +#include "statement.h" + + +/* forward declarations */ +static int interpret_expression (ExpressionNode *expression); +static void interpret_statement (StatementNode *statement); + + +/* + * Data Definitions + */ + +/* The GOSUB Stack */ +typedef struct gosub_stack_node GosubStackNode; +typedef struct gosub_stack_node { + ProgramLineNode *program_line; /* the line following the GOSUB */ + GosubStackNode *next; /* stack node for the previous GOSUB */ +} GosubStackNode; + +/* private data */ +typedef struct interpreter_data { + ProgramNode *program; /* the program to interpret */ + ProgramLineNode *line; /* current line we're executing */ + GosubStackNode *gosub_stack; /* the top of the GOSUB stack */ + int gosub_stack_size; /* number of entries on the GOSUB stack */ + int variables [26]; /* the numeric variables */ + int stopped; /* set to 1 when an END is encountered */ + ErrorHandler *errors; /* the error handler */ + LanguageOptions *options; /* the language options */ +} InterpreterData; + +/* convenience variables */ +static Interpreter *this; /* the object we are working with */ + + +/* + * Private Methods + */ + + +/* + * Evaluate a factor for the interpreter + * params: + * FactorNode* factor the factor to evaluate + */ +static int interpret_factor (FactorNode *factor) { + + /* local variables */ + int result_store = 0; /* result of factor evaluation */ + + /* check factor class */ + switch (factor->class) { + + /* a regular variable */ + case FACTOR_VARIABLE: + result_store = this->priv->variables [factor->data.variable - 1] + * (factor->sign == SIGN_POSITIVE ? 1 : -1); + break; + + /* an integer constant */ + case FACTOR_VALUE: + result_store = factor->data.value + * (factor->sign == SIGN_POSITIVE ? 1 : -1); + break; + + /* an expression */ + case FACTOR_EXPRESSION: + result_store = interpret_expression (factor->data.expression) + * (factor->sign == SIGN_POSITIVE ? 1 : -1); + break; + + /* this only happens if the parser has failed in its duty */ + default: + this->priv->errors->set_code + (this->priv->errors, E_INVALID_EXPRESSION, 0, this->priv->line->label); + } + + /* check the result and return it*/ + if (result_store < -32768 || result_store > 32767) + this->priv->errors->set_code + (this->priv->errors, E_OVERFLOW, 0, this->priv->line->label); + return result_store; +} + +/* + * Evaluate a term for the interpreter + * params: + * TermNode* term the term to evaluate + */ +static int interpret_term (TermNode *term) { + + /* local variables */ + int result_store; /* the partial evaluation */ + RightHandFactor *rhfactor; /* pointer to successive rh factor nodes */ + int divisor; /* used to check for division by 0 before attempting */ + + /* calculate the first factor result */ + result_store = interpret_factor (term->factor); + rhfactor = term->next; + + /* adjust store according to successive rh factors */ + while (rhfactor && ! this->priv->errors->get_code (this->priv->errors)) { + switch (rhfactor->op) { + case TERM_OPERATOR_MULTIPLY: + result_store *= interpret_factor (rhfactor->factor); + if (result_store < -32768 || result_store > 32767) + this->priv->errors->set_code + (this->priv->errors, E_OVERFLOW, 0, this->priv->line->label); + break; + case TERM_OPERATOR_DIVIDE: + if ((divisor = interpret_factor (rhfactor->factor))) + result_store /= divisor; + else + this->priv->errors->set_code + (this->priv->errors, E_DIVIDE_BY_ZERO, 0, this->priv->line->label); + break; + default: + break; + } + rhfactor = rhfactor->next; + } + + /* return the result */ + return result_store; +} + +/* + * Evaluate an expression for the interpreter + * params: + * ExpressionNode* expression the expression to evaluate + */ +static int interpret_expression (ExpressionNode *expression) { + + /* local variables */ + int result_store; /* the partial evaluation */ + RightHandTerm *rhterm; /* pointer to successive rh term nodes */ + + /* calculate the first term result */ + result_store = interpret_term (expression->term); + rhterm = expression->next; + + /* adjust store according to successive rh terms */ + while (rhterm && ! this->priv->errors->get_code (this->priv->errors)) { + switch (rhterm->op) { + case EXPRESSION_OPERATOR_PLUS: + result_store += interpret_term (rhterm->term); + if (result_store < -32768 || result_store > 32767) + this->priv->errors->set_code + (this->priv->errors, E_OVERFLOW, 0, this->priv->line->label); + break; + case EXPRESSION_OPERATOR_MINUS: + result_store -= interpret_term (rhterm->term); + if (result_store < -32768 || result_store > 32767) + this->priv->errors->set_code + (this->priv->errors, E_OVERFLOW, 0, this->priv->line->label); + break; + default: + break; + } + rhterm = rhterm->next; + } + + /* return the result */ + return result_store; +} + +/* + * Find a program line given its label + * returns: + * ProgramLineNode* the program line found + */ +static ProgramLineNode *find_label (int jump_label) { + + /* local variables */ + ProgramLineNode + *ptr, /* a line we're currently looking at */ + *found = NULL; /* the line if found */ + + /* do the search */ + for (ptr = this->priv->program->first; ptr && ! found; ptr = ptr->next) + if (ptr->label == jump_label) + found = ptr; + else if (ptr->label >= jump_label + && this->priv->options->get_line_numbers (this->priv->options) + != LINE_NUMBERS_OPTIONAL) + found = ptr; + + /* check for errors and return what was found */ + if (! found) + this->priv->errors->set_code + (this->priv->errors, E_INVALID_LINE_NUMBER, 0, this->priv->line->label); + return found; +} + + +/* + * Level 1 Routines + */ + + +/* + * Initialise the variables + */ +static void initialise_variables (void) { + int count; /* counter for this->priv->variables */ + for (count = 0; count < 26; ++count) { + this->priv->variables [count] = 0; + } +} + +/* + * Interpret a LET statement + * params: + * LetStatementNode* letn the LET statement details + */ +void interpret_let_statement (LetStatementNode *letn) { + this->priv->variables [letn->variable - 1] + = interpret_expression (letn->expression); + this->priv->line = this->priv->line->next; +} + +/* + * Interpret an IF statement + * params: + * IfStatementNode* ifn the IF statement details + */ +void interpret_if_statement (IfStatementNode *ifn) { + + /* local variables */ + int + left, /* result of the left-hand expression */ + right, /* result of the right-hand expression */ + comparison; /* result of the comparison between the two */ + + /* get the expressions */ + left = interpret_expression (ifn->left); + right = interpret_expression (ifn->right); + + /* make the comparison */ + switch (ifn->op) { + case RELOP_EQUAL: comparison = (left == right); break; + case RELOP_UNEQUAL: comparison = (left != right); break; + case RELOP_LESSTHAN: comparison = (left < right); break; + case RELOP_LESSOREQUAL: comparison = (left <= right); break; + case RELOP_GREATERTHAN: comparison = (left > right); break; + case RELOP_GREATEROREQUAL: comparison = (left >= right); break; + } + + /* perform the conditional statement */ + if (comparison && ! this->priv->errors->get_code (this->priv->errors)) + interpret_statement (ifn->statement); + else + this->priv->line = this->priv->line->next; +} + +/* + * Interpret a GOTO statement + * params: + * GotoStatementNode* goton the GOTO statement details + */ +void interpret_goto_statement (GotoStatementNode *goton) { + int label; /* the line label to go to */ + label = interpret_expression (goton->label); + if (! this->priv->errors->get_code (this->priv->errors)) + this->priv->line = find_label (label); +} + +/* + * Interpret a GOSUB statement + * params: + * GosubStatementNode* gosubn the GOSUB statement details + */ +void interpret_gosub_statement (GosubStatementNode *gosubn) { + + /* local variables */ + GosubStackNode *gosub_node; /* indicates the program line to return to */ + int label; /* the line label to go to */ + + /* create the new node on the GOSUB stack */ + if (this->priv->gosub_stack_size < this->priv->options->get_gosub_limit + (this->priv->options)) { + gosub_node = malloc (sizeof (GosubStackNode)); + gosub_node->program_line = this->priv->line->next; + gosub_node->next = this->priv->gosub_stack; + this->priv->gosub_stack = gosub_node; + ++this->priv->gosub_stack_size; + } else + this->priv->errors->set_code (this->priv->errors, + E_TOO_MANY_GOSUBS, 0, this->priv->line->label); + + /* branch to the subroutine requested */ + if (! this->priv->errors->get_code (this->priv->errors)) + label = interpret_expression (gosubn->label); + if (! this->priv->errors->get_code (this->priv->errors)) + this->priv->line = find_label (label); +} + +/* + * Interpret a RETURN statement + */ +void interpret_return_statement (void) { + + /* local variables */ + GosubStackNode *gosub_node; /* node popped off the GOSUB stack */ + + /* return to the statement following the most recent GOSUB */ + if (this->priv->gosub_stack) { + this->priv->line = this->priv->gosub_stack->program_line; + gosub_node = this->priv->gosub_stack; + this->priv->gosub_stack = this->priv->gosub_stack->next; + free (gosub_node); + --this->priv->gosub_stack_size; + } + + /* no GOSUBs led here, so raise an error */ + else + this->priv->errors->set_code + (this->priv->errors, E_RETURN_WITHOUT_GOSUB, 0, this->priv->line->label); +} + +/* + * Interpret a PRINT statement + * params: + * PrintStatementNode* printn the PRINT statement details + */ +void interpret_print_statement (PrintStatementNode *printn) { + + /* local variables */ + OutputNode *outn; /* current output node */ + int + items = 0, /* counter ensures runtime errors appear on a new line */ + result; /* the result of an expression */ + + /* print each of the output items */ + outn = printn->first; + while (outn) { + switch (outn->class) { + case OUTPUT_STRING: + printf ("%s", outn->output.string); + ++items; + break; + case OUTPUT_EXPRESSION: + result = interpret_expression (outn->output.expression); + if (! this->priv->errors->get_code (this->priv->errors)) { + printf ("%d", result); + ++items; + } + break; + } + outn = outn->next; + } + + /* print the linefeed */ + if (items) + printf ("\n"); + this->priv->line = this->priv->line->next; +} + +/* + * Interpret an INPUT statement + * params: + * InputStatementNode* inputn the INPUT statement details + */ +void interpret_input_statement (InputStatementNode *inputn) { + + /* local variables */ + VariableListNode *variable; /* current variable to input */ + int + value, /* value input from the user */ + sign = 1, /* the default sign */ + ch = 0; /* character from the input stream */ + + /* input each of the variables */ + variable = inputn->first; + while (variable) { + do { + if (ch == '-') sign = -1; else sign = 1; + ch = getchar (); + } while (ch < '0' || ch > '9'); + value = 0; + do { + value = 10 * value + (ch - '0'); + if (value * sign < -32768 || value * sign > 32767) + this->priv->errors->set_code + (this->priv->errors, E_OVERFLOW, 0, this->priv->line->label); + ch = getchar (); + } while (ch >= '0' && ch <= '9' + && ! this->priv->errors->get_code (this->priv->errors)); + this->priv->variables [variable->variable - 1] = sign * value; + variable = variable->next; + } + + /* advance to the next statement when done */ + this->priv->line = this->priv->line->next; +} + + +/* + * Interpret an individual statement + * params: + * StatementNode* statement the statement to interpret + */ +void interpret_statement (StatementNode *statement) { + + /* skip comments */ + if (! statement) { + this->priv->line = this->priv->line->next; + return; + } + + /* interpret real statements */ + switch (statement->class) { + case STATEMENT_NONE: + break; + case STATEMENT_LET: + interpret_let_statement (statement->statement.letn); + break; + case STATEMENT_IF: + interpret_if_statement (statement->statement.ifn); + break; + case STATEMENT_GOTO: + interpret_goto_statement (statement->statement.goton); + break; + case STATEMENT_GOSUB: + interpret_gosub_statement (statement->statement.gosubn); + break; + case STATEMENT_RETURN: + interpret_return_statement (); + break; + case STATEMENT_END: + this->priv->stopped = 1; + break; + case STATEMENT_PRINT: + interpret_print_statement (statement->statement.printn); + break; + case STATEMENT_INPUT: + interpret_input_statement (statement->statement.inputn); + break; + default: + printf ("Statement type %d not implemented.\n", statement->class); + } +} + +/* + * Interpret program starting from a particular line + * params: + * ProgramLineNode* program_line the starting line + */ +static void interpret_program_from (ProgramLineNode *program_line) { + this->priv->line = program_line; + while (this->priv->line + && ! this->priv->stopped + && ! this->priv->errors->get_code (this->priv->errors)) + interpret_statement (this->priv->line->statement); +} + + +/* + * Public Methods + */ + + +/* + * Interpret the program from the beginning + * params: + * Interpreter* interpreter the interpreter to use + * ProgramNode* program the program to interpret + */ +static void interpret (Interpreter *interpreter, ProgramNode *program) { + this = interpreter; + this->priv->program = program; + initialise_variables (); + interpret_program_from (this->priv->program->first); +} + +/* + * Destroy the interpreter + * params: + * Interpreter* interpreter the doomed interpreter + */ +static void destroy (Interpreter *interpreter) { + if (interpreter) { + if (interpreter->priv) + free (interpreter->priv); + free (interpreter); + } +} + + +/* + * Constructors + */ + + +/* + * Constructor + * returns: + * Interpreter* the new interpreter + */ +Interpreter *new_Interpreter (ErrorHandler *errors, LanguageOptions *options) { + + /* allocate memory */ + this = malloc (sizeof (Interpreter)); + this->priv = malloc (sizeof (InterpreterData)); + + /* initialise methods */ + this->interpret = interpret; + this->destroy = destroy; + + /* initialise properties */ + this->priv->gosub_stack = NULL; + this->priv->gosub_stack_size = 0; + this->priv->stopped = 0; + this->priv->errors = errors; + this->priv->options = options; + + /* return the new object */ + return this; +} diff --git a/programs/develop/tinybasic-1.0.4/src/options.c b/programs/develop/tinybasic-1.0.4/src/options.c new file mode 100644 index 0000000000..9a17d6e7f2 --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/src/options.c @@ -0,0 +1,184 @@ +/* + * Tiny BASIC Interpreter and Compiler Project + * Language Options Module + * + * Released as Public Domain by Damian Gareth Walker 2019 + * Created: 18-Aug-2019 + */ + + +/* included headers */ +#include +#include +#include +#include "options.h" + + +/* + * Data Definitions + */ + + +/* private data */ +typedef struct { + LineNumberOption line_numbers; /* mandatory, implied, optional */ + int line_limit; /* highest line number allowed */ + CommentOption comments; /* enabled, disabled */ + int gosub_limit; /* how many nested gosubs */ +} Private; + +/* convenience variables */ +static LanguageOptions *this; /* object being worked on */ +static Private *data; /* the object's private data */ + + +/* + * Public Methods + */ + + +/* + * Set the line number option individually + * params: + * LanguageOptions* options the options + * LineNumberOption line_numbers line number option to set + */ +static void set_line_numbers (LanguageOptions *options, + LineNumberOption line_numbers) { + this = options; + data = this->data; + data->line_numbers = line_numbers; +} + +/* + * Set the line number limit individually + * params: + * LanguageOptions* options the options + * int line_limit line number limit to set + */ +static void set_line_limit (LanguageOptions *options, int line_limit) { + this = options; + data = this->data; + data->line_limit = line_limit; +} + +/* + * Set the comments option individually + * params: + * LanguageOptions* options the options + * CommentOption comments comment option to set + */ +static void set_comments (LanguageOptions *options, CommentOption comments) { + this = options; + data = this->data; + data->comments = comments; +} + +/* + * Set the GOSUB stack limit + * params: + * LanuageOptions* options the options + * int limit the desired stack limit + */ +static void set_gosub_limit (LanguageOptions *options, int gosub_limit) { + this = options; + data = this->data; + data->gosub_limit = gosub_limit; +} + +/* + * Return the line number setting + * params: + * LanguageOptions* options the options + * returns: + * LineNumberOption the line number setting + */ +static LineNumberOption get_line_numbers (LanguageOptions *options) { + this = options; + data = this->data; + return data->line_numbers; +} + +/* + * Return the line number limit + * params: + * LanguageOptions* options the options + * returns: + * int the line number setting + */ +static int get_line_limit (LanguageOptions *options) { + this = options; + data = this->data; + return data->line_limit; +} + +/* + * Return the comments setting + * params: + * LanguageOptions* options the options + * returns: + * CommentOption the line number setting + */ +static CommentOption get_comments (LanguageOptions *options) { + this = options; + data = this->data; + return data->comments; +} + +/* + * Return the GOSUB stack limit setting + * params: + * LanguageOptions* options the options + * returns: + * int the current GOSUB stack limit + */ +static int get_gosub_limit (LanguageOptions *options) { + this = options; + data = this->data; + return data->gosub_limit; +} + +/* + * Destroy the settings object + * params: + * LanguageOptions* options the options + */ +static void destroy (LanguageOptions *options) { + if (options) { + if (options->data) + free (options->data); + free (options); + } +} + +/* + * Constructor for language options + * returns: + * LanguageOptions* the new language options object + */ +LanguageOptions *new_LanguageOptions (void) { + + /* allocate memory */ + this = malloc (sizeof (LanguageOptions)); + data = this->data = malloc (sizeof (Private)); + + /* initialise methods */ + this->set_line_numbers = set_line_numbers; + this->set_line_limit = set_line_limit; + this->set_comments = set_comments; + this->set_gosub_limit = set_gosub_limit; + this->get_line_numbers = get_line_numbers; + this->get_line_limit = get_line_limit; + this->get_comments = get_comments; + this->get_gosub_limit = get_gosub_limit; + this->destroy = destroy; + + /* initialise properties */ + data->line_numbers = LINE_NUMBERS_OPTIONAL; + data->line_limit = 32767; + data->comments = COMMENTS_ENABLED; + data->gosub_limit = 64; + + /* return the new object */ + return this; +} diff --git a/programs/develop/tinybasic-1.0.4/src/parser.c b/programs/develop/tinybasic-1.0.4/src/parser.c new file mode 100644 index 0000000000..8bee9c6c8f --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/src/parser.c @@ -0,0 +1,947 @@ +/* + * Tiny BASIC Interpreter and Compiler Project + * Parser module + * + * Copyright (C) Damian Gareth Walker 2019 + * Created: 12-Aug-2019 + */ + + +/* included headers */ +#include +#include +#include +#include +#include "common.h" +#include "errors.h" +#include "options.h" +#include "token.h" +#include "tokeniser.h" +#include "parser.h" +#include "expression.h" + + +/* + * Internal Function Declarations + */ + + +/* parse_expression() has a forward reference from parse_factor() */ +static ExpressionNode *parse_expression (void); + +/* parse_statement() has a forward reference from parse_if_statement() */ +static StatementNode *parse_statement (void); + + +/* + * Data Definitions + */ + + +/* private data */ +typedef struct parser_data { + int last_label; /* last line label encountered */ + int current_line; /* the last source line parsed */ + int end_of_file; /* end of file signal */ + Token *stored_token; /* token read ahead */ + TokenStream *stream; /* the input stream */ + ErrorHandler *errors; /* the parse error handler */ + LanguageOptions *options; /* the language options */ +} ParserData; + +/* convenience variables */ +static Parser *this; /* the current parser being worked on */ + + +/* + * Private methods + */ + + +/* + * Get next token to parse, from read-ahead buffer or tokeniser. + */ +static Token *get_token_to_parse () { + + /* local variables */ + Token *token; /* token to return */ + + /* get the token one way or another */ + if (this->priv->stored_token) { + token = this->priv->stored_token; + this->priv->stored_token = NULL; + } else + token = this->priv->stream->next (this->priv->stream); + + /* store the line, check EOF and return the token */ + this->priv->current_line = token->get_line (token); + if (token->get_class (token) == TOKEN_EOF) + this->priv->end_of_file = !0; + return token; +} + +/* + * Parse a factor + * returns: + * FactorNode* a new factor node holding the parsed data + */ +static FactorNode *parse_factor (void) { + + /* local variables */ + Token *token; /* token to read */ + FactorNode *factor = NULL; /* the factor we're building */ + ExpressionNode *expression = NULL; /* any parenthesised expression */ + int start_line; /* the line on which this factor occurs */ + + /* initialise the factor and grab the next token */ + factor = factor_create (); + token = get_token_to_parse (); + start_line = token->get_line (token); + + /* interpret a sign */ + if (token->get_class (token) == TOKEN_PLUS + || token->get_class (token) == TOKEN_MINUS) { + factor->sign = (token->get_class (token) == TOKEN_PLUS) + ? SIGN_POSITIVE + : SIGN_NEGATIVE; + token->destroy (token); + token = get_token_to_parse (); + } + + /* interpret a number */ + if (token->get_class (token) == TOKEN_NUMBER) { + factor->class = FACTOR_VALUE; + factor->data.value = atoi (token->get_content (token)); + if (factor->data.value < -32768 || factor->data.value > 32767) + this->priv->errors->set_code + (this->priv->errors, E_OVERFLOW, start_line, this->priv->last_label); + token->destroy (token); + } + + /* interpret a variable */ + else if (token->get_class (token) == TOKEN_VARIABLE) { + factor->class = FACTOR_VARIABLE; + factor->data.variable = (int) *token->get_content (token) & 0x1F; + token->destroy (token); + } + + /* interpret an parenthesised expression */ + else if (token->get_class (token) == TOKEN_LEFT_PARENTHESIS) { + + /* parse the parenthesised expression and complete the factor */ + token->destroy (token); + expression = parse_expression (); + if (expression) { + token = get_token_to_parse (); + if (token->get_class (token) == TOKEN_RIGHT_PARENTHESIS) { + factor->class = FACTOR_EXPRESSION; + factor->data.expression = expression; + } else { + this->priv->errors->set_code + (this->priv->errors, E_MISSING_RIGHT_PARENTHESIS, start_line, + this->priv->last_label); + factor_destroy (factor); + factor = NULL; + expression_destroy (expression); + } + token->destroy (token); + } + + /* clean up after invalid parenthesised expression */ + else { + this->priv->errors->set_code (this->priv->errors, E_INVALID_EXPRESSION, + token->get_line (token), this->priv->last_label); + token->destroy (token); + factor_destroy (factor); + factor = NULL; + } + } + + /* deal with other errors */ + else { + this->priv->errors->set_code + (this->priv->errors, E_INVALID_EXPRESSION, token->get_line (token), + this->priv->last_label); + factor_destroy (factor); + token->destroy (token); + factor = NULL; + } + + /* return the factor */ + return factor; +} + +/* + * Parse a term + * globals: + * Token* stored_token the token read past the end of the term + * returns: + * TermNode* a new term node holding the parsed term + */ +static TermNode *parse_term (void) { + + /* local variables */ + TermNode *term = NULL; /* the term we're building */ + FactorNode *factor = NULL; /* factor detected */ + RightHandFactor + *rhptr = NULL, /* previous right-hand factor */ + *rhfactor = NULL; /* right-hand factor detected */ + Token *token = NULL; /* token read while looking for operator */ + + /* scan the first factor */ + if ((factor = parse_factor ())) { + term = term_create (); + term->factor = factor; + term->next = NULL; + + /* look for subsequent factors */ + while ((token = get_token_to_parse ()) + && ! this->priv->errors->get_code (this->priv->errors) + && (token->get_class (token) == TOKEN_MULTIPLY + || token->get_class (token) == TOKEN_DIVIDE)) { + + /* parse the sign and the factor */ + rhfactor = rhfactor_create (); + rhfactor->op = token->get_class (token) == TOKEN_MULTIPLY + ? TERM_OPERATOR_MULTIPLY + : TERM_OPERATOR_DIVIDE; + if ((rhfactor->factor = parse_factor ())) { + rhfactor->next = NULL; + if (rhptr) + rhptr->next = rhfactor; + else + term->next = rhfactor; + rhptr = rhfactor; + } + + /* set an error if we read an operator but not a factor */ + else { + rhfactor_destroy (rhfactor); + if (! this->priv->errors->get_code (this->priv->errors)) + this->priv->errors->set_code + (this->priv->errors, E_INVALID_EXPRESSION, token->get_line (token), + this->priv->last_label); + } + + /* clean up token */ + token->destroy (token); + } + + /* we've read past the end of the term; put the token back */ + this->priv->stored_token = token; + } + + /* return the evaluated term, if any */ + return term; +} + +/* + * Parse an expression + * returns: + * ExpressionNode* the parsed expression + */ +static ExpressionNode *parse_expression (void) { + + /* local variables */ + ExpressionNode *expression = NULL; /* the expression we build */ + TermNode *term; /* term detected */ + RightHandTerm + *rhterm = NULL, /* the right-hand term detected */ + *rhptr = NULL; /* pointer to the previous right-hand term */ + Token *token; /* token read when scanning for right-hand terms */ + + /* scan the first term */ + if ((term = parse_term ())) { + expression = expression_create (); + expression->term = term; + expression->next = NULL; + + /* look for subsequent terms */ + while ((token = get_token_to_parse ()) + && ! this->priv->errors->get_code (this->priv->errors) + && (token->get_class (token) == TOKEN_PLUS + || token->get_class (token) == TOKEN_MINUS)) { + + /* parse the sign and the factor */ + rhterm = rhterm_create (); + rhterm->op = token->get_class (token) == TOKEN_PLUS + ? EXPRESSION_OPERATOR_PLUS + : EXPRESSION_OPERATOR_MINUS; + if ((rhterm->term = parse_term ())) { + rhterm->next = NULL; + if (rhptr) + rhptr->next = rhterm; + else + expression->next = rhterm; + rhptr = rhterm; + } + + /* set an error condition if we read a sign but not a factor */ + else { + rhterm_destroy (rhterm); + if (! this->priv->errors->get_code (this->priv->errors)) + this->priv->errors->set_code + (this->priv->errors, E_INVALID_EXPRESSION, token->get_line (token), + this->priv->last_label); + } + + /* clean up token */ + token->destroy (token); + } + + /* we've read past the end of the term; put the token back */ + this->priv->stored_token = token; + } + + /* return the evaluated expression, if any */ + return expression; +} + +/* + * Calculate numeric line label according to language options. + * This will be used if the line has no label specified. + * returns: + * int numeric line label + */ +static int generate_default_label (void) { + if (this->priv->options->get_line_numbers (this->priv->options) + == LINE_NUMBERS_IMPLIED) + return this->priv->last_label + 1; + else + return 0; +} + +/* + * Validate a line label according to the language options + * params: + * int label the numeric label to verify. + * returns: + * int !0 if the number is valid, 0 if invalid + */ +static int validate_line_label (int label) { + + /* line labels should be non-negative and within the set limit */ + if (label < 0 + || label > this->priv->options->get_line_limit (this->priv->options)) + return 0; + + /* line labels should be non-zero unless they're optional */ + if (label == 0 + && this->priv->options->get_line_numbers (this->priv->options) + != LINE_NUMBERS_OPTIONAL) + return 0; + + /* line labels should be ascending unless they're optional */ + if (label <= this->priv->last_label + && this->priv->options->get_line_numbers (this->priv->options) + != LINE_NUMBERS_OPTIONAL) + return 0; + + /* if all the above tests passed, the line label is valid */ + return 1; +} + +/* + * Parse a LET statement + * returns: + * StatementNode* The statement assembled + */ +static StatementNode *parse_let_statement (void) { + + /* local variables */ + Token *token; /* tokens read as part of LET statement */ + int line; /* line containing the LET token */ + StatementNode *statement; /* the new statement */ + + /* initialise the statement */ + statement = statement_create (); + statement->class = STATEMENT_LET; + statement->statement.letn = statement_create_let (); + line = this->priv->stream->get_line (this->priv->stream); + + /* see what variable we're assigning */ + token = get_token_to_parse (); + if (token->get_class (token) != TOKEN_VARIABLE) { + this->priv->errors->set_code + (this->priv->errors, E_INVALID_VARIABLE, line, this->priv->last_label); + statement_destroy (statement); + token->destroy (token); + return NULL; + } + statement->statement.letn->variable = *token->get_content (token) & 0x1f; + + /* get the "=" */ + token->destroy (token); + token = get_token_to_parse (); + if (token->get_class (token) != TOKEN_EQUAL) { + this->priv->errors->set_code + (this->priv->errors, E_INVALID_ASSIGNMENT, line, this->priv->last_label); + statement_destroy (statement); + token->destroy (token); + return NULL; + } + token->destroy (token); + + /* get the expression */ + statement->statement.letn->expression = parse_expression (); + if (! statement->statement.letn->expression) { + this->priv->errors->set_code + (this->priv->errors, E_INVALID_EXPRESSION, line, this->priv->last_label); + statement_destroy (statement); + return NULL; + } + + /* return the statement */ + return statement; +} + +/* + * Parse an IF statement + * returns: + * StatementNode* The statement to assemble. + */ +static StatementNode *parse_if_statement (void) { + + /* local variables */ + Token *token; /* tokens read as part of the statement */ + StatementNode *statement; /* the IF statement */ + + /* initialise the statement */ + statement = statement_create (); + statement->class = STATEMENT_IF; + statement->statement.ifn = statement_create_if (); + + /* parse the first expression */ + statement->statement.ifn->left = parse_expression (); + + /* parse the operator */ + if (! this->priv->errors->get_code (this->priv->errors)) { + token = get_token_to_parse (); + switch (token->get_class (token)) { + case TOKEN_EQUAL: + statement->statement.ifn->op = RELOP_EQUAL; + break; + case TOKEN_UNEQUAL: + statement->statement.ifn->op = RELOP_UNEQUAL; + break; + case TOKEN_LESSTHAN: + statement->statement.ifn->op = RELOP_LESSTHAN; + break; + case TOKEN_LESSOREQUAL: + statement->statement.ifn->op = RELOP_LESSOREQUAL; + break; + case TOKEN_GREATERTHAN: + statement->statement.ifn->op = RELOP_GREATERTHAN; + break; + case TOKEN_GREATEROREQUAL: + statement->statement.ifn->op = RELOP_GREATEROREQUAL; + break; + default: + this->priv->errors->set_code + (this->priv->errors, E_INVALID_OPERATOR, token->get_line (token), + this->priv->last_label); + } + token->destroy (token); + } + + /* parse the second expression */ + if (! this->priv->errors->get_code (this->priv->errors)) + statement->statement.ifn->right = parse_expression (); + + /* parse the THEN */ + if (! this->priv->errors->get_code (this->priv->errors)) { + token = get_token_to_parse (); + if (token->get_class (token) != TOKEN_THEN) + this->priv->errors->set_code + (this->priv->errors, E_THEN_EXPECTED, token->get_line (token), + this->priv->last_label); + token->destroy (token); + } + + /* parse the conditional statement */ + if (! this->priv->errors->get_code (this->priv->errors)) + statement->statement.ifn->statement = parse_statement (); + + /* destroy the half-made statement if errors occurred */ + if (this->priv->errors->get_code (this->priv->errors)) { + statement_destroy (statement); + statement = NULL; + } + + /* return the statement */ + return statement; +} + +/* + * Parse a GOTO statement + * returns: + * StatementNode* the parsed GOTO statement + */ +static StatementNode *parse_goto_statement (void) { + + /* local variables */ + StatementNode *statement; /* the IF statement */ + + /* initialise the statement */ + statement = statement_create (); + statement->class = STATEMENT_GOTO; + statement->statement.goton = statement_create_goto (); + + /* parse the line label expression */ + if (! (statement->statement.goton->label = parse_expression ())) { + statement_destroy (statement); + statement = NULL; + } + + /* return the new statement */ + return statement; +} + +/* + * Parse a GOSUB statement + * returns: + * StatementNode* the parsed GOSUB statement + */ +static StatementNode *parse_gosub_statement (void) { + + /* local variables */ + StatementNode *statement; /* the IF statement */ + + /* initialise the statement */ + statement = statement_create (); + statement->class = STATEMENT_GOSUB; + statement->statement.gosubn = statement_create_gosub (); + + /* parse the line label expression */ + if (! (statement->statement.gosubn->label = parse_expression ())) { + statement_destroy (statement); + statement = NULL; + } + + /* return the new statement */ + return statement; +} + +/* + * Parse an RETURN statement + * returns: + * StatementNode* The statement assembled + */ +static StatementNode *parse_return_statement (void) { + StatementNode *statement; /* the RETURN */ + statement = statement_create (); + statement->class = STATEMENT_RETURN; + return statement; +} + +/* + * Parse an END statement + * returns: + * StatementNode* The statement assembled + */ +static StatementNode *parse_end_statement (void) { + StatementNode *statement = NULL; /* the END */ + statement = statement_create (); + statement->class = STATEMENT_END; + return statement; +} + +/* + * Parse a PRINT statement + * returns: + * StatementNode* The statement assembled + */ +static StatementNode *parse_print_statement (void) { + + /* local variables */ + Token *token = NULL; /* tokens read as part of the statement */ + StatementNode *statement; /* the statement we're building */ + int line; /* line containing the PRINT token */ + OutputNode + *nextoutput = NULL, /* the next output node we're parsing */ + *lastoutput = NULL; /* the last output node we parsed */ + ExpressionNode *expression; /* a parsed expression */ + + /* initialise the statement */ + statement = statement_create (); + statement->class = STATEMENT_PRINT; + statement->statement.printn = statement_create_print (); + line = this->priv->stream->get_line (this->priv->stream); + + /* main loop for parsing the output list */ + do { + + /* discard a previous comma, and read the next output value */ + if (token) + token->destroy (token); + token = get_token_to_parse (); + + /* process a premature end of line */ + if (token->get_class (token) == TOKEN_EOF + || token->get_class (token) == TOKEN_EOL) { + this->priv->errors->set_code + (this->priv->errors, E_INVALID_PRINT_OUTPUT, line, + this->priv->last_label); + statement_destroy (statement); + statement = NULL; + token->destroy (token); + } + + /* process a literal string */ + else if (token->get_class (token) == TOKEN_STRING) { + nextoutput = malloc (sizeof (OutputNode)); + nextoutput->class = OUTPUT_STRING; + nextoutput->output.string = malloc + (1 + strlen (token->get_content (token))); + strcpy (nextoutput->output.string, token->get_content (token)); + nextoutput->next = NULL; + token->destroy (token); + } + + /* attempt to process an expression */ + else { + this->priv->stored_token = token; + if ((expression = parse_expression ())) { + nextoutput = malloc (sizeof (OutputNode)); + nextoutput->class = OUTPUT_EXPRESSION; + nextoutput->output.expression = expression; + nextoutput->next = NULL; + } else { + this->priv->errors->set_code + (this->priv->errors, E_INVALID_PRINT_OUTPUT, token->get_line (token), + this->priv->last_label); + statement_destroy (statement); + statement = NULL; + } + } + + /* add this output item to the statement and look for another */ + if (! this->priv->errors->get_code (this->priv->errors)) { + if (lastoutput) + lastoutput->next = nextoutput; + else + statement->statement.printn->first = nextoutput; + lastoutput = nextoutput; + token = get_token_to_parse (); + } + + /* continue the loop until the statement appears to be finished */ + } while (! this->priv->errors->get_code (this->priv->errors) + && token->get_class (token) == TOKEN_COMMA); + + /* push back the last token and return the assembled statement */ + if (! this->priv->errors->get_code (this->priv->errors)) + this->priv->stored_token = token; + return statement; +} + +/* + * Parse an INPUT statement + * returns: + * StatementNode* The statement assembled + */ +static StatementNode *parse_input_statement (void) { + + /* local variables */ + Token *token = NULL; /* tokens read as part of the statement */ + StatementNode *statement; /* the statement we're building */ + int line; /* line containing the INPUT token */ + VariableListNode + *nextvar = NULL, /* the next variable node we're parsing */ + *lastvar = NULL; /* the last variable node we parsed */ + + /* initialise the statement */ + statement = statement_create (); + statement->class = STATEMENT_INPUT; + statement->statement.inputn = statement_create_input (); + line = this->priv->stream->get_line (this->priv->stream); + + /* main loop for parsing the variable list */ + do { + + /* discard a previous comma, and seek the next variable */ + if (token) token->destroy (token); + token = get_token_to_parse (); + + /* process a premature end of line */ + if (token->get_class (token) == TOKEN_EOF + || token->get_line (token) != line) { + this->priv->errors->set_code + (this->priv->errors, E_INVALID_VARIABLE, line, this->priv->last_label); + statement_destroy (statement); + statement = NULL; + } + + /* attempt to process an variable name */ + else if (token->get_class (token) != TOKEN_VARIABLE) { + this->priv->errors->set_code + (this->priv->errors, E_INVALID_VARIABLE, token->get_line (token), + this->priv->last_label); + statement_destroy (statement); + statement = NULL; + } else { + nextvar = malloc (sizeof (VariableListNode)); + nextvar->variable = *token->get_content (token) & 0x1f; + nextvar->next = NULL; + token->destroy (token); + } + + /* add this variable to the statement and look for another */ + if (! this->priv->errors->get_code (this->priv->errors)) { + if (lastvar) + lastvar->next = nextvar; + else + statement->statement.inputn->first = nextvar; + lastvar = nextvar; + token = get_token_to_parse (); + } + } while (! this->priv->errors->get_code (this->priv->errors) + && token->get_class (token) == TOKEN_COMMA); + + /* return the assembled statement */ + this->priv->stored_token = token; + return statement; +} + +/* + * Parse a statement from the source file + * returns: + * StatementNode* a fully-assembled statement, hopefully. + */ +static StatementNode *parse_statement () { + + /* local variables */ + Token *token; /* token read */ + StatementNode *statement = NULL; /* the new statement */ + + /* get the next token */ + token = get_token_to_parse (); + + /* check for command */ + switch (token->get_class (token)) { + case TOKEN_EOL: + this->priv->stored_token = token; + statement = NULL; + break; + case TOKEN_LET: + token->destroy (token); + statement = parse_let_statement (); + break; + case TOKEN_IF: + token->destroy (token); + statement = parse_if_statement (); + break; + case TOKEN_GOTO: + token->destroy (token); + statement = parse_goto_statement (); + break; + case TOKEN_GOSUB: + token->destroy (token); + statement = parse_gosub_statement (); + break; + case TOKEN_RETURN: + token->destroy (token); + statement = parse_return_statement (); + break; + case TOKEN_END: + token->destroy (token); + statement = parse_end_statement (); + break; + case TOKEN_PRINT: + token->destroy (token); + statement = parse_print_statement (); + break; + case TOKEN_INPUT: + token->destroy (token); + statement = parse_input_statement (); + break; + default: + this->priv->errors->set_code + (this->priv->errors, E_UNRECOGNISED_COMMAND, token->get_line (token), + this->priv->last_label); + token->destroy (token); + } + + /* return the statement */ + return statement; +} + +/* + * Parse a line from the source file. + * returns: + * StatementNode a fully-assembled statement, hopefully. + */ +static ProgramLineNode *parse_program_line (void) { + + /* local variables */ + Token *token; /* token read */ + ProgramLineNode *program_line; /* program line read */ + int label_encountered = 0; /* 1 if this line has an explicit label */ + + /* initialise the program line and get the first token */ + program_line = program_line_create (); + program_line->label = generate_default_label (); + token = get_token_to_parse (); + + /* deal with end of file */ + if (token->get_class (token) == TOKEN_EOF) { + token->destroy (token); + program_line_destroy (program_line); + return NULL; + } + + /* deal with line label, if supplied */ + if (token->get_class (token) == TOKEN_NUMBER) { + program_line->label = atoi (token->get_content (token)); + label_encountered = 1; + token->destroy (token); + } else + this->priv->stored_token = token; + + /* validate the supplied or implied line label */ + if (! validate_line_label (program_line->label)) { + this->priv->errors->set_code + (this->priv->errors, E_INVALID_LINE_NUMBER, this->priv->current_line, + program_line->label); + program_line_destroy (program_line); + return NULL; + } + if (label_encountered) + this->priv->last_label = program_line->label; + + /* check for a statement and an EOL */ + program_line->statement = parse_statement (); + if (! this->priv->errors->get_code (this->priv->errors)) { + token = get_token_to_parse (); + if (token->get_class (token) != TOKEN_EOL + && token->get_class (token) != TOKEN_EOF) + this->priv->errors->set_code + (this->priv->errors, E_UNEXPECTED_PARAMETER, this->priv->current_line, + this->priv->last_label); + token->destroy (token); + } + if (program_line->statement) + this->priv->last_label = program_line->label; + + /* return the program line */ + return program_line; +} + + +/* + * Public Methods + */ + + +/* + * Parse the whole program + * params: + * Parser* parser The parser to use + * returns: + * ProgramNode* The parsed program + */ +static ProgramNode *parse (Parser *parser) { + + /* local varables */ + ProgramNode *program; /* the stored program */ + ProgramLineNode + *previous = NULL, /* the previous line */ + *current; /* the current line */ + + /* initialise the program */ + this = parser; + program = malloc (sizeof (ProgramNode)); + program->first = NULL; + + /* read lines until reaching an error or end of input */ + while ((current = parse_program_line ()) + && ! this->priv->errors->get_code (this->priv->errors)) { + if (previous) + previous->next = current; + else + program->first = current; + previous = current; + } + + /* return the program */ + return program; +} + +/* + * Return the current source line we're parsing + * params: + * Parser* The parser to use + * returns: + * int the line returned + */ +static int get_line (Parser *parser) { + return parser->priv->current_line; +} + +/* + * Return the label of the source line we're parsing + * params: + * Parser* parser The parser to use + * returns: + * int the label returned + */ +static int get_label (Parser *parser) { + return parser->priv->last_label; +} + +/* + * Destroy this parser object + * params: + * Parser* parser the doomed parser + */ +void destroy (Parser *parser) { + if (parser) { + if (parser->priv) { + if (parser->priv->stream) + parser->priv->stream->destroy (parser->priv->stream); + } + free (parser->priv); + } + free (parser); +} + + +/* + * Constructors + */ + + +/* + * Constructor + * params: + * ErrorHandler* errors the error handler to use + * LanguageOptions* options the language options to use + * FILE* input the input file + * returns: + * Parser* the new parser + */ +Parser *new_Parser (ErrorHandler *errors, LanguageOptions *options, + FILE *input) { + + /* allocate memory */ + this = malloc (sizeof (Parser)); + this->priv = malloc (sizeof (ParserData)); + this->priv->stream = new_TokenStream (input); + + /* initialise methods */ + this->parse = parse; + this->get_line = get_line; + this->get_label = get_label; + this->destroy = destroy; + + /* initialise properties */ + this->priv->last_label = 0; + this->priv->current_line = 0; + this->priv->end_of_file = 0; + this->priv->stored_token = NULL; + this->priv->errors = errors; + this->priv->options = options; + + /* return the new object */ + return this; +} diff --git a/programs/develop/tinybasic-1.0.4/src/statement.c b/programs/develop/tinybasic-1.0.4/src/statement.c new file mode 100644 index 0000000000..3b1b7f97c6 --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/src/statement.c @@ -0,0 +1,393 @@ +/* + * Tiny BASIC + * Statement Handling Module + * + * Released as Public Domain by Damian Gareth Walker 2019 + * Created: 15-Aug-2019 + */ + + +/* included headers */ +#include +#include +#include +#include +#include "statement.h" + + +/* + * LET Statement Functions + */ + + +/* + * LET statement constructor + * returns: + * LetStatementNode* the created LET statement + */ +LetStatementNode *statement_create_let (void) { + + /* local variables */ + LetStatementNode *letn; /* the created node */ + + /* allocate memory and assign safe defaults */ + letn = malloc (sizeof (LetStatementNode)); + letn->variable = 0; + letn->expression = NULL; + + /* return the LET statement node */ + return letn; +} + +/* + * Destructor for a LET statement + * params: + * LetStatementNode *letn the doomed LET statement. + */ +void statement_destroy_let (LetStatementNode *letn) { + if (letn->expression) + expression_destroy (letn->expression); + free (letn); +} + + +/* + * IF Statement Functions + */ + + +/* + * IF statement constructor + * returns: + * IfStatementNode* the created IF statement + */ +IfStatementNode *statement_create_if (void) { + + /* local variables */ + IfStatementNode *ifn; /* the created node */ + + /* allocate memory and assign safe defaults */ + ifn = malloc (sizeof (IfStatementNode)); + ifn->left = ifn->right = NULL; + ifn->op = RELOP_EQUAL; + ifn->statement = NULL; + + /* return the IF statement node */ + return ifn; +} + +/* + * IF statement destructor + * params: + * IfStatementNode* ifn the doomed IF statement + */ +void statement_destroy_if (IfStatementNode *ifn) { + if (ifn->left) + expression_destroy (ifn->left); + if (ifn->right) + expression_destroy (ifn->right); + if (ifn->statement) + statement_destroy (ifn->statement); + free (ifn); +} + + +/* + * GOTO Statement Functions + */ + + +/* + * GOTO Statement Constructor + * returns: + * GotoStatementNode* the new GOTO statement + */ +GotoStatementNode *statement_create_goto (void) { + + /* local variables */ + GotoStatementNode *goton; /* the statement to create */ + + /* create and initialise the data */ + goton = malloc (sizeof (GotoStatementNode)); + goton->label = NULL; + + /* return the goto statement */ + return goton; +} + +/* + * GOTO Statement Destructor + * params: + * GotoStatementNode* goton the doomed GOTO statement + */ +void statement_destroy_goto (GotoStatementNode *goton) { + if (goton) { + if (goton->label) + expression_destroy (goton->label); + free (goton); + } +} + + +/* + * GOSUB Statement Functions + */ + + +/* + * GOSUB Statement Constructor + * returns: + * GosubStatementNode* the new GOSUB statement + */ +GosubStatementNode *statement_create_gosub (void) { + + /* local variables */ + GosubStatementNode *gosubn; /* the statement to create */ + + /* create and initialise the data */ + gosubn = malloc (sizeof (GosubStatementNode)); + gosubn->label = NULL; + + /* return the gosub statement */ + return gosubn; +} + +/* + * GOSUB Statement Destructor + * params: + * GosubStatementNode* gosubn the doomed GOSUB statement + */ +void statement_destroy_gosub (GosubStatementNode *gosubn) { + if (gosubn) { + if (gosubn->label) + expression_destroy (gosubn->label); + free (gosubn); + } +} + + +/* + * PRINT Statement Functions + */ + + +/* + * PRINT statement constructor + * returns: + * PrintStatementNode* the created PRINT statement + */ +PrintStatementNode *statement_create_print (void) { + + /* local variables */ + PrintStatementNode *printn; /* the created node */ + + /* allocate memory and assign safe defaults */ + printn = malloc (sizeof (PrintStatementNode)); + printn->first = NULL; + + /* return the PRINT statement node */ + return printn; +} + +/* + * Destructor for a PRINT statement + * params: + * PrintStatementNode *printn the doomed PRINT statement. + */ +void statement_destroy_print (PrintStatementNode *printn) { + OutputNode *current, *next; + current = printn->first; + while (current) { + next = current->next; + if (current->class == OUTPUT_STRING) + free (current->output.string); + else if (current->class == OUTPUT_EXPRESSION) + expression_destroy (current->output.expression); + free (current); + current = next; + } + free (printn); +} + + +/* + * INPUT Statement Functions + */ + + +/* + * INPUT statement constructor + * returns: + * InputStatementNode* initialised INPUT statement data + */ +InputStatementNode *statement_create_input (void) { + + /* local variables */ + InputStatementNode *inputn; /* the new input statement data */ + + /* allocate memory and initalise safely */ + inputn = malloc (sizeof (InputStatementNode)); + inputn->first = NULL; + + /* return the created node */ + return inputn; +} + +/* + * INPUT statement destructor + * params: + * InputStatementNode* inputn the doomed INPUT statement node + */ +void statement_destroy_input (InputStatementNode *inputn) { + + /* local variables */ + VariableListNode + *variable, /* the current variable to destroy */ + *next; /* the next variable to destroy */ + + /* delete the variables from the variable list, then the input node */ + if (inputn) { + variable = inputn->first; + while (variable) { + next = variable->next; + free (variable); + variable = next; + } + free (inputn); + } +} + + +/* + * Top Level Functions + */ + + +/* + * Statement constructor + * returns: + * StatementNode* the newly-created blank statement + */ +StatementNode *statement_create (void) { + + /* local variables */ + StatementNode *statement; /* the created statement */ + + /* allocate memory and set defaults */ + statement = malloc (sizeof (StatementNode)); + statement->class = STATEMENT_NONE; + + /* return the created statement */ + return statement; +} + +/* + * Statement destructor + * params: + * StatementNode* statement the doomed statement + */ +void statement_destroy (StatementNode *statement) { + switch (statement->class) { + case STATEMENT_LET: + statement_destroy_let (statement->statement.letn); + break; + case STATEMENT_PRINT: + statement_destroy_print (statement->statement.printn); + break; + case STATEMENT_INPUT: + statement_destroy_input (statement->statement.inputn); + break; + case STATEMENT_IF: + statement_destroy_if (statement->statement.ifn); + break; + case STATEMENT_GOTO: + statement_destroy_goto (statement->statement.goton); + break; + case STATEMENT_GOSUB: + statement_destroy_gosub (statement->statement.gosubn); + break; + default: + break; + } + free (statement); +} + + +/* + * Program Line Constructor + * returns: + * ProgramLineNode* the new program line + */ +ProgramLineNode *program_line_create (void) { + + /* local variables */ + ProgramLineNode *program_line; /* the program line to create */ + + /* create and initialise the program line */ + program_line = malloc (sizeof (ProgramLineNode)); + program_line->label = 0; + program_line->statement = NULL; + program_line->next = NULL; + + /* return the new program line */ + return program_line; +} + +/* + * Program Line Destructor + * params: + * ProgramLineNode* program_line the doomed program line + * params: + * ProgramLineNode* the next program line + */ +ProgramLineNode *program_line_destroy (ProgramLineNode *program_line) { + + /* local variables */ + ProgramLineNode *next = NULL; /* the next program line */ + + /* record the next line and destroy this one */ + if (program_line) { + next = program_line->next; + if (program_line->statement) + statement_destroy (program_line->statement); + free (program_line); + } + + /* return the line following */ + return next; +} + +/* + * Program Constructor + * returns: + * ProgramNode* the constructed program + */ +ProgramNode *program_create (void) { + + /* local variables */ + ProgramNode *program; /* new program */ + + /* create and initialise the program */ + program = malloc (sizeof (program)); + program->first = NULL; + + /* return the new program */ + return program; +} + +/* + * Program Destructor + * params: + * ProgramNode* program the doomed program + */ +void program_destroy (ProgramNode *program) { + + /* local variables */ + ProgramLineNode *program_line; /* the program line to destroy */ + + /* destroy the program lines, then the program itself */ + program_line = program->first; + while (program_line) + program_line = program_line_destroy (program_line); + free (program); +} diff --git a/programs/develop/tinybasic-1.0.4/src/tinybasic.c b/programs/develop/tinybasic-1.0.4/src/tinybasic.c new file mode 100644 index 0000000000..355cadc841 --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/src/tinybasic.c @@ -0,0 +1,411 @@ +/* + * Tiny BASIC + * Interpreter and Compiler Main Program + * + * Released as Public Domain by Damian Gareth Walker 2019 + * Created: 04-Aug-2019 + */ + + +/* included headers */ +#include +#include +#include +#include "options.h" +#include "errors.h" +#include "parser.h" +#include "statement.h" +#include "interpret.h" +#include "formatter.h" +#include "generatec.h" + +#ifdef _KOLIBRI + #define KTCC_BIN "/kolibrios/develop/tcc/tcc" + #define KTCC_FLAGS "%s -o %s -lck" +#endif + +/* static variables */ +static char *input_filename = NULL; /* name of the input file */ +static enum { /* action to take with parsed program */ + OUTPUT_INTERPRET, /* interpret the program */ + OUTPUT_LST, /* output a formatted listing */ + OUTPUT_C, /* output a C program */ + OUTPUT_EXE /* output an executable */ +} output = OUTPUT_INTERPRET; +static ErrorHandler *errors; /* universal error handler */ +static LanguageOptions *loptions; /* language options */ + + +/* + * Level 2 Routines + */ + + +/* + * Set line number option + * params: + * char* option the option supplied on the command line + */ +static void set_line_numbers (char *option) { + if (! strncmp ("optional", option, strlen (option))) + loptions->set_line_numbers (loptions, LINE_NUMBERS_OPTIONAL); + else if (! strncmp ("implied", option, strlen (option))) + loptions->set_line_numbers (loptions, LINE_NUMBERS_IMPLIED); + else if (! strncmp ("mandatory", option, strlen (option))) + loptions->set_line_numbers (loptions, LINE_NUMBERS_MANDATORY); + else + errors->set_code (errors, E_BAD_COMMAND_LINE, 0, 0); +} + +/* + * Set line number limit + * params: + * char* option the option supplied on the command line + */ +static void set_line_limit (char *option) { + int limit; /* the limit contained in the option */ + if (sscanf (option, "%d", &limit)) + loptions->set_line_limit (loptions, limit); + else + errors->set_code (errors, E_BAD_COMMAND_LINE, 0, 0); +} + +/* + * Set comment option + * params: + * char* option the option supplied on the command line + */ +static void set_comments (char *option) { + if (! strncmp ("enabled", option, strlen (option))) + loptions->set_comments (loptions, COMMENTS_ENABLED); + else if (! strncmp ("disabled", option, strlen (option))) + loptions->set_comments (loptions, COMMENTS_DISABLED); + else + errors->set_code (errors, E_BAD_COMMAND_LINE, 0, 0); +} + +/* + * Set the output options + * params: + * char* option the option supplied on the command line + */ +static void set_output (char *option) { + if (! strcmp ("lst", option)) + output = OUTPUT_LST; + else if (! strcmp ("c", option)) + output = OUTPUT_C; + else if (! strcmp ("exe", option)) + output = OUTPUT_EXE; + else + errors->set_code (errors, E_BAD_COMMAND_LINE, 0, 0); +} + +/* + * Set the GOSUB stack limit option + * params: + * char* option the option supplied on the command line + */ +static void set_gosub_limit (char *option) { + int limit; /* the limit contained in the option */ + if (sscanf (option, "%d", &limit)) + loptions->set_gosub_limit (loptions, limit); + else + errors->set_code (errors, E_BAD_COMMAND_LINE, 0, 0); +} + + +/* + * Level 1 Routines + */ + + +/* + * Process the command line options + * params: + * int argc number of arguments on the command line + * char** argv the arguments + */ +static void set_options (int argc, char **argv) { + + /* local variables */ + int argn; /* argument number count */ + + /* loop through all parameters */ + for (argn = 1; argn < argc && ! errors->get_code (errors); ++argn) { + + /* scan for line number options */ + if (! strncmp (argv[argn], "-n", 2)) + set_line_numbers (&argv[argn][2]); + else if (! strncmp (argv[argn], "--line-numbers=", 15)) + set_line_numbers (&argv[argn][15]); + + /* scan for line number limit */ + else if (! strncmp (argv[argn], "-N", 2)) + set_line_limit (&argv[argn][2]); + else if (! strncmp (argv[argn], "--line-limit=", 13)) + set_line_limit (&argv[argn][13]); + + /* scan for comment option */ + else if (! strncmp (argv[argn], "-o", 2)) + set_comments (&argv[argn][2]); + else if (! strncmp (argv[argn], "--comments=", 11)) + set_comments (&argv[argn][11]); + + /* scan for output option */ + else if (! strncmp (argv[argn], "-O", 2)) + set_output (&argv[argn][2]); + else if (! strncmp (argv[argn], "--output=", 9)) + set_output (&argv[argn][9]); + + /* scan for gosub stack limit */ + else if (! strncmp (argv[argn], "-g", 2)) + set_gosub_limit (&argv[argn][2]); + else if (! strncmp (argv[argn], "--gosub-limit=", 14)) + set_gosub_limit (&argv[argn][14]); + + /* accept filename */ + else if (! input_filename) + input_filename = argv[argn]; + + /* raise an error upon illegal option */ + else + errors->set_code (errors, E_BAD_COMMAND_LINE, 0, 0); + } +} + +/* + * Output a formatted program listing + * params: + * ProgramNode* program the program to output + */ +static void output_lst (ProgramNode *program) { + + /* local variables */ + FILE *output; /* the output file */ + char *output_filename; /* the output filename */ + Formatter *formatter; /* the formatter object */ + + /* ascertain the output filename */ + output_filename = malloc (strlen (input_filename) + 5); + if (output_filename) { + + /* open the output file */ + sprintf (output_filename, "%s.lst", input_filename); + if ((output = fopen (output_filename, "w"))) { + + /* write to the output file */ + formatter = new_Formatter (errors); + if (formatter) { + formatter->generate (formatter, program); + if (formatter->output) + fprintf (output, "%s", formatter->output); + formatter->destroy (formatter); + } + fclose (output); + } + + /* deal with errors */ + else + errors->set_code (errors, E_FILE_NOT_FOUND, 0, 0); + + /* free the output filename */ + free (output_filename); + } + + /* deal with out of memory error */ + else + errors->set_code (errors, E_MEMORY, 0, 0); +} + +/* + * Output a C source file + * params: + * ProgramNode* program the parsed program + */ +static void output_c (ProgramNode *program) { + + /* local variables */ + FILE *output; /* the output file */ + char *output_filename; /* the output filename */ + CProgram *c_program; /* the C program */ + + /* open the output file */ + output_filename = malloc (strlen (input_filename) + 5); + sprintf (output_filename, "%s.c", input_filename); + if ((output = fopen (output_filename, "w"))) { + + /* write to the output file */ + c_program = new_CProgram (errors, loptions); + if (c_program) { + c_program->generate (c_program, program); + if (c_program->c_output) + fprintf (output, "%s", c_program->c_output); + c_program->destroy (c_program); + } + fclose (output); + } + + /* deal with errors */ + else + errors->set_code (errors, E_FILE_NOT_FOUND, 0, 0); + + /* clean up allocated memory */ + free (output_filename); +} + +/* + * Invoke a compiler to turn a C source file into an executable + * params: + * char* basic_filename The BASIC program's name + */ +static void output_exe (char *command, char *basic_filename) { + + /* local variables */ + char + c_filename[256], /* the name of the C source */ + exe_filename[256], /* the base name of the executable */ + final_command[1024], /* the constructed compiler command */ + *ext, /* position of extension character '.' in filename */ + *src, /* source pointer for string copying */ + *dst; /* destination pointer for string copying */ + + /* work out the C and EXE filenames */ + sprintf (c_filename, "%s.c", basic_filename); + strcpy (exe_filename, basic_filename); + if ((ext = strchr (exe_filename, '.'))) + *ext = '\0'; + else + strcat (exe_filename, ".out"); + +#ifndef _KOLIBRI + /* build the compiler command */ + src = command; + dst = final_command; + while (*src) { + if (! strncmp (src, "$(TARGET)", strlen ("$(TARGET)"))) { + strcpy (dst, exe_filename); + dst += strlen (exe_filename); + src += strlen ("$(TARGET)"); + } else if (! strncmp (src, "$(SOURCE)", strlen ("$(SOURCE)"))) { + strcpy (dst, c_filename); + dst += strlen (c_filename); + src += strlen ("$(SOURCE)"); + } else + *(dst++) = *(src++); + } + *dst = '\0'; + + /* run the compiler command */ + system (final_command); +#else + sprintf(final_command, KTCC_FLAGS, c_filename, exe_filename); + if(!_ksys_exec(KTCC_BIN, final_command)){ + printf(final_command); + }else{ + printf("Bad command: %s %s\n", KTCC_BIN, final_command); + exit(0); + } +#endif +} + + +/* + * Top Level Routine + */ + + +/* + * Main Program + * params: + * int argc number of arguments on the command line + * char** argv the arguments + * returns: + * int any error code from processing/running the program + */ +int main (int argc, char **argv) { + /* local variables */ + FILE *input; /* input file */ + ProgramNode *program; /* the parsed program */ + ErrorCode code; /* error returned */ + Parser *parser; /* parser object */ + Interpreter *interpreter; /* interpreter object */ + char + *error_text, /* error text message */ + *command; /* command for compilation */ + + /* interpret the command line arguments */ + errors = new_ErrorHandler (); + loptions = new_LanguageOptions (); + set_options (argc, argv); + + /* give usage if filename not given */ + if (! input_filename) { + printf ("Usage: tinybas [OPTIONS] INPUT-FILE\n"); + errors->destroy (errors); + loptions->destroy (loptions); + return 0; + } + /* otherwise attempt to open the file */ + if (!(input = fopen (input_filename, "r"))) { + printf ("Error: cannot open file %s\n", input_filename); + errors->destroy (errors); + loptions->destroy (loptions); + return E_FILE_NOT_FOUND; + } + + /* get the parse tree */ + parser = new_Parser (errors, loptions, input); + program = parser->parse (parser); + parser->destroy (parser); + fclose (input); + + /* deal with errors */ + if ((code = errors->get_code (errors))) { + error_text = errors->get_text (errors); + printf ("Parse error: %s\n", error_text); + free (error_text); + loptions->destroy (loptions); + errors->destroy (errors); + return code; + } + + /* perform the desired action */ + switch (output) { + case OUTPUT_INTERPRET: + interpreter = new_Interpreter (errors, loptions); + interpreter->interpret (interpreter, program); + interpreter->destroy (interpreter); + if ((code = errors->get_code (errors))) { + error_text = errors->get_text (errors); + printf ("Runtime error: %s\n", error_text); + free (error_text); + } + break; + case OUTPUT_LST: + output_lst (program); + break; + case OUTPUT_C: + output_c (program); + break; + case OUTPUT_EXE: + + #ifndef _KOLIBRI + if ((command = getenv ("TBEXE"))) { + output_c (program); + output_exe (command, input_filename); + } else { + printf ("TBEXE not set.\n"); + break; + } + #else + output_c (program); + output_exe (NULL, input_filename); + break; + #endif + } + /* clean up and return success */ + program_destroy (program); + loptions->destroy (loptions); + errors->destroy (errors); + exit(0); +} diff --git a/programs/develop/tinybasic-1.0.4/src/token.c b/programs/develop/tinybasic-1.0.4/src/token.c new file mode 100644 index 0000000000..ff15d92a5a --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/src/token.c @@ -0,0 +1,235 @@ +/* + * Tiny BASIC + * Token handling functions + * + * Copyright (C) Damian Walker 2019 + * Created: 15-Aug-2019 + */ + + +/* includes */ +#include +#include +#include +#include "token.h" + + +/* + * Internal data structures + */ + + +/* Private data */ +typedef struct { + TokenClass class; /* class of token */ + int line; /* line on which token was found */ + int pos; /* position within the line on which token was found */ + char *content; /* representation of token */ +} Private; + + +/* + * Internal Data + */ + + +/* Convenience variables */ +static Token *this; /* the token object */ +static Private *data; /* the private data */ + + +/* + * Public methods + */ + + +/* + * Return the class of the token + * params: + * Token* token the token object + * returns: + * TokenClass the class of the token + */ +static TokenClass get_class (Token *token) { + this = token; + data = token->data; + return data->class; +} + +/* + * Return the line on which the token begins + * params: + * Token* token the token object + * returns: + * int the line on which the token begins + */ +static int get_line (Token *token) { + this = token; + data = token->data; + return data->line; +} + +/* + * Return the character position on which the token begins + * params: + * Token* token the token object + * returns: + * int the position on which the token begins + */ +static int get_pos (Token *token) { + this = token; + data = token->data; + return data->pos; +} + +/* + * Return the content of the token begins + * params: + * Token* token the token object + * returns: + * char* the text content of the token + */ +static char *get_content (Token *token) { + this = token; + data = token->data; + return data->content; +} + +/* + * Set the token class + * params: + * Token* token the token to set + * TokenClass class the class + */ +static void set_class (Token *token, TokenClass class) { + this = token; + data = this->data; + data->class = class; +} + +/* + * Set the token line and position + * params: + * Token* token the token to set + * int line the line on which the token began + * int pos the position on which the token began + */ +static void set_line_pos (Token *token, int line, int pos) { + this = token; + data = this->data; + data->line = line; + data->pos = pos;; +} + +/* + * Set the token's text content + * params: + * Token* token the token to alter + * char* content the text content to set + */ +static void set_content (Token *token, char *content) { + this = token; + data = this->data; + if (data->content) + free (data->content); + data->content = malloc (strlen (content) + 1); + strcpy (data->content, content); +} + +/* + * Set all of the values of an existing token in a single function call. + * params: + * Token* token the token to update + * TokenClass class class of token to initialise + * int line line on which the token occurred + * int pos character position on which the token occurred + * char* content the textual content of the token + */ +static void initialise (Token *token, TokenClass class, int line, int pos, + char *content) { + + /* set convenience variables */ + this = token; + data = this->data; + + /* initialise the easy members */ + data->class = class ? class : TOKEN_NONE; + data->line = line ? line : 0; + data->pos = pos ? pos : 0; + + /* initialise the content */ + if (content) + set_content (this, content); +} + +/* + * Token destructor + * params: + * Token* token the doomed token + */ +static void destroy (Token *token) { + if ((this = token)) { + data = this->data; + if (data->content) + free (data->content); + free (data); + free (this); + } +} + + +/* + * Constructors + */ + + +/* + * Token constructor without values to initialise + * returns: + * Token* the created token + */ +Token *new_Token (void) { + + /* create the structure */ + this = malloc (sizeof (Token)); + this->data = data = malloc (sizeof (Private)); + + /* initialise properties */ + data->class = TOKEN_NONE; + data->line = data->pos = 0; + data->content = NULL; + + /* set up methods */ + this->initialise = initialise; + this->get_class = get_class; + this->get_line = get_line; + this->get_pos = get_pos; + this->get_content = get_content; + this->set_class = set_class; + this->set_line_pos = set_line_pos; + this->set_content = set_content; + this->destroy = destroy; + + /* return the created structure */ + return this; +} + +/* + * Token constructor with values to initialise + * params: + * TokenClass class class of token to initialise + * int line line on which the token occurred + * int pos character position on which the token occurred + * char* content the textual content of the token + * returns: + * Token* the created token + */ +Token *new_Token_init (TokenClass class, int line, int pos, char *content) { + + /* create a blank token */ + this = new_Token (); + this->initialise (this, class, line, pos, content); + + /* return the new token */ + return this; +} diff --git a/programs/develop/tinybasic-1.0.4/src/tokeniser.c b/programs/develop/tinybasic-1.0.4/src/tokeniser.c new file mode 100644 index 0000000000..64401a34bb --- /dev/null +++ b/programs/develop/tinybasic-1.0.4/src/tokeniser.c @@ -0,0 +1,602 @@ +/* + * Tiny BASIC + * Tokenisation module + * + * Copyright (C) Damian Gareth Walker 2019 + * Created: 04-Aug-2019 + */ + + +/* included headers */ +#include +#include +#include +#include "token.h" +#include "tokeniser.h" +#include "common.h" + + +/* + * Data definitions + */ + + +/* modes of reading */ +typedef enum { + DEFAULT_MODE, /* we have no idea what's coming */ + COMMENT_MODE, /* reading a comment */ + WORD_MODE, /* reading an identifier or keyword */ + NUMBER_MODE, /* reading a numeric constant */ + LESS_THAN_MODE, /* reading an operator staring with < */ + GREATER_THAN_MODE, /* reading an operator starting with > */ + STRING_LITERAL_MODE, /* reading a string literal */ + UNKNOWN_MODE /* we are lost */ +} Mode; + +/* current state information */ +typedef struct { + Token *token; /* token to return */ + Mode mode; /* current reading mode */ + int ch; /* last-read character */ + char *content; /* content of token under construction */ + int max; /* memory reserved for content */ +} TokeniserState; + +/* Private data */ +typedef struct { + FILE *input; /* the input file */ + int line, /* current line in the input file */ + pos, /* current position on the input line */ + start_line, /* line on which a token started */ + start_pos; /* position on which a token started */ +} Private; + + +/* + * File level variables + */ + + +/* convenience variables */ +static TokenStream *this; /* token stream passed in to public method */ +static Private *data; /* private data for this */ + + +/* + * Level 2 Tokeniser Routines + */ + + +/* + * Read a character and update the position counter + * globals: + * int line current line after character read + * int pos current character position after character read + * params: + * TokeniserState* state current state of the tokeniser + * returns: + * int character just read + */ +static int read_character (TokeniserState *state) { + + int ch; /* character read from stream */ + + /* read the character */ + ch = fgetc (data->input); + + /* update the position and line counters */ + if (ch == '\n') { + ++data->line; + data->pos = 0; + } else { + ++data->pos; + } + + /* return the character */ + return ch; +} + +/* + * Push a character back into the input stream and update position markers + * globals: + * int line line number rolled back + * int pos character position rolled back + * params: + * TokeniserState* state current state of the tokeniser + */ +static void unread_character (TokeniserState *state) { + ungetc (state->ch, data->input); + if (state->ch == '\n') + --data->line; + else + --data->pos; +} + +/* + * Append the last read character to the token content + * params: + * TokeniserState* state current state of the tokeniser + */ +static void store_character (TokeniserState *state) { + + /* variable declarations */ + char *temp; /* temporary pointer to content */ + int length; /* current length of token */ + + /* allocate more memory for the token content if necessary */ + if (strlen (state->content) == state->max - 1) { + temp = state->content; + state->max *= 2; + state->content = malloc (state->max); + strcpy (state->content, temp); + free (temp); + } + + /* now add the character to the token */ + length = strlen (state->content); + state->content [length++] = state->ch; + state->content [length] = '\0'; +} + +/* + * Identify the various recognised symbols + * params: + * int ch the character to identify + * returns: + * TokenClass the token class recognised by the parser + */ +static TokenClass identify_symbol (int ch) { + switch (ch) { + case '+': + return TOKEN_PLUS; + break; + case '-': + return TOKEN_MINUS; + break; + case '*': + return TOKEN_MULTIPLY; + break; + case '/': + return TOKEN_DIVIDE; + break; + case '=': + return TOKEN_EQUAL; + break; + case '(': + return TOKEN_LEFT_PARENTHESIS; + break; + case ')': + return TOKEN_RIGHT_PARENTHESIS; + break; + case ',': + return TOKEN_COMMA; + break; + default: + return TOKEN_SYMBOL; + } +} + +static TokenClass identify_word (char *word) { + if (strlen (word) == 1) + return TOKEN_VARIABLE; + else if (! tinybasic_strcmp (word, "LET")) + return TOKEN_LET; + else if (! tinybasic_strcmp (word, "IF")) + return TOKEN_IF; + else if (! tinybasic_strcmp (word, "THEN")) + return TOKEN_THEN; + else if (! tinybasic_strcmp (word, "GOTO")) + return TOKEN_GOTO; + else if (! tinybasic_strcmp (word, "GOSUB")) + return TOKEN_GOSUB; + else if (! tinybasic_strcmp (word, "RETURN")) + return TOKEN_RETURN; + else if (! tinybasic_strcmp (word, "END")) + return TOKEN_END; + else if (! tinybasic_strcmp (word, "PRINT")) + return TOKEN_PRINT; + else if (! tinybasic_strcmp (word, "INPUT")) + return TOKEN_INPUT; + else if (! tinybasic_strcmp (word, "REM")) + return TOKEN_REM; + else + return TOKEN_WORD; +} + +/* + * Identify compound (multi-character) symbols. + * Also identifies some single-character symbols that can form + * the start of multi-character symbols. + * params: + * char* symbol the symbol to identify + * returns: + * TokenClass the identification + */ +static TokenClass identify_compound_symbol (char *symbol) { + if (! strcmp (symbol, "<>") + || ! strcmp (symbol, "><")) + return TOKEN_UNEQUAL; + else if (! strcmp (symbol, "<")) + return TOKEN_LESSTHAN; + else if (! strcmp (symbol, "<=")) + return TOKEN_LESSOREQUAL; + else if (! strcmp (symbol, ">")) + return TOKEN_GREATERTHAN; + else if (! strcmp (symbol, ">=")) + return TOKEN_GREATEROREQUAL; + else + return TOKEN_SYMBOL; +} + + +/* + * Level 1 Tokeniser Routines + */ + + +/* + * Default mode - deal with character when state is unknown + * globals: + * int line current line in the source file + * int pos current character position in the source + * int start_line line on which the current token started + * int start_pos char pos on which the current token started + * params: + * TokeniserState* state current state of the tokeniser + */ +static void default_mode (TokeniserState *state) { + + /* deal with non-EOL whitespace */ + if (state->ch == ' ' || + state->ch == '\t') { + state->ch = read_character (state); + data->start_line = data->line; + data->start_pos = data->pos; + } + + /* deal with EOL whitespace */ + else if (state->ch == '\n') { + data->start_line = data->line - 1; + data->start_pos = data->pos; + state->token = new_Token_init + (TOKEN_EOL, data->start_line, data->start_pos, state->content); + } + + /* alphabetic characters start a word */ + else if ((state->ch >= 'A' && state->ch <= 'Z') || + (state->ch >= 'a' && state->ch <= 'z')) { + data->start_line = data->line; + data->start_pos = data->pos; + state->mode = WORD_MODE; + } + + /* digits start a number */ + else if (state->ch >= '0' && state->ch <= '9') + state->mode = NUMBER_MODE; + + /* check for tokens starting with less-than (<, <=, <>) */ + else if (state->ch == '<') { + data->start_line = data->line; + data->start_pos = data->pos; + store_character (state); + state->ch = read_character (state); + state->mode = LESS_THAN_MODE; + } + + /* check for tokens starting with greater-than (>, >=) */ + else if (state->ch == '>') { + data->start_line = data->line; + data->start_pos = data->pos; + store_character (state); + state->ch = read_character (state); + state->mode = GREATER_THAN_MODE; + } + + /* deal with other symbol operators */ + else if (strchr ("+-*/=(),", state->ch) != NULL) { + data->start_line = data->line; + data->start_pos = data->pos; + store_character (state); + state->token = new_Token_init (identify_symbol (state->ch), + data->start_line, data->start_pos, state->content); + } + + /* double quotes start a string literal */ + else if (state->ch == '"') { + data->start_line = data->line; + data->start_pos = data->pos; + state->ch = read_character (state); + state->mode = STRING_LITERAL_MODE; + } + + /* detect end of file */ + else if (state->ch == EOF) { + data->start_line = data->line; + data->start_pos = data->pos; + state->token = new_Token_init + (TOKEN_EOF, data->start_line, data->start_pos, state->content); + } + + /* other characters are illegal */ + else { + data->start_line = data->line; + data->start_pos = data->pos; + store_character (state); + state->token = new_Token_init + (TOKEN_ILLEGAL, data->start_line, data->start_pos, state->content); + } +} + +/* + * Word mode - deal with character when building a word token + * globals: + * int start_line line on which the current token started + * int start_pos char pos on which the current token started + * params: + * TokeniserState* state current state of the tokeniser + */ +static void word_mode (TokeniserState *state) { + + /* local variables */ + TokenClass class; /* recognised class of keyword */ + + /* add letters and digits to the token */ + if ((state->ch >= 'A' && state->ch <= 'Z') || + (state->ch >= 'a' && state->ch <= 'z')) { + store_character (state); + state->ch = read_character (state); + } + + /* other characters are pushed back for the next token */ + else { + if (state->ch != EOF) + unread_character (state); + class = identify_word (state->content); + if (class == TOKEN_REM) { + *state->content = '\0'; + state->mode = COMMENT_MODE; + } + else + state->token = new_Token_init + (class, data->start_line, data->start_pos, state->content); + } +} + +/* + * Comment mode - skip till end of line after a REM + * globals: + * int start_line line on which the current token started + * int start_pos char pos on which the current token started + * params: + * TokeniserState* state current state of the tokeniser + */ +static void comment_mode (TokeniserState *state) { + if (state->ch == '\n') + state->mode = DEFAULT_MODE; + else + state->ch = read_character (state); +} + +/* + * Number mode - building a number token (integer only) + * globals: + * int start_line line on which the current token started + * int start_pos char pos on which the current token started + * params: + * TokeniserState* state current state of the tokeniser + */ +static void number_mode (TokeniserState *state) { + + /* add digits to the token */ + if (state->ch >= '0' && state->ch <= '9') { + store_character (state); + state->ch = read_character (state); + } + + /* other characters are pushed back for the next token */ + else { + if (state->ch != EOF) + unread_character (state); + state->token = new_Token_init + (TOKEN_NUMBER, data->start_line, data->start_pos, state->content); + } + +} + +/* + * Less than mode - checking for <> and <= operators + * globals: + * int start_line line on which the current token started + * int start_pos char pos on which the current token started + * params: + * TokeniserState* state current state of the tokeniser + */ +static void less_than_mode (TokeniserState *state) { + if (state->ch == '=' || state->ch == '>') + store_character (state); + else + unread_character (state); + state->token = new_Token_init + (identify_compound_symbol (state->content), data->start_line, + data->start_pos, state->content); +} + +/* + * Greater than mode - checking for >= and >< operators + * globals: + * int start_line line on which the current token started + * int start_pos char pos on which the current token started + * params: + * TokeniserState* state current state of the tokeniser + */ +static void greater_than_mode (TokeniserState *state) { + if (state->ch == '=' || state->ch == '<') + store_character (state); + else + ungetc (state->ch, data->input); + state->token = new_Token_init + (identify_compound_symbol (state->content), data->start_line, + data->start_pos, state->content); +} + +/* + * String literal mode - reading a string + * globals: + * int start_line line on which the current token started + * int start_pos char pos on which the current token started + * params: + * TokeniserState* state current state of the tokeniser + */ +static void string_literal_mode (TokeniserState *state) { + + /* a quote terminates the string */ + if (state->ch == '"') + state->token = new_Token_init + (TOKEN_STRING, data->start_line, data->start_pos, state->content); + + /* a backslash escapes the next character */ + else if (state->ch == '\\') { + state->ch = read_character (state); + store_character (state); + state->ch = read_character (state); + } + + /* EOF generates an error */ + else if (state->ch == EOF) + state->token = new_Token_init + (TOKEN_ILLEGAL, data->start_line, data->start_pos, state->content); + + /* all other characters are part of the string */ + else { + store_character (state); + state->ch = read_character (state); + } +} + + +/* + * Top Level Tokeniser Routines + */ + + +/* + * Get the next token + * params: + * TokenStream* token_stream the token stream being processed + * returns: + * Token* the token built + */ +static Token *next (TokenStream *token_stream) { + + /* local variables */ + TokeniserState state; /* current state of reading */ + Token *return_token; /* token to return */ + + /* initialise */ + this = token_stream; + data = this->data; + state.token = NULL; + state.mode = DEFAULT_MODE; + state.max = 1024; + state.content = malloc (state.max); + *(state.content) = '\0'; + state.ch = read_character (&state); + /* main loop */ + while (state.token == NULL) { + switch (state.mode) { + case DEFAULT_MODE: + + default_mode (&state); + break; + case COMMENT_MODE: + comment_mode (&state); + break; + case WORD_MODE: + word_mode (&state); + break; + case NUMBER_MODE: + number_mode (&state); + break; + case LESS_THAN_MODE: + less_than_mode (&state); + break; + case GREATER_THAN_MODE: + greater_than_mode (&state); + break; + case STRING_LITERAL_MODE: + string_literal_mode (&state); + break; + default: + state.token = new_Token_init + (TOKEN_EOF, data->start_line, data->start_pos, state.content); + state.ch = EOF; /* temporary hack */ + } + } + + /* store token and release state memory */ + return_token = state.token; + free (state.content); + + /* return result */ + return return_token; + +} + +/* + * Getter for the current line number + * paramss: + * TokenStream* token_stream the token stream being processed + * returns: + * int the current line number returned + */ +static int get_line (TokenStream *token_stream) { + this = token_stream; + data = this->data; + return data->line; +} + +/* + * Destructor for a TokenStream + * params: + * TokenStream* token_stream the doomed token stream + */ +static void destroy (TokenStream *token_stream) { + if (token_stream) { + if (token_stream->data) + free (token_stream->data); + free (token_stream); + } +} + + +/* + * Constructors + */ + + +/* + * Constructor for TokenStream + * params: + * FILE* input Input file + * returns: + * TokenStream* The new token stream + */ +TokenStream *new_TokenStream (FILE *input) { + + /* allocate the memory */ + this = malloc (sizeof (TokenStream)); + this->data = data = malloc (sizeof (Private)); + + /* initialise methods */ + this->next = next; + this->get_line = get_line; + this->destroy = destroy; + + /* initialise data */ + data->input = input; + data->line = data->start_line = 1; + data->pos = data->start_pos = 0; + + /* return new token stream */ + return this; +}