Added new port TinyBasic

(An improved version in conjunction with ktcc can generate executable files.)

git-svn-id: svn://kolibrios.org@8733 a494cfbc-eb01-0410-851d-a64ba20cac60
This commit is contained in:
turbocat 2021-05-23 15:55:49 +00:00
parent 4a09257a8f
commit 43795ab11a
34 changed files with 7387 additions and 0 deletions

View File

@ -0,0 +1,7 @@
TinyBASIC 1.0.1
* 'Cannot open file' error now refers to the correct argument.
TinyBASIC 1.0.0
* Initial release

View File

@ -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)

View File

@ -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

View File

@ -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*P<M THEN LET M=10*P
IF L+B<M THEN LET M=L+B
50 PRINT "How many acres do you wish to plant with seed (0-",M,")?"
INPUT S
IF S>M 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

View File

@ -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 G<X THEN IF H<Y THEN PRINT "...to the northwest."
IF G=X THEN IF H<Y THEN PRINT "...to the north."
IF G>X THEN IF H<Y THEN PRINT "...to the northeast."
IF G>X 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 G<X THEN IF H>Y THEN PRINT "...to the southwest."
IF G<X THEN IF H=Y THEN PRINT "...to the west."
IF G=X THEN IF H=Y THEN GOTO 60
IF M>6 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,61 @@
<line> ::=
<number> <statement> <EOL> | <statement> <EOL>
<statement> ::=
"REM" <printable-characters> |
"PRINT" <output-list> |
"IF" <expression> <relational-operator> <expression> "THEN" <statement> |
"GOTO" <expression> |
"INPUT" <variable-list> |
"LET" <variable> "=" <expression> |
"GOSUB" <expression> |
"RETURN" |
"END"
<printable-characters> ::=
<printable-character> | <printable-character> <printable-characters>
<printable-character> ::=
" " .. "~"
<output-list> ::=
<output-term> | <output-term> "," <output-list>
<output-term> ::=
<string> | <expression>
<variable-list> ::=
<variable> | <variable> "," <variable-list>
<expression> ::=
<term> |
<expression> "+" <expression> |
<expression> "-" <expression>
<term> ::=
<factor> |
<term> "*" <term> |
<term> "/" <term>
factor ::=
"-" <factor> |
"+" <factor> |
<variable> |
<number> |
"(" <expression> ")"
<variable> ::=
"A" .. "Z"
<number> ::=
<digit> | <digit> <number>
<digit> ::=
"0" .. "9"
<relational-operator> ::=
"<" | "=" | ">" | "<=" | "<>" | ">="
<string> ::=
'"' <printable-characters> '"'

View File

@ -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

View File

@ -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 a<b, 0 if a==b, 1 if a>b
*/
int tinybasic_strcmp (char *a, char *b);
#endif

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#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 a<b, 0 if a==b, 1 if a>b
*/
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;
}

View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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);
}

View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#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;
}

View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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 <stdio.h>\n");
strcat (include_text, "#include <stdlib.h>\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;
}

View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#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;
}

View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#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);
}

View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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);
}

View File

@ -0,0 +1,235 @@
/*
* Tiny BASIC
* Token handling functions
*
* Copyright (C) Damian Walker 2019
* Created: 15-Aug-2019
*/
/* includes */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

View File

@ -0,0 +1,602 @@
/*
* Tiny BASIC
* Tokenisation module
*
* Copyright (C) Damian Gareth Walker 2019
* Created: 04-Aug-2019
*/
/* included headers */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}