├── .gitignore ├── Makefile ├── embed.c ├── engine ├── basic_errors.c ├── basic_errors.h ├── basic_evaluator.c ├── basic_evaluator.h ├── basic_string.c ├── basic_string.h ├── basic_tokenizer.c ├── basic_tokenizer.h ├── basic_variable.c ├── basic_variable.h └── basic_version.h ├── main.c ├── programs ├── continuation.bas ├── datatest.bas ├── fib.bas ├── for.bas ├── gosubs.bas ├── look.bas ├── ontest.bas ├── sample1.bas └── simplecontinuation.bas ├── readme.txt └── todo.txt /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | sdl.* 3 | *bak.* 4 | *.swp 5 | .DS_Store 6 | *.zip 7 | *.o 8 | a.out 9 | *.exe 10 | blbasic 11 | blbasic_e 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for BleuLlamaBasic 2 | # 3 | # 2011-01 Scott Lawrence 4 | # 5 | 6 | CC ?= gcc 7 | 8 | CFLAGS += -Wall -pedantic -g -ggdb -I engine 9 | 10 | # LDFLAGS += -lreadline 11 | 12 | SRCSCOMMON := \ 13 | engine/basic_errors.c \ 14 | engine/basic_string.c \ 15 | engine/basic_variable.c \ 16 | engine/basic_tokenizer.c \ 17 | engine/basic_evaluator.c 18 | 19 | SRCS := main.c $(SRCSCOMMON) 20 | ESRCS := embed.c $(SRCSCOMMON) 21 | 22 | OBJS := $(SRCS:%.c=%.o) 23 | EOBJS := $(ESRCS:%.c=%.o) 24 | 25 | TARG := blbasic 26 | ETARG := blbasic_e 27 | 28 | all: $(TARG) $(ETARG) 29 | 30 | 31 | $(TARG): $(OBJS) 32 | $(CC) -g -o $@ $(OBJS) $(LDFLAGS) $(LIBS) 33 | 34 | $(ETARG): $(EOBJS) 35 | $(CC) -g -o $@ $(EOBJS) $(LDFLAGS) $(LIBS) 36 | 37 | clean: 38 | rm -rf $(TARG) $(OBJS) $(ETARG) $(EOBJS) 39 | 40 | 41 | test: $(TARG) 42 | ./$(TARG) 43 | 44 | val: $(TARG) 45 | valgrind ./$(TARG) 46 | -------------------------------------------------------------------------------- /embed.c: -------------------------------------------------------------------------------- 1 | /* BleuLlama BASIC 2 | * 3 | * A BASIC interpreter 4 | * Copyright (c) 2011 Scott Lawrence 5 | */ 6 | 7 | /* TODO: 8 | REM command needs to store to end of line 9 | after immediate evaluate() here, we need to dispose of the tree. 10 | deleteLines( bl ); ? 11 | */ 12 | 13 | /* LICENSE: 14 | * 15 | * Copyright (C) 2011 by Scott Lawrence 16 | * 17 | * Permission is hereby granted, free of charge, to any person obtaining a copy 18 | * of this software and associated documentation files (the "Software"), to deal 19 | * in the Software without restriction, including without limitation the rights 20 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | * copies of the Software, and to permit persons to whom the Software is 22 | * furnished to do so, subject to the following conditions: 23 | * 24 | * The above copyright notice and this permission notice shall be included in 25 | * all copies or substantial portions of the Software. 26 | * 27 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 30 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 31 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 32 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 33 | * THE SOFTWARE. 34 | */ 35 | 36 | /* Reference: 37 | * http://www.devili.iki.fi/Computers/Commodore/C64/Programmers_Reference/Chapter_1/page_008.html 38 | * http://www.commodore.ca/manuals/funet/cbm/programming/cbm-basic-tokens.txt 39 | * http://www.c64-wiki.com/index.php/C64-Commands 40 | */ 41 | 42 | #include 43 | #include /* exit */ 44 | #include /* signal catching */ 45 | #include /* strcmp */ 46 | #include /* mkdir on OS X */ 47 | #include /* mkdir on MinGW */ 48 | #include /* for nanosleep */ 49 | 50 | #include "basic_version.h" 51 | #include "basic_errors.h" 52 | #include "basic_tokenizer.h" 53 | #include "basic_evaluator.h" 54 | #include "basic_string.h" 55 | 56 | char *program[] = { 57 | "10 print \"Hello, World!\"", 58 | "20 for a = 0 to 10", 59 | "30 print a", 60 | "40 next a", 61 | "50 end" 62 | }; 63 | 64 | 65 | /* main 66 | * 67 | * do some main stuff. 68 | */ 69 | int main( int argc, char ** argv ) 70 | { 71 | basic_program * bp; 72 | basic_line * bl; 73 | 74 | 75 | printf( "0\n" ); 76 | /* set up our program space */ 77 | bp = newProgram(); 78 | if( !bp ) { 79 | errorReport( kErrorMalloc, 1 ); 80 | return 0; 81 | } 82 | 83 | /* display version info */ 84 | cmd_info( bp, NULL ); 85 | 86 | bl = consumeString( bp, program[0] ); 87 | bl = consumeString( bp, program[1] ); 88 | bl = consumeString( bp, program[2] ); 89 | bl = consumeString( bp, program[3] ); 90 | bl = consumeString( bp, program[4] ); 91 | 92 | /* and run the program, if we should... */ 93 | printf( "Running program\n" ); 94 | runProgram( bp, 0 ); 95 | while( run_poll( bp ) ); 96 | 97 | /* just show access of variables */ 98 | printf( "Variable 'a' is %ld\n", getVariableNumber( bp->variables, "a" )); 99 | 100 | deleteProgram( bp ); 101 | 102 | return 0; 103 | } 104 | -------------------------------------------------------------------------------- /engine/basic_errors.c: -------------------------------------------------------------------------------- 1 | /* basic_errors 2 | * 3 | * BleuLlama BASIC 4 | * Copyright (c) 2011 Scott Lawrence 5 | */ 6 | 7 | /* LICENSE: 8 | * 9 | * Copyright (C) 2011 by Scott Lawrence 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in 19 | * all copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | */ 29 | 30 | 31 | #include 32 | #include "basic_errors.h" 33 | 34 | void errorReportAdditional( int code, int codeMinor, char * additional ) 35 | { 36 | char * msg = "Unknown."; 37 | 38 | switch( code ) 39 | { 40 | case( kErrorNone ): msg = "No error."; break; 41 | case( kErrorMalloc ): msg = "Could not allocate memory."; break; 42 | case( kErrorParams ): msg = "Parameter list incorrect."; break; 43 | case( kErrorParamType ): msg = "A parameter type is incorrect."; break; 44 | case( kErrorFileNotFound ): msg = "File not found."; break; 45 | case( kErrorFileError ): msg = "File IO error."; break; 46 | case( kErrorData ): msg = "Data stream error."; break; 47 | case( kErrorProgramEmpty ): msg = "No program data."; break; 48 | case( kErrorNoLine ): msg = "Unavailable line number."; break; 49 | case( kErrorNonsense ): msg = "Nonsense in BASIC."; break; 50 | case( kErrorGosub ): msg = "GOSUB max depth reached."; break; 51 | case( kErrorReturn ): msg = "RETURN to nowhere."; break; 52 | case( kErrorDirectory ): msg = "Directory is unreadable."; break; 53 | case( kErrorRunning ): msg = "Not a runnable command."; break; 54 | case( kErrorNoOn ): msg = "ON target address not found."; break; 55 | case( kErrorNext ): msg = "For-Next error."; break; 56 | case( kErrorDataRead ): msg = "Out of DATA error."; break; 57 | case( kErrorDBZero ): msg = "Divide by zero error."; break; 58 | 59 | default: 60 | break; 61 | } 62 | 63 | fprintf( stderr, "ERROR %d-%d: %s", -code, codeMinor, msg ); 64 | if( additional ) 65 | { 66 | fprintf( stderr, "(%s)", additional ); 67 | } 68 | fprintf( stderr, "\n" ); 69 | } 70 | 71 | void errorReport( int code, int codeMinor ) 72 | { 73 | errorReportAdditional( code, codeMinor, NULL ); 74 | } 75 | -------------------------------------------------------------------------------- /engine/basic_errors.h: -------------------------------------------------------------------------------- 1 | /* basic_errors 2 | * 3 | * BleuLlama BASIC 4 | * Copyright (c) 2011 Scott Lawrence 5 | */ 6 | 7 | /* LICENSE: 8 | * 9 | * Copyright (C) 2011 by Scott Lawrence 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in 19 | * all copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | */ 29 | 30 | 31 | #define kErrorNone 0 32 | #define kErrorMalloc -1 33 | #define kErrorParams -2 /* not enough params */ 34 | #define kErrorParamType -3 /* a parameter was of incorrect type */ 35 | #define kErrorFileNotFound -4 /* file not found */ 36 | #define kErrorFileError -5 /* other file error */ 37 | #define kErrorData -6 /* data stream error */ 38 | #define kErrorProgramEmpty -7 /* program is empty */ 39 | #define kErrorNoLine -8 /* line number doesn't exist */ 40 | #define kErrorNonsense -9 /* nonsense in BASIC */ 41 | #define kErrorGosub -10 /* GOSUB max depth reached */ 42 | #define kErrorReturn -11 /* RETURN when there's no place to return to*/ 43 | #define kErrorDirectory -12 /* unable to read directory */ 44 | #define kErrorRunning -13 /* not a runnable command */ 45 | #define kErrorNoOn -14 /* ON target list exhausted */ 46 | #define kErrorNext -15 /* NEXT error */ 47 | #define kErrorDataRead -16 /* out of DATA error */ 48 | #define kErrorDBZero -17 /* divide by zero */ 49 | 50 | void errorReportAdditional( int code, int codeMinor, char * additional ); 51 | void errorReport( int code, int codeMinor ); 52 | -------------------------------------------------------------------------------- /engine/basic_evaluator.c: -------------------------------------------------------------------------------- 1 | /* basic_evaluator 2 | * 3 | * BleuLlama BASIC 4 | * Copyright (c) 2011 Scott Lawrence 5 | */ 6 | 7 | /* LICENSE: 8 | * 9 | * Copyright (C) 2011 by Scott Lawrence 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in 19 | * all copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | */ 29 | 30 | 31 | #include 32 | #include /* malloc, free */ 33 | #include /* strlen */ 34 | #include /* tolower */ 35 | 36 | #include /* for directory listing "ls" */ 37 | 38 | #include /* for stat */ 39 | #include /* for directory listing (file sizes) */ 40 | #include /* for stat */ 41 | 42 | #include "basic_evaluator.h" 43 | #include "basic_version.h" 44 | #include "basic_string.h" 45 | #include "basic_errors.h" 46 | 47 | /* ********************************************************************** */ 48 | 49 | 50 | /* valueFromToken 51 | * convert a token to a value 52 | */ 53 | long valueFromToken( basic_token * bt, basic_variable * bv ) 54 | { 55 | long ret = 0; 56 | 57 | if( !bt ) return ret; 58 | 59 | if( bt->token_id == kToken_Number ) { 60 | ret = bt->l_data; 61 | } else if( bt->token_id == kToken_Alpha ) { 62 | if( bv ) ret = getVariableNumber( bv, bt->c_data ); 63 | } else { 64 | ret = 0; 65 | } 66 | 67 | return ret; 68 | } 69 | 70 | /* evaluateStringExpression 71 | * 72 | * evaluate a stringical expressionometric whatsit 73 | */ 74 | void evaluateStringExpression( basic_program * bp, basic_token * bt ) 75 | { 76 | char * destVariable; 77 | char * destContent; 78 | 79 | if( !bp || !bt ) return; 80 | destVariable = bt->c_data; 81 | 82 | if( getVariableType( destVariable ) != kVar_String ) 83 | { 84 | errorReport( kErrorParamType, 99 ); /* intenral error! */ 85 | return; 86 | } 87 | 88 | /* advance to the = */ 89 | bt = bt->next; 90 | if( !bt ) return; 91 | 92 | if( bt->token_id != 0xb2 ) /* = */ 93 | { 94 | errorReport( kErrorParamType, 98 ); /* intenral error! */ 95 | return; 96 | } 97 | 98 | /* okay. now we have expressions to work with... */ 99 | bt = bt->next; 100 | 101 | destContent = (char *) malloc( sizeof( char ) * 2 ); 102 | strcpy( destContent, "" ); 103 | 104 | /* okay, we'll do this the simple way. 105 | * the only things that get appended to strings are numbers, 106 | * other quoted text bits, and variable content */ 107 | 108 | while( bt ) { 109 | if( bt->token_id == kToken_Quote ) { 110 | /* quoted - just append it */ 111 | destContent = appendString( destContent, bt->c_data ); 112 | } 113 | if( bt->token_id == kToken_Alpha ) { 114 | /* alpha - it's a variable */ 115 | if( getVariableType( bt->c_data ) == kVar_String ) 116 | { 117 | destContent = appendString( destContent, getVariableString( bp->variables, bt->c_data )); 118 | } else { 119 | destContent = appendNumber( destContent, getVariableNumber( bp->variables, bt->c_data )); 120 | } 121 | 122 | } 123 | if( bt->token_id == kToken_Number ) { 124 | destContent = appendNumber( destContent,bt->l_data ); 125 | } 126 | 127 | bt = bt->next; 128 | } 129 | 130 | bp->variables = setVariableString( bp->variables, destVariable, destContent ); 131 | } 132 | 133 | /* evaluateExpression 134 | * 135 | * evaluate a functional expressional thingy 136 | */ 137 | void evaluateExpression( basic_program * bp, basic_token * bt ) 138 | { 139 | int op = 0xb2; 140 | char * destVariable; 141 | long value = 0; 142 | 143 | if( !bp || !bt ) return; 144 | 145 | /* set up the destination variable name */ 146 | /* from here, assume for now that it's numerical */ 147 | destVariable = bt->c_data; 148 | 149 | if( getVariableType( destVariable ) == kVar_String ) 150 | { 151 | evaluateStringExpression( bp, bt ); 152 | return; 153 | } 154 | 155 | if( !bt->next ) return; 156 | bt = bt->next; /* should be pointing at an '=' */ 157 | if( !bt->next ) return; 158 | bt = bt->next; /* should be pointing at a value or something */ 159 | 160 | while( bt ) { 161 | /* aa + 162 | * ab - 163 | * ac * 164 | * AD / 165 | * AE ^ 166 | * b2 = 167 | */ 168 | if( (bt->token_id >= 0xaa && bt->token_id <= 0xae) || bt->token_id == 0xb2) { 169 | /* store aside our operation */ 170 | op = bt->token_id; 171 | 172 | } else if( bt->token_id == kToken_Number ) { 173 | long lv = bt->l_data; 174 | 175 | switch( op ) { 176 | case( 0xb2 ): value = lv; break; 177 | case( 0xaa ): value += lv; break; 178 | case( 0xab ): value -= lv; break; 179 | case( 0xac ): value *= lv; break; 180 | case( 0xae ): value ^= lv; break; 181 | case( 0xad ): 182 | if( lv == 0 ) { 183 | errorReport( kErrorDBZero, 100 ); 184 | return; 185 | } 186 | value /= lv; 187 | break; 188 | } 189 | 190 | } else if( bt->token_id == kToken_Alpha ) { 191 | long lv = getVariableNumber( bp->variables, bt->c_data ); 192 | 193 | switch( op ) { 194 | case( 0xb2 ): value = lv; break; 195 | case( 0xaa ): value += lv; break; 196 | case( 0xab ): value -= lv; break; 197 | case( 0xac ): value *= lv; break; 198 | case( 0xae ): value ^= lv; break; 199 | case( 0xad ): 200 | if( lv == 0 ) { 201 | errorReport( kErrorDBZero, 101 ); 202 | return; 203 | } 204 | value /= lv; 205 | break; 206 | } 207 | } 208 | 209 | bt = bt->next; 210 | } 211 | 212 | bp->variables = setVariableNumber( bp->variables, destVariable, value ); 213 | } 214 | 215 | /* evaluateLine 216 | * 217 | * the call that triggers evaluation of the basic perform operation 218 | */ 219 | void evaluateLine( basic_program * bp, basic_line * bl ) 220 | { 221 | int idx = 0; 222 | 223 | if( !bl || !bl->tokens ) { 224 | return; 225 | } 226 | 227 | 228 | /* check for keyword */ 229 | while( token_lut[idx].cmd != NULL ) 230 | { 231 | if( token_lut[idx].token == bl->tokens->token_id ) 232 | { 233 | /* printf( "%s operation.\n", token_lut[idx].cmd ); */ 234 | if( token_lut[idx].perform ) 235 | { 236 | token_lut[idx].perform( bp, bl ); 237 | } 238 | return; 239 | } 240 | idx++; 241 | } 242 | 243 | /* okay. check for variable... */ 244 | 245 | if( bl->tokens->token_id == kToken_Alpha ) 246 | { 247 | evaluateExpression( bp, bl->tokens ); 248 | } 249 | } 250 | 251 | /* 252 | * the structure of all of these entry calls is: 253 | * basic_program * bp the program that this is assocated with 254 | * basic_line * bl the line that triggered calling this entry point 255 | */ 256 | 257 | 258 | 259 | /* stopProgram 260 | * 261 | * halt execution of the program 262 | */ 263 | void stopProgram( basic_program * bp ) 264 | { 265 | clearRuntime( bp ); 266 | } 267 | 268 | 269 | /* run_poll 270 | * 271 | * update for running the program 272 | */ 273 | int run_poll( basic_program * bp ) 274 | { 275 | char tl[512]; 276 | 277 | long thisLineNumber = bp->nextLineNumberToExecute; 278 | if( !bp || !bp->runningLine ) return 0; 279 | 280 | if( bp->traceOn ) 281 | { 282 | printf( ": %s\n", stringizeLine( bp->runningLine, tl, 512 )); 283 | } 284 | 285 | /* perform the line! */ 286 | bp->nextLineNumberToExecute = -1; 287 | evaluateLine( bp, bp->runningLine ); 288 | 289 | /* now, this can change the nextLineNumberToExecute */ 290 | if( bp->nextLineNumberToExecute >= 0 ) { 291 | bp->runningLine = findLineNumber( bp, bp->nextLineNumberToExecute ); 292 | } else { 293 | /* or we continue on our way! */ 294 | if( bp->runningLine->continuation ) { 295 | /* do the continuation */ 296 | bp->runningLine = bp->runningLine->continuation; 297 | 298 | } else { 299 | /* if we're done with continuations... back out of it */ 300 | while( bp->runningLine->continuationParent ) 301 | { 302 | bp->runningLine = bp->runningLine->continuationParent; 303 | } 304 | 305 | /* and advance to the next line */ 306 | bp->runningLine = bp->runningLine->next; 307 | } 308 | } 309 | 310 | if( bp->runningLine ) { 311 | /* there's something more to do... */ 312 | bp->nextLineNumberToExecute = bp->runningLine->lineNumber; 313 | } else { 314 | printf( "\nRun stopped after line %ld.\n", thisLineNumber ); 315 | stopProgram( bp ); 316 | } 317 | 318 | if( bp->runningLine ) return 1; 319 | return 0; 320 | } 321 | 322 | 323 | /* ********************************************************************** */ 324 | 325 | /* new_actual 326 | * 327 | * do the actual work of NEW 328 | */ 329 | void new_actual( basic_program * bp ) 330 | { 331 | if( bp->listing ) 332 | { 333 | deleteLines( bp->listing ); 334 | bp->listing = NULL; 335 | } 336 | bp->runningLine = NULL; 337 | bp->nextLineNumberToExecute = -1; 338 | /* TODO: Free variables too */ 339 | } 340 | 341 | /* getFilePathInDocuments 342 | * 343 | * get a full path for the specified file in the documents folder 344 | * NOTE: returns a thing to be freed 345 | */ 346 | char * getFilePathInDocuments( char * fn ) 347 | { 348 | char * ret; 349 | long fpl = 0; 350 | 351 | if( !fn ) return NULL; 352 | 353 | fpl = strlen( fn ) + strlen( kDocumentsDirectory ) + 2; 354 | ret = (char *)malloc( sizeof( char ) * fpl ); 355 | snprintf( ret, fpl, "%s/%s", kDocumentsDirectory, fn ); 356 | return ret; 357 | } 358 | 359 | 360 | /* load_actual 361 | * 362 | * the routine that does the actual loop/loading from a file 363 | * returns 0 on success 364 | */ 365 | int load_actual( basic_program * bp, char * filename ) 366 | { 367 | FILE * fp; 368 | char buf[1024]; 369 | 370 | fp = fopen( filename, "r" ); 371 | if( !fp ) { 372 | errorReport( kErrorFileError, 100 ); 373 | return -1; 374 | } 375 | 376 | new_actual( bp ); 377 | 378 | while( fgets( buf, 1024, fp )) 379 | { 380 | basic_line * lll = consumeString( bp, buf ); 381 | 382 | /* if it was not absorbed, it has a negative line number */ 383 | if( lll && lll->lineNumber == kNoLineNumber ) { 384 | deleteLines( lll ); 385 | } 386 | } 387 | 388 | fclose( fp ); 389 | printf( "Program loaded from file %s\n", filename ); 390 | return 0; 391 | } 392 | 393 | 394 | /* save_actual 395 | * 396 | * the routine that does the actual loop/saving to a file 397 | */ 398 | void save_actual( basic_program * bp, char * filename ) 399 | { 400 | FILE * fp; 401 | 402 | char buf[1024]; 403 | basic_line * ll; 404 | 405 | if( !bp || !bp->listing ); 406 | 407 | fp = fopen( filename, "w" ); 408 | if( !fp ) { errorReport( kErrorFileError, 103 ); return; } 409 | 410 | ll = bp->listing; 411 | while( ll ) { 412 | stringizeLine( ll, buf, 1024 ); 413 | fprintf( fp, "%s\n", buf ); 414 | ll = ll->next; 415 | } 416 | fclose( fp ); 417 | printf( "Program saved to file %s\n", filename ); 418 | } 419 | 420 | 421 | /* cmd_infosteps 422 | * 423 | * display step info 424 | */ 425 | void cmd_infosteps( basic_program *bp, basic_line * bl ) 426 | { 427 | if( !bp ) 428 | { 429 | printf( "No program loaded.\n" ); 430 | return; 431 | } else { 432 | printf( "%ld steps used\n", programSize( bp )); 433 | } 434 | printf( "\n" ); 435 | } 436 | 437 | 438 | /* rebuild_labels 439 | * 440 | * rebuild the label variables 441 | */ 442 | void rebuild_labels( basic_program * bp ) 443 | { 444 | basic_line * bl; 445 | basic_token * tt; 446 | 447 | if( !bp || !bp->listing ) return; 448 | 449 | bl = bp->listing; 450 | 451 | while( bl ) { 452 | tt = bl->tokens; 453 | /* look for GOSUB 0x8d and GOTO 0x89 */ 454 | if( tt ) 455 | { 456 | if( tt->token_id == 0x0400 ) /* LABEL */ 457 | { 458 | tt = tt->next; 459 | if( tt ) { 460 | if( tt->c_data ) 461 | { 462 | /* first, unprotect the variable */ 463 | bp->variables = protectVariable( bp->variables, tt->c_data, 0 ); 464 | 465 | /* store the value */ 466 | bp->variables = setVariableNumber( bp->variables, tt->c_data, bl->lineNumber ); 467 | 468 | /* protect the value */ 469 | bp->variables = protectVariable( bp->variables, tt->c_data, 1 ); 470 | } 471 | } 472 | } 473 | } 474 | 475 | 476 | bl = bl->next; 477 | } 478 | } 479 | 480 | 481 | /* restore_actual 482 | * 483 | * perform the DATA list rebuild, and setup data pointers 484 | */ 485 | void restore_actual( basic_program * bp, long line ) 486 | { 487 | basic_line * bl = NULL; 488 | basic_token * bt = NULL; 489 | basic_token * last_data_bt = NULL; 490 | 491 | if( !bp ) return; 492 | 493 | bp->firstData = NULL; 494 | bp->currentData = NULL; 495 | 496 | /* Rebuild the list */ 497 | bl = bp->listing; 498 | while( bl ) { 499 | bt = bl->tokens; 500 | if( bt->token_id == 0x83 ) { 501 | while( bt ) { 502 | bt = bt->next; /* advance past DATA or , token */ 503 | if( bt ) { 504 | if( last_data_bt ) { 505 | last_data_bt->nextData = bt; 506 | } 507 | last_data_bt = bt; 508 | 509 | if( !bl->nextData ) { 510 | bl->nextData = bt; 511 | } 512 | 513 | bt = bt->next; 514 | } 515 | } 516 | } 517 | 518 | 519 | /* if there's no head pointer, set it up */ 520 | if( !bp->firstData ) { 521 | bp->firstData = bl->nextData; 522 | } 523 | bl = bl->next; 524 | } 525 | 526 | 527 | /* Set up the currentData pointer to the appropriate place */ 528 | 529 | /* 0 is a special item meaning the first thing on the list */ 530 | if( line == 0 ) { 531 | bp->currentData = bp->firstData; 532 | return; 533 | } 534 | 535 | /* go to the selected place */ 536 | bl = findLineNumber( bp, line ); 537 | if( !bl ) 538 | { 539 | /* line number doesn't exist */ 540 | errorReport( kErrorNoLine, 112 ); 541 | stopProgram( bp ); 542 | } 543 | 544 | /* set the data pointer */ 545 | while( bl ) { 546 | if( bl->nextData ) { 547 | bp->currentData = bl->nextData; 548 | return; 549 | } 550 | bl = bl->next; 551 | } 552 | } 553 | 554 | 555 | /* runProgram 556 | * 557 | * start running a program on the specified line (or the next one) 558 | */ 559 | void runProgram( basic_program * bp, long startline ) 560 | { 561 | basic_line * r; 562 | 563 | if( !bp ) return; 564 | 565 | /* rebuild the labels */ 566 | rebuild_labels( bp ); 567 | 568 | /* set up the DATA list */ 569 | restore_actual( bp, 0 ); 570 | 571 | /* clear for-notes */ 572 | clearVariableAux( bp->variables ); 573 | 574 | /* find the actual line */ 575 | r = findLowestLineNumber( bp, startline ); 576 | 577 | /* and start running! */ 578 | bp->runningLine = r; 579 | if( bp->runningLine ) { 580 | bp->nextLineNumberToExecute = bp->runningLine->lineNumber; 581 | } 582 | } 583 | 584 | 585 | /* goto_actual 586 | * 587 | * the actual stuff necessary to do a goto call 588 | */ 589 | void goto_actual( basic_program * bp, long newLine ) 590 | { 591 | bp->nextLineNumberToExecute = newLine; 592 | 593 | if( !findLineNumber( bp, bp->nextLineNumberToExecute )) 594 | { 595 | errorReport( kErrorNoLine, 112 ); 596 | stopProgram( bp ); 597 | } 598 | } 599 | 600 | 601 | /* gosub_actual 602 | * 603 | * the actual stuff necessary to do a gosub call 604 | */ 605 | void gosub_actual( basic_program * bp, long newLine ) 606 | { 607 | bp->nextLineNumberToExecute = newLine; 608 | 609 | /* make sure we haven't reached max depth for GOSUBs */ 610 | if( bp->nGosubLevels +1 >= kGosubLevels ) { 611 | errorReport( kErrorGosub, 114 ); 612 | stopProgram( bp ); 613 | return; 614 | } 615 | 616 | /* our current line onto the list */ 617 | bp->gosubList[bp->nGosubLevels] = bp->runningLine; 618 | bp->nGosubLevels++; 619 | 620 | /* and jump to the requested line */ 621 | if( !findLineNumber( bp, bp->nextLineNumberToExecute )) 622 | { 623 | errorReport( kErrorNoLine, 115 ); 624 | stopProgram( bp ); 625 | } 626 | } 627 | 628 | 629 | 630 | 631 | /* ********************************************************************** */ 632 | 633 | /* cmd_load 634 | * 635 | * load a file in to memory 636 | * clear out the old one before loading 637 | */ 638 | void cmd_load( basic_program * bp, basic_line * bl ) 639 | { 640 | char * params[] = { "TQE", "TQ,0E", NULL }; 641 | int pm; 642 | int autorun = 0; 643 | 644 | char * fn; 645 | char * fullpath; 646 | 647 | if( !bp || !bl ) return; 648 | 649 | if( bp->runningLine ) { 650 | errorReport( kErrorRunning, 109 ); 651 | return; 652 | } 653 | 654 | pm = argvMatch( bl->tokens, params ); 655 | switch( pm ) { 656 | case( 0 ): /* TQE */ 657 | fn = argvChar( bl, 1 ); 658 | break; 659 | case( 1 ): /* TQ,0E */ 660 | if( argvLong( bl, 3 ) != 1 ) { 661 | errorReport( kErrorParams, 109 ); 662 | return; 663 | } 664 | fn = argvChar( bl, 1 ); 665 | autorun = 1; 666 | break; 667 | 668 | default: 669 | errorReport( pm, 109 ); 670 | return; 671 | } 672 | 673 | 674 | fullpath = getFilePathInDocuments( fn ); 675 | if( !load_actual( bp, fullpath ) ) { 676 | cmd_infosteps( bp, NULL ); 677 | 678 | if( autorun ) { 679 | runProgram( bp, 0 ); 680 | } 681 | 682 | } 683 | free( fullpath ); 684 | } 685 | 686 | 687 | 688 | /* cmd_save 689 | * 690 | * save out the current program to a file 691 | */ 692 | void cmd_save( basic_program * bp, basic_line * bl ) 693 | { 694 | char * params[] = { "TQE", NULL }; 695 | int pm; 696 | 697 | char * fn; 698 | char * fullpath; 699 | 700 | if( !bp || !bl ) return; 701 | 702 | if( bp->runningLine ) { 703 | errorReport( kErrorRunning, 109 ); 704 | return; 705 | } 706 | 707 | if( !bp->listing ) { 708 | errorReport( kErrorProgramEmpty, 104 ); 709 | return; 710 | } 711 | 712 | 713 | pm = argvMatch( bl->tokens, params ); 714 | switch( pm ) { 715 | case( 0 ): /* TQE */ 716 | fn = argvChar( bl, 1 ); 717 | break; 718 | 719 | default: 720 | errorReport( pm, 105 ); 721 | return; 722 | } 723 | 724 | 725 | if( !fn ) 726 | { 727 | errorReport( kErrorParamType, 106 ); 728 | return; 729 | } 730 | 731 | fullpath = getFilePathInDocuments( fn ); 732 | save_actual( bp, fullpath ); 733 | free( fullpath ); 734 | } 735 | 736 | 737 | /* ********************************************************************** */ 738 | 739 | 740 | 741 | /* cmd_new 742 | * 743 | * clear the program memory 744 | */ 745 | void cmd_new( basic_program * bp, basic_line * bl ) 746 | { 747 | char * params[] = { "TE", NULL }; 748 | int pm; 749 | 750 | if( !bp ) return; 751 | 752 | pm = argvMatch( bl->tokens, params ); 753 | if( pm != 0 ) { 754 | errorReport( pm, 105 ); 755 | return; 756 | } 757 | 758 | new_actual( bp ); 759 | } 760 | 761 | 762 | /* renumberParameters 763 | * renumber goto and gosubs 764 | */ 765 | void renumberParameters( basic_program * bp, long oldNumber, long newNumber, int temp ) 766 | { 767 | basic_line * bl; 768 | basic_token * tt; 769 | 770 | if( !bp ) return; 771 | if( oldNumber == newNumber ) return; 772 | 773 | bl = bp->listing; 774 | 775 | while( bl ) { 776 | /* TODO: NOTE: 777 | Need to handle continuations 778 | */ 779 | 780 | tt = bl->tokens; 781 | /* look for GOSUB 0x8d and GOTO 0x89 */ 782 | if( tt ) 783 | { 784 | if( tt->token_id == 0x8d /* GOSUB */ 785 | || tt->token_id == 0x89 /* GOTO */ ) 786 | { 787 | tt = tt->next; 788 | if( tt ) { 789 | if( tt->l_data == oldNumber && bl->temp != temp ) 790 | { 791 | bl->temp = temp; 792 | tt->l_data = newNumber; 793 | } 794 | } 795 | } 796 | 797 | /* TODO: add support for "ON" */ 798 | } 799 | 800 | bl = bl->next; 801 | } 802 | } 803 | 804 | /* cmd_renum 805 | * 806 | * renumber all lines 807 | */ 808 | void cmd_renum( basic_program * bp, basic_line * bl ) 809 | { 810 | char * params[] = { "TE", "T0E", "T00E", "T0,0E", NULL }; 811 | int pm; 812 | 813 | static int renumTemp = 3; 814 | 815 | basic_line * cl; 816 | 817 | long count = 0; 818 | long start = 100; 819 | long step = 10; 820 | long newLineNo; 821 | long oldLineNo; 822 | 823 | if( !bp || !bp->listing ) return; 824 | 825 | pm = argvMatch( bl->tokens, params ); 826 | switch( pm ) { 827 | case( 0 ): /* TE */ 828 | start = 100; 829 | step = 10; 830 | break; 831 | 832 | case( 1 ): /* T0E */ 833 | start = argvLong( bl, 1 ); 834 | break; 835 | 836 | case( 2 ): /* T00E */ 837 | start = argvLong( bl, 1 ); 838 | step = argvLong( bl, 2 ); 839 | break; 840 | 841 | case( 3 ): /* T0,0E */ 842 | start = argvLong( bl, 1 ); 843 | step = argvLong( bl, 3 ); 844 | break; 845 | 846 | default: 847 | errorReport( pm, 109 ); 848 | return; 849 | } 850 | 851 | /* make sure our numbers make sense */ 852 | if( start <= 0 || step < 1 ) { 853 | errorReport( kErrorParams, 108 ); 854 | return; 855 | } 856 | 857 | renumTemp++; /* so that we know which have been renumbered */ 858 | 859 | newLineNo = start; 860 | cl = bp->listing; 861 | while( cl ) { 862 | oldLineNo = cl->lineNumber; 863 | cl->lineNumber = newLineNo; 864 | 865 | renumberParameters( bp, oldLineNo, newLineNo, renumTemp ); 866 | 867 | count++; 868 | 869 | newLineNo += step; 870 | cl = cl->next; 871 | } 872 | printf( "%ld lines renumbered.\n", count ); 873 | } 874 | 875 | /* cmd_list 876 | * 877 | * display a program listing 878 | */ 879 | void cmd_list( basic_program * bp, basic_line * bl ) 880 | { 881 | char * params[] = { "TE", "T0E", "T0-E", "T-0E", "T00E", "T0-0E", NULL }; 882 | int pm; 883 | 884 | long min = -1; 885 | long max = -1; 886 | int showThis = 1; 887 | 888 | char buf[1024]; 889 | basic_line * ll; 890 | 891 | if( !bp || !bp->listing ); 892 | 893 | 894 | pm = argvMatch( bl->tokens, params ); 895 | switch( pm ) { 896 | case( 0 ): /* TE */ 897 | min = max = -1; 898 | break; 899 | 900 | case( 1 ): /* T0E */ 901 | min = max = argvLong( bl, 1 ); 902 | break; 903 | 904 | case( 2 ): /* T0-E */ 905 | min = argvLong( bl, 1 ); 906 | break; 907 | 908 | case( 3 ): /* T-0E */ 909 | max = argvLong( bl, 2 ); 910 | break; 911 | 912 | case( 4 ): /* T00E */ 913 | min = argvLong( bl, 1 ); 914 | max = argvLong( bl, 2 ); 915 | break; 916 | 917 | case( 5 ): /* T0-0E */ 918 | min = argvLong( bl, 1 ); 919 | max = argvLong( bl, 3 ); 920 | break; 921 | 922 | default: 923 | errorReport( pm, 109 ); 924 | return; 925 | } 926 | 927 | ll = bp->listing; 928 | while( ll ) { 929 | showThis = 1; 930 | if( min > 0 ) { 931 | if( ll->lineNumber < min ) showThis = 0; 932 | } 933 | if( max > 0 ) { 934 | if( ll->lineNumber > max ) showThis = 0; 935 | } 936 | 937 | if( showThis ) { 938 | stringizeLine( ll, buf, 1024 ); 939 | printf( "%s\n", buf ); 940 | } 941 | 942 | ll = ll->next; 943 | } 944 | printf( "\n" ); 945 | } 946 | 947 | 948 | /* cmd_head 949 | * 950 | * print out the first 5 lines of a program to see if we want to load it 951 | */ 952 | void cmd_head( basic_program * bp, basic_line * bl ) 953 | { 954 | char * params[] = { "TE", NULL }; 955 | int pm; 956 | 957 | char buf[1024]; 958 | int lineCounter = 0; 959 | FILE * fp; 960 | char * fn; 961 | char * fullpath; 962 | 963 | /* determine the file to check. */ 964 | pm = argvMatch( bl->tokens, params ); 965 | switch( pm ) { 966 | case( 0 ): 967 | fn = argvChar( bl, 1 ); 968 | break; 969 | default: 970 | errorReport( pm, 201 ); 971 | return; 972 | } 973 | 974 | 975 | if( !fn ) 976 | { 977 | errorReport( kErrorParamType, 202 ); 978 | return; 979 | } 980 | 981 | /* get the full path in the documents folder */ 982 | fullpath = getFilePathInDocuments( fn ); 983 | fp = fopen( fullpath, "r" ); 984 | 985 | /* make sure it is readable */ 986 | if( !fp ) { 987 | errorReport( kErrorFileNotFound, 203 ); 988 | free( fullpath ); 989 | return; 990 | } 991 | 992 | /* print out the first 5 lines. */ 993 | printf( "%s:\n", fn ); 994 | while( fgets( buf, 1024, fp ) && lineCounter < 5 ) 995 | { 996 | printf( "%02d: %s", lineCounter, buf ); 997 | lineCounter++; 998 | } 999 | fclose( fp ); 1000 | if( lineCounter >= 5 ) printf( "..." ); 1001 | printf( "\n" ); 1002 | 1003 | free( fullpath ); 1004 | } 1005 | 1006 | 1007 | /* cmd_files 1008 | * 1009 | * listing of the available documents 1010 | */ 1011 | void cmd_files( basic_program * bp, basic_line * bl ) 1012 | { 1013 | char * params[] = { "TE", NULL }; 1014 | int pm; 1015 | 1016 | char * directory = kDocumentsDirectory; 1017 | DIR *dfp; 1018 | struct stat st; 1019 | struct dirent *de; 1020 | 1021 | pm = argvMatch( bl->tokens, params ); 1022 | if( pm != 0 ) { 1023 | errorReport( pm, 201 ); 1024 | return; 1025 | } 1026 | 1027 | /* do the listing... */ 1028 | dfp = opendir( directory ); 1029 | 1030 | if( !dfp ) { 1031 | errorReport( kErrorDirectory, 201 ); 1032 | return; 1033 | } 1034 | 1035 | while( (de = readdir( dfp )) ) 1036 | { 1037 | int strsz = strlen( directory ) + strlen( de->d_name ) + 5; 1038 | char * fullpath = (char *) malloc( sizeof( char ) * strsz ); 1039 | 1040 | snprintf( fullpath, strsz, "%s/%s", directory, de->d_name ); 1041 | 1042 | stat( fullpath, &st ); 1043 | if( st.st_mode & S_IFREG ) 1044 | { 1045 | #ifdef NEVER 1046 | /* sinclair style */ 1047 | printf( " %-15s : %ld bytes\n", de->d_name, (long) st.st_size ); 1048 | #endif 1049 | 1050 | /* commodore 64 style */ 1051 | printf( " %-8ld \"%s\"\n", (long)st.st_size, de->d_name ); 1052 | } 1053 | } 1054 | closedir( dfp ); 1055 | } 1056 | 1057 | 1058 | 1059 | /* cmd_help 1060 | * 1061 | * print out a list of available commands 1062 | */ 1063 | void cmd_help( basic_program * bp, basic_line * bl ) 1064 | { 1065 | char * params[] = { "TE", NULL }; 1066 | int pm; 1067 | int pos = 0; 1068 | 1069 | 1070 | pm = argvMatch( bl->tokens, params ); 1071 | switch( pm ) { 1072 | case( 0 ): 1073 | break; 1074 | 1075 | default: 1076 | errorReport( pm, 201 ); 1077 | return; 1078 | } 1079 | 1080 | while( token_lut[pos].cmd != NULL ) 1081 | { 1082 | if( token_lut[pos].helpText ) { 1083 | printf( "%10s .. %s\n", token_lut[pos].cmd, token_lut[pos].helpText ); 1084 | } 1085 | pos++; 1086 | } 1087 | 1088 | } 1089 | 1090 | 1091 | 1092 | /* cmd_info 1093 | * 1094 | * Display statistics about the system 1095 | */ 1096 | void cmd_info( basic_program * bp, basic_line * bl ) 1097 | { 1098 | printf( "\n" ); 1099 | printf( "BleuLlama BASIC version %s (%s)\n", BASIC_VERSION, BASIC_BUILDNAME ); 1100 | printf( "Copyright 2011 by Scott Lawrence\n" ); 1101 | 1102 | cmd_infosteps( bp, bl ); 1103 | } 1104 | 1105 | void cmd_bye( basic_program * bp, basic_line * bl ) 1106 | { 1107 | printf( "Exiting...\n" ); 1108 | if( bp ) bp->exitNow = 1; 1109 | } 1110 | 1111 | 1112 | /* ********************************************************************** */ 1113 | 1114 | 1115 | /* cmd_end 1116 | * 1117 | * terminate program running 1118 | */ 1119 | void cmd_end( basic_program * bp, basic_line * bl ) 1120 | { 1121 | char * params[] = { "TE", NULL }; 1122 | int pm; 1123 | 1124 | if( !bp ) return; 1125 | 1126 | pm = argvMatch( bl->tokens, params ); 1127 | if( pm <0 ) { 1128 | errorReport( pm, 110 ); 1129 | } 1130 | 1131 | stopProgram( bp ); 1132 | } 1133 | 1134 | 1135 | /* cmd_run 1136 | * 1137 | * start execution of the program 1138 | */ 1139 | void cmd_run( basic_program * bp, basic_line * bl ) 1140 | { 1141 | char * params[] = { "TE", "T0E", NULL }; 1142 | int pm; 1143 | 1144 | long startLine = 0; 1145 | 1146 | if( !bp ) return; 1147 | 1148 | pm = argvMatch( bl->tokens, params ); 1149 | switch( pm ) { 1150 | case( 0 ): 1151 | startLine = 0; 1152 | break; 1153 | 1154 | case( 1 ): 1155 | startLine = argvLong( bl, 1 ); 1156 | break; 1157 | 1158 | default: 1159 | errorReport( pm, 110 ); 1160 | break; 1161 | } 1162 | 1163 | /* startLine contains the lowest line number to start on */ 1164 | runProgram( bp, startLine ); 1165 | } 1166 | 1167 | 1168 | /* cmd_goto 1169 | * 1170 | * move execution elsewhere 1171 | */ 1172 | void cmd_goto( basic_program * bp, basic_line * bl ) 1173 | { 1174 | char * params[] = { "T0E", "TAE", NULL }; 1175 | int pm; 1176 | long newLine; 1177 | 1178 | if( !bp ) return; 1179 | 1180 | pm = argvMatch( bl->tokens, params ); 1181 | switch( pm ) { 1182 | case( 0 ): 1183 | newLine = argvLong( bl, 1 ); 1184 | break; 1185 | 1186 | case( 1 ): 1187 | newLine = getVariableNumber( bp->variables, argvChar( bl, 1 )); 1188 | break; 1189 | 1190 | default: 1191 | errorReport( kErrorParams, 111 ); 1192 | stopProgram( bp ); 1193 | return; 1194 | } 1195 | 1196 | goto_actual( bp, newLine ); 1197 | } 1198 | 1199 | 1200 | /* cmd_gosub 1201 | * 1202 | * call a subroutine 1203 | */ 1204 | void cmd_gosub( basic_program * bp, basic_line * bl ) 1205 | { 1206 | char * params[] = { "T0E", "TAE", NULL }; 1207 | int pm; 1208 | long newLine; 1209 | 1210 | if( !bp ) return; 1211 | 1212 | pm = argvMatch( bl->tokens, params ); 1213 | switch( pm ) { 1214 | case( 0 ): 1215 | newLine = argvLong( bl, 1 ); 1216 | break; 1217 | 1218 | case( 1 ): 1219 | newLine = getVariableNumber( bp->variables, argvChar( bl, 1 )); 1220 | break; 1221 | 1222 | default: 1223 | errorReport( pm, 113 ); 1224 | stopProgram( bp ); 1225 | return; 1226 | } 1227 | 1228 | gosub_actual( bp, newLine ); 1229 | } 1230 | 1231 | 1232 | /* cmd_on 1233 | * 1234 | * ON x GOTO 1,2,3 1235 | * ON x GOSUB 1,2,3 1236 | */ 1237 | void cmd_on( basic_program * bp, basic_line * bl ) 1238 | { 1239 | basic_token * toList; 1240 | char * params[] = { "TAT", "TA0", NULL }; 1241 | int pm; 1242 | int tosub; /* goto or gosub */ 1243 | long userIdx; 1244 | long toLoc = 0; 1245 | 1246 | 1247 | pm = argvMatch( bl->tokens, params ); 1248 | switch( pm ) { 1249 | case( 0 ): 1250 | /* ON x GOTO Y,Z,A */ 1251 | /* ON x GOSUB Y,Z,A */ 1252 | userIdx = valueFromToken( argvToken( bl, 1 ), bp->variables ); 1253 | 1254 | tosub = argvTokenID( bl, 2 ); 1255 | 1256 | if( tosub == 0x89 ) { 1257 | /* GOTO */ 1258 | } else if( tosub == 0x8d ){ 1259 | /* GOSUB */ 1260 | } else { 1261 | /* NOPE! */ 1262 | errorReport( kErrorParams, 140 ); 1263 | return; 1264 | } 1265 | toList = argvToken( bl, 3 ); 1266 | 1267 | 1268 | break; 1269 | 1270 | case( 1 ): 1271 | /* ON x Y,Z,A */ 1272 | userIdx = valueFromToken( argvToken( bl, 1 ), bp->variables ); 1273 | tosub = 0x89; /* force GOTO */ 1274 | toList = argvToken( bl, 2 ); 1275 | break; 1276 | 1277 | default: 1278 | /* ERROR */ 1279 | errorReport( pm, 140 ); 1280 | stopProgram( bp ); 1281 | return; 1282 | } 1283 | 1284 | userIdx -= 1; 1285 | 1286 | while( toList ) { 1287 | /* where we're going to for this element */ 1288 | toLoc = valueFromToken( toList, bp->variables ); 1289 | 1290 | if( userIdx <= 0 ) { 1291 | break; 1292 | } 1293 | 1294 | userIdx--; 1295 | 1296 | /* advance to the next one - ignore the , tokens. */ 1297 | /* don't even bother checking them. */ 1298 | toList = toList->next; 1299 | if( toList ) toList = toList->next; 1300 | } 1301 | 1302 | if( toList == NULL || userIdx != 0 ) { 1303 | /* past the end or before the beginning */ 1304 | /* 1305 | errorReport( kErrorNoOn, 140 ); 1306 | stopProgram( bp ); 1307 | */ 1308 | /* just bail out... it's not an error */ 1309 | return; 1310 | } 1311 | 1312 | if( tosub == 0x89 ) { /* goto */ 1313 | goto_actual( bp, toLoc ); 1314 | } else if( tosub == 0x8d ){ /* gosub */ 1315 | gosub_actual( bp, toLoc ); 1316 | } 1317 | } 1318 | 1319 | 1320 | /* cmd_return 1321 | * 1322 | * resume from a gosub 1323 | */ 1324 | void cmd_return( basic_program * bp, basic_line * bl ) 1325 | { 1326 | char * params[] = { "TE", NULL }; 1327 | int pm; 1328 | 1329 | if( !bp || !bl ) return; 1330 | 1331 | pm = argvMatch( bl->tokens, params ); 1332 | if( pm != 0 ) { 1333 | errorReport( pm, 116 ); 1334 | stopProgram( bp ); 1335 | return; 1336 | } 1337 | 1338 | /* make sure we have someplace to go */ 1339 | if( bp->nGosubLevels == 0 ) { 1340 | errorReport( kErrorReturn, 117 ); 1341 | stopProgram( bp ); 1342 | return; 1343 | } 1344 | 1345 | /* restore our location */ 1346 | bp->nGosubLevels --; 1347 | bp->runningLine = bp->gosubList[ bp->nGosubLevels]; 1348 | bp->nextLineNumberToExecute = -1; /* just in case */ 1349 | } 1350 | 1351 | 1352 | /* cmd_for 1353 | * 1354 | * iterate through a variable 1355 | * 1356 | * FOR VAR = VALUE TO VALUE 1357 | * FOR VAR = VALUE TO VALUE STEP VALUE 1358 | * where VALUE is an immediate number or variable 1359 | */ 1360 | void cmd_for( basic_program * bp, basic_line * bl ) 1361 | { 1362 | basic_variable * bv; 1363 | int argc; 1364 | 1365 | char * bvar; 1366 | long curr = 0; 1367 | long start = 0; 1368 | long end = 0; 1369 | long step = 1; 1370 | 1371 | /* TO 0xa4 STEP 0xa9 */ 1372 | 1373 | /* 01234567 */ 1374 | char * params[] = { "TA=ATA", 1375 | "TA=AT0", 1376 | "TA=0TA", 1377 | "TA=0T0", 1378 | /* ^TO 0xA4 */ 1379 | /* ^STEP 0xA9 */ 1380 | NULL }; 1381 | int pm; 1382 | 1383 | if( !bp || !bl ) return; 1384 | 1385 | argc = lineArgc( bl ); 1386 | if( argc != 6 && argc != 8 ) { 1387 | errorReport( kErrorParams, 115 ); 1388 | return; 1389 | } 1390 | 1391 | pm = argvMatch( bl->tokens, params ); 1392 | switch( pm ){ 1393 | case( 0 ): 1394 | case( 1 ): 1395 | case( 2 ): 1396 | case( 3 ): 1397 | /* 0: FOR 1398 | * 1: (var) 1399 | * 2: = 1400 | * 3: (start) 1401 | * 4: TO 1402 | * 5: (end) 1403 | * 6: STEP (optional) 1404 | * 7: (step) (optional) 1405 | */ 1406 | bvar = argvChar( bl, 1 ); 1407 | 1408 | start = valueFromToken( argvToken( bl, 3), bp->variables ); 1409 | 1410 | if( argvTokenID(bl, 4) != 0xA4 ) { 1411 | errorReport( kErrorParams, 115 ); 1412 | return; 1413 | } 1414 | end = valueFromToken( argvToken( bl, 5), bp->variables ); 1415 | 1416 | if( argc == 8 ) { 1417 | if( argvTokenID( bl, 6 ) != 0xa9 ) { 1418 | errorReport( kErrorParams, 115 ); 1419 | return; 1420 | } 1421 | 1422 | step = valueFromToken( argvToken( bl, 7 ), bp->variables ); 1423 | } 1424 | break; 1425 | 1426 | case( 4 ): 1427 | default: 1428 | errorReport( pm, 117 ); 1429 | stopProgram( bp ); 1430 | } 1431 | 1432 | 1433 | /* set the variable->forStartLine to this line */ 1434 | 1435 | /* check to see if we're doing this for the first time through */ 1436 | bv = findVariable( bp->variables, bvar ); 1437 | if( !bv ) 1438 | { 1439 | /* it doesn't exist. make it first. */ 1440 | bp->variables = setVariableNumber( bp->variables, bvar, start ); 1441 | /* we're obviously in the first time through now, but re=find it anyway */ 1442 | bv = findVariable( bp->variables, bvar ); 1443 | } 1444 | 1445 | /* okay, now we check bv */ 1446 | if( bv->forStartLine ) { 1447 | /* we've been through here, adjust things */ 1448 | curr = getVariableNumber( bp->variables, bvar ); 1449 | curr += step; 1450 | bp->variables = setVariableNumber( bp->variables, bvar, curr ); 1451 | } else { 1452 | /* start it! */ 1453 | bp->variables = setVariableNumber( bp->variables, bvar, start ); 1454 | bv->forStartLine = bl->lineNumber; 1455 | } 1456 | 1457 | if( curr == end ) { 1458 | /* end */ 1459 | bv->forStartLine = 0; 1460 | 1461 | /* we fall out of the loop at the NEXT */ 1462 | } 1463 | } 1464 | 1465 | 1466 | /* cmd_next 1467 | * 1468 | * end of a FOR block 1469 | */ 1470 | void cmd_next( basic_program * bp, basic_line * bl ) 1471 | { 1472 | basic_variable * bv; 1473 | char * bvar; 1474 | char * params[] = { "TAE", NULL }; 1475 | int pm; 1476 | 1477 | if( !bp || !bl ) return; 1478 | 1479 | pm = argvMatch( bl->tokens, params ); 1480 | if( pm != 0 ) { 1481 | errorReport( pm, 116 ); 1482 | stopProgram( bp ); 1483 | return; 1484 | } 1485 | 1486 | /* okay, now pull the variable name, and set the */ 1487 | bvar = argvChar( bl, 1 ); 1488 | bv = findVariable( bp->variables, bvar ); 1489 | if( !bv ) { 1490 | errorReport( kErrorNext, 117 ); 1491 | stopProgram( bp ); 1492 | return; 1493 | } 1494 | 1495 | if( bv->forStartLine != 0 ) { 1496 | goto_actual( bp, bv->forStartLine ); 1497 | } 1498 | } 1499 | 1500 | 1501 | /* cmd_if 1502 | * 1503 | * conditionally do stuff 1504 | * IF v = w THEN x -- TI 1505 | * IF v = w THEN GOTO x -- Sinclair 1506 | * IF v = w THEN GOSUB x 1507 | * 0 1 2 3 4 5 6 1508 | * 1509 | * ATARI basic seems to have no ELSE 1510 | * TI basic only allows GOTOs 1511 | */ 1512 | void cmd_if( basic_program * bp, basic_line * bl ) 1513 | { 1514 | char * params[] = { 1515 | "TAT0T0E", /* IF a ?= 30 THEN 140 */ 1516 | "TAT0TAE", /* IF a ?= 30 THEN foobar */ 1517 | "TATAT0E", /* IF a ?= b THEN 140 */ 1518 | "TATATAE", /* IF a ?= b THEN foobar */ 1519 | 1520 | "TA=0T0E", /* IF a = 30 THEN 140 */ 1521 | "TA=0TAE", /* IF a = 30 THEN foobar */ 1522 | "TA=AT0E", /* IF a = b THEN 140 */ 1523 | "TA=ATAE", /* IF a = b THEN foobar */ 1524 | 1525 | "TAT0TT0E", /* IF a ?= 30 THEN GOTO/GOSUB 140 */ 1526 | "TAT0TTAE", /* IF a ?= 30 THEN GOTO/GOSUB foobar */ 1527 | "TATATT0E", /* IF a ?= b THEN GOTO/GOSUB 140 */ 1528 | "TATATTAE", /* IF a ?= b THEN GOTO/GOSUB foobar */ 1529 | 1530 | "TA=0TT0E", /* IF a = 30 THEN GOTO/GOSUB 140 */ 1531 | "TA=0TTAE", /* IF a = 30 THEN GOTO/GOSUB foobar */ 1532 | "TA=ATT0E", /* IF a = b THEN GOTO/GOSUB 140 */ 1533 | "TA=ATTAE", /* IF a = b THEN GOTO/GOSUB foobar */ 1534 | 1535 | /* ^ ^-comparison 1536 | IF 1537 | */ 1538 | NULL }; 1539 | int pm; 1540 | 1541 | long testValue = 0; 1542 | int comparisonTokenID = 0; 1543 | long compareValue = 0; 1544 | int isThen = 0; 1545 | int isGoto = 1; 1546 | long destLine = 0; 1547 | 1548 | int trigger = 0; 1549 | 1550 | if( !bp ) return; 1551 | 1552 | pm = argvMatch( bl->tokens, params ); 1553 | switch( pm ) { 1554 | case( 0 ): /* IF a ?= 30 THEN 140 */ 1555 | case( 1 ): /* IF a ?= 30 THEN label */ 1556 | case( 2 ): /* IF a ?= b THEN 140 */ 1557 | case( 3 ): /* IF a ?= b THEN label */ 1558 | case( 4 ): /* IF a = 30 THEN 140 */ 1559 | case( 5 ): /* IF a = 30 THEN label */ 1560 | case( 6 ): /* IF a = b THEN 140 */ 1561 | case( 7 ): /* IF a = b THEN label */ 1562 | testValue = valueFromToken( argvToken( bl, 1 ), bp->variables ); 1563 | comparisonTokenID = argvTokenID( bl, 2 ); 1564 | compareValue = valueFromToken( argvToken( bl, 3 ), bp->variables ); 1565 | if( argvTokenID( bl, 4 ) == 0xa7 ) isThen = 1; 1566 | isGoto = 1; 1567 | destLine = valueFromToken( argvToken( bl, 5 ), bp->variables ); 1568 | break; 1569 | 1570 | case( 8 ): /* IF a ?= 30 THEN GOTO/GOSUB 140 */ 1571 | case( 9 ): /* IF a ?= 30 THEN GOTO/GOSUB label */ 1572 | case( 10 ): /* IF a ?= b THEN GOTO/GOSUB 140 */ 1573 | case( 11 ): /* IF a ?= b THEN GOTO/GOSUB label */ 1574 | case( 12 ): /* IF a = 30 THEN GOTO/GOSUB 140 */ 1575 | case( 13 ): /* IF a = 30 THEN GOTO/GOSUB label */ 1576 | case( 14 ): /* IF a = b THEN GOTO/GOSUB 140 */ 1577 | case( 15 ): /* IF a = b THEN GOTO/GOSUB label */ 1578 | testValue = valueFromToken( argvToken( bl, 1 ), bp->variables ); 1579 | comparisonTokenID = argvTokenID( bl, 2 ); 1580 | compareValue = valueFromToken( argvToken( bl, 3 ), bp->variables ); 1581 | if( argvTokenID( bl, 4 ) == 0xa7 ) isThen = 1; 1582 | if( argvTokenID( bl, 5 ) == 0x8d ) isGoto = 0; /* gosub is 8d goto is 89 */ 1583 | destLine = valueFromToken( argvToken( bl, 6 ), bp->variables ); 1584 | break; 1585 | 1586 | default: 1587 | errorReport( pm, 116 ); 1588 | return; 1589 | } 1590 | 1591 | if( !isThen ) { 1592 | errorReport( kErrorParams, 117 ); 1593 | return; 1594 | } 1595 | 1596 | trigger = 0; 1597 | switch( comparisonTokenID ) { 1598 | case( 0x0507 ): if( testValue <= compareValue ) trigger = 1; break; 1599 | case( 0x0508 ): if( testValue >= compareValue ) trigger = 1; break; 1600 | case( 0x0509 ): if( testValue != compareValue ) trigger = 1; break; 1601 | 1602 | case( 0x00b1 ): if( testValue > compareValue ) trigger = 1; break; 1603 | case( 0x00b2 ): if( testValue == compareValue ) trigger = 1; break; 1604 | case( 0x00b3 ): if( testValue < compareValue ) trigger = 1; break; 1605 | default: 1606 | errorReport( kErrorParams, 118 ); 1607 | return; 1608 | } 1609 | 1610 | if( !trigger ) return; 1611 | 1612 | if( isGoto ) { 1613 | goto_actual( bp, destLine ); 1614 | } else { 1615 | gosub_actual( bp, destLine ); 1616 | } 1617 | } 1618 | 1619 | 1620 | /* cmd_tron 1621 | * 1622 | * (debug and) trace on 1623 | */ 1624 | void cmd_tron( basic_program * bp, basic_line * bl ) 1625 | { 1626 | char * params[] = { "TE", NULL }; 1627 | int pm; 1628 | 1629 | if( !bp ) return; 1630 | 1631 | pm = argvMatch( bl->tokens, params ); 1632 | if( pm != 0 ) { 1633 | errorReport( pm, 116 ); 1634 | stopProgram( bp ); 1635 | return; 1636 | } 1637 | 1638 | bp->traceOn = 1; 1639 | printf( "Trace on.\n" ); 1640 | } 1641 | 1642 | 1643 | /* cmd_troff 1644 | * 1645 | * (debug and) trace off 1646 | */ 1647 | void cmd_troff( basic_program * bp, basic_line * bl ) 1648 | { 1649 | char * params[] = { "TE", NULL }; 1650 | int pm; 1651 | 1652 | if( !bp ) return; 1653 | 1654 | pm = argvMatch( bl->tokens, params ); 1655 | if( pm != 0 ) { 1656 | errorReport( pm, 116 ); 1657 | stopProgram( bp ); 1658 | return; 1659 | } 1660 | 1661 | bp->traceOn = 0; 1662 | printf( "Trace off.\n" ); 1663 | } 1664 | 1665 | 1666 | /* ********************************************************************** */ 1667 | 1668 | /* cmd_print 1669 | * 1670 | * print stuff to the console 1671 | */ 1672 | void cmd_print( basic_program * bp, basic_line * bl ) 1673 | { 1674 | basic_token * bt; 1675 | int addNewline = 1; 1676 | int vt; 1677 | 1678 | if( !bp || !bl ) return; 1679 | 1680 | bt = bl->tokens->next; 1681 | 1682 | /* okay.. let's walk down the token list */ 1683 | while( bt ) { 1684 | if( bt->token_id == kToken_Semicolon ) { 1685 | addNewline = 0; 1686 | 1687 | } else if( bt->token_id == kToken_Comma ) { 1688 | printf( "\t" ); 1689 | addNewline = 1; 1690 | 1691 | } else if( bt->token_id == kToken_Number ) { 1692 | printf( "%ld", bt->l_data ); 1693 | addNewline = 1; 1694 | 1695 | } else if( bt->token_id == kToken_Quote ) { 1696 | if( bt->c_data ) printf( "%s", bt->c_data ); 1697 | addNewline = 1; 1698 | 1699 | } else if( bt->c_data) { 1700 | /* probably a variable or expression */ 1701 | vt = getVariableType( bt->c_data ); 1702 | if( vt == kVar_String ) printf( "%s", getVariableString( bp->variables, bt->c_data )); 1703 | else if( vt == kVar_Number ) printf( "%ld", getVariableNumber( bp->variables, bt->c_data )); 1704 | else printf( " ?? " ); 1705 | 1706 | addNewline = 1; 1707 | } else { 1708 | printf( " ?%04x? ", bt->token_id ); 1709 | addNewline = 1; 1710 | } 1711 | bt = bt->next; 1712 | } 1713 | 1714 | if( addNewline ) { 1715 | printf( "\n" ); 1716 | } 1717 | fflush( stdout ); 1718 | } 1719 | 1720 | 1721 | /* cmd_input 1722 | * 1723 | * prompt the user for input to a variable 1724 | INPUT A$ Apple C64 Casio ZX81 TI \n? ___ 1725 | INPUT "Enter Info",A Apple Casio \nEnter Info? ___ 1726 | INPUT "Enter Info":A TI \nEnter Info? ___ 1727 | INPUT A,B,C Apple \N? ___ 1728 | NOTE: TI does not have a space between ? and ___ (user input) 1729 | 1730 | OUR IMPLEMENTATION: 1731 | 1732 | INPUT A 1733 | INPUT A$ 1734 | INPUT "FOO";A 1735 | INPUT "FOO";A$ 1736 | 1737 | */ 1738 | void cmd_input( basic_program * bp, basic_line * bl ) 1739 | { 1740 | char * params[] = { "TAE", "TQ;AE", NULL }; 1741 | int pm; 1742 | 1743 | char * userPrompt = ""; 1744 | char * userInput = NULL; 1745 | char * variableName = "UNK"; 1746 | 1747 | if( !bp || !bl ) return; 1748 | 1749 | pm = argvMatch( bl->tokens, params ); 1750 | switch( pm ) { 1751 | case( 0 ): 1752 | variableName = argvChar( bl, 1 ); 1753 | break; 1754 | 1755 | case( 1 ): 1756 | userPrompt = argvChar( bl, 1 ); 1757 | variableName = argvChar( bl, 3 ); 1758 | break; 1759 | 1760 | default: 1761 | errorReport( pm, 301 ); 1762 | return; 1763 | } 1764 | 1765 | /* now, display the prompt to the user. */ 1766 | printf( "%s? ", userPrompt ); fflush( stdout ); 1767 | userInput = getLineFromUser(); 1768 | stringChomp( userInput ); 1769 | 1770 | /* store it */ 1771 | bp->variables = setVariableSmart( bp->variables, variableName, userInput ); 1772 | 1773 | /* clean up */ 1774 | free( userInput ); 1775 | } 1776 | 1777 | /* ********************************************************************** */ 1778 | /* Data */ 1779 | 1780 | 1781 | 1782 | /* cmd_restore 1783 | * 1784 | * restore the data pointer to a specific line 1785 | */ 1786 | void cmd_restore( basic_program * bp, basic_line * bl ) 1787 | { 1788 | char * params[] = { "TE", "T0E", "TAE", NULL }; 1789 | int pm; 1790 | 1791 | if( !bp ) return; 1792 | 1793 | pm = argvMatch( bl->tokens, params ); 1794 | switch( pm ) { 1795 | case( 0 ): /* RESTORE */ 1796 | restore_actual( bp, 0 ); 1797 | break; 1798 | 1799 | case( 1 ): /* RESTORE 430 */ 1800 | case( 2 ): /* RESTORE foo */ 1801 | restore_actual( bp, valueFromToken( argvToken( bl, 1 ), bp->variables )); 1802 | break; 1803 | 1804 | default: 1805 | errorReport( pm, 400 ); 1806 | return; 1807 | } 1808 | } 1809 | 1810 | 1811 | /* cmd_read 1812 | * 1813 | * read the next data item to the supplied variable 1814 | */ 1815 | void cmd_read( basic_program * bp, basic_line * bl ) 1816 | { 1817 | char * params[] = { "TAE", NULL }; 1818 | int pm; 1819 | 1820 | if( !bp ) return; 1821 | 1822 | if( !bp->firstData ) { 1823 | /* assume we've got a program loaded and haven't runit yet. 1824 | set up the data list for immediate mode */ 1825 | restore_actual( bp, 0 ); 1826 | } 1827 | 1828 | pm = argvMatch( bl->tokens, params ); 1829 | switch( pm ) { 1830 | case( 0 ): /* READ foo READ foo$ */ 1831 | if( bp->currentData ) { 1832 | /* store it */ 1833 | char * dval = argvChar( bl, 1 ); 1834 | 1835 | if( getVariableType( dval ) == kVar_String ) { 1836 | bp->variables = setVariableString( 1837 | bp->variables, 1838 | argvChar( bl, 1 ), 1839 | bp->currentData->c_data ); 1840 | } else { 1841 | bp->variables = setVariableNumber( 1842 | bp->variables, 1843 | argvChar( bl, 1 ), 1844 | bp->currentData->l_data ); 1845 | } 1846 | 1847 | /* advance to the next data token */ 1848 | bp->currentData = bp->currentData->nextData; 1849 | } else { 1850 | errorReport( kErrorDataRead, 400 ); 1851 | } 1852 | 1853 | break; 1854 | default: 1855 | errorReport( pm, 400 ); 1856 | return; 1857 | } 1858 | } 1859 | 1860 | 1861 | void cmd_data( basic_program * bp, basic_line * bl ) 1862 | { 1863 | /* I think we'll just assume that it's set up properly. 1864 | It will never be outright "run" anyway. */ 1865 | 1866 | /* do nothing, but do it a lot. */ 1867 | } 1868 | 1869 | 1870 | /* ********************************************************************** */ 1871 | /* Variables */ 1872 | 1873 | /* cmd_let 1874 | * 1875 | * handle an assignment 1876 | */ 1877 | void cmd_let( basic_program * bp, basic_line * bl ) 1878 | { 1879 | basic_token * bt; 1880 | if( !bp || !bl ) return; 1881 | 1882 | bt = bl->tokens; 1883 | 1884 | /* advance past the "LET" keyword -- it's surperfluous */ 1885 | if( !bt || !bt->next ) return; 1886 | bt = bt->next; 1887 | 1888 | /* and evaluate it */ 1889 | evaluateExpression( bp, bt ); 1890 | } 1891 | 1892 | 1893 | /* cmd_vac 1894 | * 1895 | * clear all variables 1896 | */ 1897 | void cmd_vac( basic_program * bp, basic_line * bl ) 1898 | { 1899 | char * params[] = { "TE", "TAE", NULL }; 1900 | int pm; 1901 | 1902 | if( !bp || !bp->variables ) return; 1903 | 1904 | pm = argvMatch( bl->tokens, params ); 1905 | switch( pm ) { 1906 | case( 0 ): 1907 | bp->variables = deleteVariables( bp->variables, 0 ); 1908 | break; 1909 | 1910 | case( 1 ): 1911 | if( !strcmp( argvChar( bl, 1 ), "ALL" )){ 1912 | bp->variables = deleteVariables( bp->variables, 1 ); 1913 | } else { 1914 | errorReport( kErrorParams, 400 ); 1915 | } 1916 | break; 1917 | 1918 | default: 1919 | errorReport( pm, 400 ); 1920 | return; 1921 | } 1922 | } 1923 | 1924 | 1925 | /* cmd_variables 1926 | * 1927 | * dump a list of all variables 1928 | */ 1929 | void cmd_variables( basic_program * bp, basic_line * bl ) 1930 | { 1931 | char * params[] = { "TE", NULL }; 1932 | int pm; 1933 | 1934 | if( !bp || !bp->variables ) return; 1935 | 1936 | pm = argvMatch( bl->tokens, params ); 1937 | if( pm < 0 ) { 1938 | errorReport( pm, 400 ); 1939 | return; 1940 | } 1941 | 1942 | dumpVariables( bp->variables ); 1943 | } 1944 | 1945 | 1946 | /* cmd_swap 1947 | * 1948 | * swap two variables 1949 | */ 1950 | void cmd_swap( basic_program * bp, basic_line * bl ) 1951 | { 1952 | char * params[] = { "TAAE", "TA,AE", NULL }; 1953 | int pm; 1954 | char * v1; 1955 | char * v2; 1956 | int t1,t2; 1957 | 1958 | /* first, get the parameters */ 1959 | pm = argvMatch( bl->tokens, params ); 1960 | switch( pm ) { 1961 | case( 0 ): 1962 | v1 = argvChar( bl, 1 ); 1963 | v2 = argvChar( bl, 2 ); 1964 | break; 1965 | case( 1 ): 1966 | v1 = argvChar( bl, 1 ); 1967 | v2 = argvChar( bl, 3 ); 1968 | break; 1969 | default: 1970 | errorReport( pm, 400 ); 1971 | return; 1972 | } 1973 | 1974 | /* next, check their types */ 1975 | t1 = getVariableType( v1 ); 1976 | t2 = getVariableType( v2 ); 1977 | if( t1 != t2 && t1 != kVar_Undefined ) { 1978 | errorReport( kErrorParamType, 401 ); 1979 | return; 1980 | } 1981 | 1982 | /* check their protection */ 1983 | if( isProtectedVariable( bp->variables, v1 ) || isProtectedVariable( bp->variables, v2 )) 1984 | { 1985 | errorReport( kErrorNonsense, 404 ); 1986 | return; 1987 | } 1988 | 1989 | /* finally... swap */ 1990 | if( t1 == kVar_String ) { 1991 | char * cv1 = strdup( getVariableString( bp->variables, v1 )); 1992 | char * cv2 = strdup( getVariableString( bp->variables, v2 )); 1993 | 1994 | bp->variables = setVariableString( bp->variables, v1, cv2 ); 1995 | bp->variables = setVariableString( bp->variables, v2, cv1 ); 1996 | 1997 | free( cv1 ); 1998 | free( cv2 ); 1999 | } 2000 | if( t1 == kVar_Number ) { 2001 | long lv1 = getVariableNumber( bp->variables, v1 ); 2002 | long lv2 = getVariableNumber( bp->variables, v2 ); 2003 | 2004 | bp->variables = setVariableNumber( bp->variables, v1, lv2 ); 2005 | bp->variables = setVariableNumber( bp->variables, v2, lv1 ); 2006 | } 2007 | } 2008 | 2009 | 2010 | /* cmd_protect 2011 | * 2012 | * protects a variable (readonly) 2013 | */ 2014 | void cmd_protect( basic_program * bp, basic_line * bl ) 2015 | { 2016 | char * params[] = { "TAE", NULL }; 2017 | int pm; 2018 | 2019 | if( !bp || !bl ) return; 2020 | 2021 | pm = argvMatch( bl->tokens, params ); 2022 | if( pm < 0 ) { 2023 | errorReport( pm, 400 ); 2024 | return; 2025 | } 2026 | 2027 | bp->variables = protectVariable( bp->variables, argvChar( bl, 1 ), 1 ); 2028 | } 2029 | 2030 | 2031 | /* cmd_unprotect 2032 | * 2033 | * unprotects a variable (read-write) 2034 | */ 2035 | void cmd_unprotect( basic_program * bp, basic_line * bl ) 2036 | { 2037 | char * params[] = { "TAE", NULL }; 2038 | int pm; 2039 | 2040 | if( !bp || !bl ) return; 2041 | 2042 | pm = argvMatch( bl->tokens, params ); 2043 | if( pm < 0 ) { 2044 | errorReport( pm, 400 ); 2045 | return; 2046 | } 2047 | 2048 | bp->variables = protectVariable( bp->variables, argvChar( bl, 1 ), 0 ); 2049 | } 2050 | 2051 | 2052 | 2053 | /* ********************************************************************** */ 2054 | /* Token Lookup Table */ 2055 | 2056 | 2057 | /* Bare minimum commands/operators: 2058 | * LOAD, SAVE, DATA, READ 2059 | * RUN, END, IF, THEN, FOR, NEXT, PRINT, INPUT, : 2060 | * +, -, *, /, ^, AND, OR, <, <=, =, >=, > 2061 | * PEEK, POKE 2062 | * 2063 | * although ':' is a tokenizer thing. 2064 | */ 2065 | 2066 | basic_tlut token_lut[] = { 2067 | /* NOTE: Based on CBM BASIC */ 2068 | /* 80-A2 Action Keywords 2069 | * B4-CA function keywords 2070 | * AA-B3 are BASIC operators 2071 | * F0-ff are my additions 2072 | */ 2073 | /* Action keywords */ 2074 | { "END", 0x80, cmd_end, "Terminate a running program" }, 2075 | { "FOR", 0x81, cmd_for, "Iterate via a variables" }, 2076 | { "NEXT", 0x82, cmd_next, "End of a \"for\" iterative block" }, 2077 | { "DATA", 0x83, cmd_data, "Comma separated list of data items" }, 2078 | { "INPUT#", 0x84 }, 2079 | { "INPUT", 0x85, cmd_input, "Input from the user to a variable" }, 2080 | { "DIM", 0x86 }, 2081 | { "READ", 0x87, cmd_read, "Read the next item from the data list" }, 2082 | 2083 | { "LET", 0x88, cmd_let, "Assign a variable a value" }, 2084 | { "GOTO", 0x89, cmd_goto, "Continue on the specified line" }, 2085 | { "RUN", 0x8a, cmd_run, "Run the loaded program" }, 2086 | { "IF", 0x8b, cmd_if, "Conditionally goto a new line" }, 2087 | { "RESTORE", 0x8c, cmd_restore, "Adjust the DATA-READ list location" }, 2088 | { "GOSUB", 0x8d, cmd_gosub, "Call a subroutine" }, 2089 | { "RETURN", 0x8e, cmd_return, "Return from a subroutine" }, 2090 | { "REM", 0x8f, NULL, "REMark. (comment)" }, 2091 | 2092 | { "STOP", 0x90 }, 2093 | { "ON", 0x91, cmd_on, "Switch for GOTO and GOSUB" }, 2094 | { "WAIT", 0x92 }, 2095 | { "LOAD", 0x93, cmd_load, "Load the specified \"program\"" }, 2096 | { "SAVE", 0x94, cmd_save, "Save the program to disk" }, 2097 | { "VERIFY", 0x95 }, 2098 | { "DEF", 0x96 }, 2099 | { "POKE", 0x97 }, 2100 | 2101 | { "PRINT#", 0x98 }, 2102 | { "PRINT", 0x99, cmd_print, "Print out an expression" }, 2103 | { "CONT", 0x9a }, 2104 | { "LIST", 0x9b, cmd_list, "List the current program" }, 2105 | { "CLR", 0x9c }, 2106 | { "CMD", 0x9d }, 2107 | { "SYS", 0x9e }, 2108 | { "OPEN", 0x9f }, 2109 | { "CLOSE", 0xa0 }, 2110 | { "GET", 0xa1 }, 2111 | { "NEW", 0xa2, cmd_new, "Erase program ram" }, 2112 | 2113 | /* Misc functions */ 2114 | { "TAB(", 0xa3 }, 2115 | { "TO", 0xa4 }, 2116 | { "FN", 0xa5 }, 2117 | { "SPC(", 0xa6 }, 2118 | { "THEN", 0xa7 }, 2119 | { "NOT", 0xa8 }, 2120 | { "STEP", 0xa9 }, 2121 | 2122 | /* operators */ 2123 | { "+", 0xaa }, 2124 | { "-", 0xab }, 2125 | { "*", 0xac }, 2126 | { "/", 0xad }, 2127 | { "^", 0xae }, 2128 | { "AND", 0xaf }, 2129 | { "OR", 0xb0 }, 2130 | 2131 | { "<=", 0x0507 }, /* less than or equal */ 2132 | { ">=", 0x0508 }, /* greater than or equal */ 2133 | { "<>", 0x0509 }, /* not equal */ 2134 | 2135 | { ">", 0xb1 }, /* greater than */ 2136 | { "=", 0xb2 }, /* equal */ 2137 | { "<", 0xb3 }, /* less than */ 2138 | 2139 | /* functions */ 2140 | { "SGN", 0xb4 }, 2141 | { "INT", 0xb5 }, 2142 | { "ABS", 0xb6 }, 2143 | { "USR", 0xb7 }, 2144 | 2145 | { "FRE", 0xb8 }, 2146 | { "POS", 0xb9 }, 2147 | { "SQR", 0xba }, 2148 | { "RND", 0xbb }, 2149 | { "LOG", 0xbc }, 2150 | { "EXP", 0xbd }, 2151 | { "COS", 0xbe }, 2152 | { "SIN", 0xbf }, 2153 | 2154 | { "TAN", 0xc0 }, 2155 | { "ATN", 0xc1 }, 2156 | { "PEEK", 0xc2 }, 2157 | { "LEN", 0xc3 }, 2158 | { "STR$", 0xc4 }, 2159 | { "VAL", 0xc5 }, 2160 | { "ASC", 0xc6 }, 2161 | { "CHR$", 0xc7 }, 2162 | 2163 | { "LEFT$", 0xc8 }, 2164 | { "RIGHT$", 0xc9 }, 2165 | { "MID$", 0xca }, 2166 | 2167 | /* file and disk operations */ 2168 | { "BANK", 0xe1 }, /* select program bank PARAM */ 2169 | 2170 | /* misc */ 2171 | { "PI", 0xff }, 2172 | 2173 | 2174 | /* my turtle graphics */ 2175 | { "CLS", 0x0100 }, /* clear screen */ 2176 | { "COLOR", 0x0101 }, /* set color to PARAM */ 2177 | /* Commodore 64 colors: 2178 | * 1: black 000 2179 | * 2: white fff 2180 | * 3: red f00 2181 | * 4: cyan 0ff 2182 | * 5: purple f0f 2183 | * 6: green 0f0 2184 | * 7: blue 00f 2185 | * 8: yellow ff0 2186 | * 2187 | * TI 99/4A colors: 2188 | * 1: transparent 2189 | * 2: black 000 2190 | * 3: medium green 080 2191 | * 4: light green 8f8 2192 | * 5: dark blue 008 2193 | * 6: light blue 88f 2194 | * 7: dark red 800 2195 | * 8: cyan 0ff 2196 | * 9: medium red f00 2197 | * 10: light red f88 2198 | * 11: dark yellow 880 2199 | * 12: light yellow ff0 2200 | * 13: dark green 0f0 2201 | * 14: magenta f0f 2202 | * 15: gray 888 2203 | * 16: white fff 2204 | */ 2205 | { "PD", 0x0102 }, /* pen down (start drawing) */ 2206 | { "PU", 0x0103 }, /* pen up (stop drawing) */ 2207 | { "FD", 0x0104 }, /* forward PARAM px */ 2208 | { "RT", 0x0105 }, /* right rotate PARAM degrees */ 2209 | { "LT", 0x0106 }, /* left rotate PARAM degrees */ 2210 | { "MT", 0x0107 }, /* move to PARAMX,PARAMY */ 2211 | 2212 | /* floppy diskette commands */ 2213 | { "FILES", 0x0201, cmd_files, "List of saved basic program files" }, 2214 | { "CATALOG", 0x0202, cmd_files, "List of saved basic program files" }, 2215 | { "HEAD", 0x0203, cmd_head, "Display 5 lines of the specified program" }, 2216 | 2217 | /* user commands */ 2218 | { "HELP", 0x0301, cmd_help, "Display a list of commands" }, 2219 | { "INFO", 0x0302, cmd_info, "Print system information" }, 2220 | { "BYE", 0x0303, cmd_bye, "Quit out of the interpreter" }, 2221 | { "RENUM", 0x0304, cmd_renum, "Renumber the basic program lines" }, 2222 | 2223 | { "TRON", 0x0305, cmd_tron, "Enable program run tracing" }, 2224 | { "TROFF", 0x0306, cmd_troff, "Disable program run tracing" }, 2225 | 2226 | { "VAC", 0x0307, cmd_vac, "Erase all variables from memory" }, 2227 | { "VARIABLES", 0x0308, cmd_variables, "Displays all variables in memory" }, 2228 | { "PROTECT", 0x0310, cmd_protect, "Protects a variable (read-only)" }, 2229 | { "UNPROTECT", 0x0311, cmd_unprotect, "Unprotects a variable (read-write)" }, 2230 | 2231 | { "LABEL", 0x0400, NULL, "Label the line for goto/gosubs" }, 2232 | { "SWAP", 0x0401, cmd_swap, "Swap two variables" }, 2233 | 2234 | /* anything less than 0x1000 is string tokens with functions (above) */ 2235 | /* 0x42xx range is used for token detection of characters (num, space, etc) */ 2236 | 2237 | { NULL } 2238 | }; 2239 | 2240 | 2241 | /* 2242 | * isSameToken 2243 | * compare the haystack against possible tokens (case insensitive) 2244 | * returns 0 if it doesn't match 2245 | * returns 1 if it does match 2246 | */ 2247 | int isSameToken( char * master, char * haystack ) 2248 | { 2249 | if( !master || !haystack ) return 0; 2250 | 2251 | while( *master != '\0' ) 2252 | { 2253 | if( tolower( *master ) != tolower( *haystack )) return 0; 2254 | master++; 2255 | haystack++; 2256 | } 2257 | 2258 | return 1; 2259 | } 2260 | 2261 | 2262 | 2263 | /* findLutTokenFromID 2264 | * 2265 | * find the token node that has the same ID 2266 | * 2267 | */ 2268 | basic_tlut * findLutTokenFromID( int token ) 2269 | { 2270 | int tidx = 0; 2271 | 2272 | while( token_lut[tidx].cmd != NULL ) 2273 | { 2274 | if( token_lut[tidx].token == token ) 2275 | { 2276 | return &token_lut[tidx]; 2277 | } 2278 | tidx++; 2279 | } 2280 | return NULL; 2281 | } 2282 | 2283 | 2284 | /* findTokenStringFromID 2285 | * 2286 | * get the string representation of the passed in token 2287 | */ 2288 | char * findTokenStringFromID( int token ) 2289 | { 2290 | basic_tlut * t = findLutTokenFromID( token ); 2291 | 2292 | if( t ) { 2293 | return t->cmd; 2294 | } 2295 | 2296 | if( token == 10 ) return "EOL"; 2297 | switch( token ) { 2298 | case( kToken_User ): return "USER"; 2299 | case( kToken_ERROR ): return "ERR0"; 2300 | case( kToken_Alpha ): return "A"; 2301 | case( kToken_Space ): return "SPC"; 2302 | case( kToken_Number ): return "1"; 2303 | case( kToken_Newline ): return "CR"; 2304 | case( kToken_Colon ): return ":"; 2305 | case( kToken_Comma ): return ","; 2306 | case( kToken_Semicolon ): return ";"; 2307 | case( kToken_Quote ): return "\""; 2308 | case( kToken_EOS ): return "0"; 2309 | } 2310 | 2311 | return "UNK?"; 2312 | } 2313 | 2314 | -------------------------------------------------------------------------------- /engine/basic_evaluator.h: -------------------------------------------------------------------------------- 1 | /* basic_evaluator 2 | * 3 | * BleuLlama BASIC 4 | * Copyright (c) 2011 Scott Lawrence 5 | */ 6 | 7 | /* LICENSE: 8 | * 9 | * Copyright (C) 2011 by Scott Lawrence 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in 19 | * all copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | */ 29 | 30 | #ifndef __basic_evaluator_h__ 31 | #define __basic_evaluator_h__ 32 | 33 | #include "basic_tokenizer.h" 34 | 35 | #define kDocumentsDirectory "./programs" /* directory where we store programs */ 36 | 37 | /* ********************************************************************** */ 38 | 39 | typedef struct basic_tlut { 40 | char * cmd; /* command text to match/generate */ 41 | int token; /* internal token ID number */ 42 | void (*perform)( basic_program * bp, basic_line * bl ); 43 | char * helpText; /* text to display for this command */ 44 | } basic_tlut; 45 | 46 | extern basic_tlut token_lut[]; /* the actual table */ 47 | 48 | /* ********************************************************************** */ 49 | 50 | /* <0x1000 T */ 51 | #define kToken_User 0x1000 /* anything above this is special use */ 52 | #define kToken_ERROR 0x4200 /* nothing */ 53 | #define kToken_Alpha 0x4201 /* V anything printable */ 54 | #define kToken_Space 0x4202 /* [ \n\t\r] */ 55 | #define kToken_Number 0x4203 /* 0 [0123456789] */ 56 | #define kToken_Newline 0x4204 /* \n */ 57 | #define kToken_Colon 0x4205 /* : : */ 58 | #define kToken_Semicolon 0x4206 /* ; ; */ 59 | #define kToken_Quote 0x4207 /* Q " */ 60 | #define kToken_Comma 0x4208 /* , */ 61 | #define kToken_EOS 0x5000 /* E \0 */ 62 | /* SPECIALS: */ 63 | /* ( ( */ 64 | /* ) ) */ 65 | #define kToken_Dash 0x00ab /* - */ 66 | #define kToken_Equals 0x00b2 /* = */ 67 | 68 | /* ********************************************************************** */ 69 | 70 | int isSameToken( char * master, char * haystack ); 71 | basic_tlut * findLutTokenFromID( int token ); 72 | char * findTokenStringFromID( int token ); 73 | 74 | /* ********************************************************************** */ 75 | 76 | void evaluateLine( basic_program * bp, basic_line * bl ); 77 | int run_poll( basic_program * bp ); 78 | 79 | void runProgram( basic_program * bp, long startline ); 80 | void stopProgram( basic_program * bp ); 81 | 82 | 83 | /* ********************************************************************** */ 84 | 85 | void cmd_new( basic_program * bp, basic_line * bl ); 86 | void cmd_info( basic_program * bp, basic_line * bl ); 87 | void cmd_infosteps( basic_program *bp, basic_line * bl ); 88 | 89 | #endif 90 | -------------------------------------------------------------------------------- /engine/basic_string.c: -------------------------------------------------------------------------------- 1 | /* basic_string 2 | * 3 | * BleuLlama BASIC 4 | * Copyright (c) 2011 Scott Lawrence 5 | */ 6 | 7 | /* LICENSE: 8 | * 9 | * Copyright (C) 2011 by Scott Lawrence 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in 19 | * all copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | */ 29 | 30 | #include 31 | #include /* malloc, etc */ 32 | #include /* strdup */ 33 | #include "basic_string.h" 34 | 35 | /* newString 36 | * 37 | * creates a new string, filled with "" [0]='\0' 38 | */ 39 | char * newString( char * content ) 40 | { 41 | char * ret; 42 | 43 | if( !content ) content = ""; 44 | 45 | ret = strdup( content ); 46 | return ret; 47 | } 48 | 49 | 50 | /* appendString 51 | * 52 | * append a string to an existing string, realloc if necessary 53 | */ 54 | char * appendString( char * start, char * newBit ) 55 | { 56 | char * ret; 57 | 58 | /* nothing to add, just return */ 59 | if( newBit == NULL ) return start; 60 | 61 | /* nothing to start with, start with "" */ 62 | if( !start ) start = newString( "" ); 63 | 64 | /* realloc our space for the new bit */ 65 | ret = realloc( start, strlen( newBit ) + strlen( start ) + 2 ); 66 | strcat( ret, newBit ); 67 | return ret; 68 | } 69 | 70 | 71 | /* appendNumber 72 | * 73 | * append a number to the end of a string, realloc if necessary 74 | */ 75 | char * appendNumber( char * start, long newBit ) 76 | { 77 | char buf[32]; 78 | snprintf( buf, 32, "%ld", newBit ); 79 | return appendString( start, buf ); 80 | } 81 | 82 | 83 | /* stringChomp 84 | * 85 | * remove newlines from the end of a string (if applicable) 86 | */ 87 | char * stringChomp( char * str ) 88 | { 89 | int len; 90 | 91 | /* check for valid strings */ 92 | if( !str ) return NULL; 93 | len = strlen( str ); 94 | 95 | if( len < 1 ) return str; 96 | 97 | /* remove newline stuffs */ 98 | if( str[len-1] == '\n' ) 99 | str[len-1] = '\0'; 100 | if( str[len-1] == '\r' ) 101 | str[len-1] = '\0'; 102 | 103 | return str; 104 | } 105 | 106 | 107 | /* getLineFromUser 108 | * 109 | * gets a line of text from the user 110 | * returned line must be freed. 111 | */ 112 | char * getLineFromUser( void ) 113 | { 114 | char lnbuf[kInputBufferSize]; 115 | fgets( lnbuf, kInputBufferSize, stdin ); 116 | 117 | return( strdup( lnbuf )); 118 | } 119 | -------------------------------------------------------------------------------- /engine/basic_string.h: -------------------------------------------------------------------------------- 1 | /* basic_string 2 | * 3 | * BleuLlama BASIC 4 | * Copyright (c) 2011 Scott Lawrence 5 | */ 6 | 7 | /* LICENSE: 8 | * 9 | * Copyright (C) 2011 by Scott Lawrence 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in 19 | * all copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | */ 29 | 30 | #ifndef __basic_string_h__ 31 | #define __basic_string_h__ 32 | 33 | #define kInputBufferSize 1024 34 | 35 | char * newString( char * content ); 36 | char * appendString( char * start, char * newBit ); 37 | char * appendNumber( char * start, long newBit ); 38 | char * stringChomp( char * str ); 39 | char * getLineFromUser( void ); 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /engine/basic_tokenizer.c: -------------------------------------------------------------------------------- 1 | /* basic_tokenizer 2 | * 3 | * BleuLlama BASIC 4 | * Copyright (c) 2011 Scott Lawrence 5 | */ 6 | 7 | /* LICENSE: 8 | * 9 | * Copyright (C) 2011 by Scott Lawrence 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in 19 | * all copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | */ 29 | 30 | #include /* printf */ 31 | #include /* malloc */ 32 | #include /* strdup, strncpy */ 33 | #include /* tolower */ 34 | 35 | #include "basic_tokenizer.h" 36 | #include "basic_evaluator.h" 37 | #include "basic_errors.h" 38 | #include "basic_string.h" 39 | 40 | /* ******************************************************************************* */ 41 | 42 | 43 | /* extractQuotedFromString 44 | * 45 | * return a string of the contents of the current quoted text 46 | * on failure, return NULL 47 | */ 48 | char * extractQuotedFromString( char * line ) 49 | { 50 | char * ret; 51 | int i = 0; 52 | 53 | if( !line ) return strdup( "" ); 54 | /* this assumes that *line == '"' */ 55 | line++; 56 | 57 | /* count size */ 58 | while( line[i] != '\0' && line[i] != '"' ) i++; 59 | 60 | /* check for EOL */ 61 | if( line[i] == '\0' ) { 62 | return NULL; 63 | } 64 | 65 | ret = (char *)malloc( sizeof( char ) * (i+1) ); 66 | strncpy( ret, line, i ); 67 | ret[i] = '\0'; 68 | 69 | return ret; 70 | } 71 | 72 | 73 | /* extractNumberFromString 74 | * 75 | * return a string of the contents of the current integer 76 | */ 77 | char * extractNumberFromString( char * line ) 78 | { 79 | char * ret; 80 | int i = 0; 81 | 82 | if( !line ) return strdup( "" ); 83 | 84 | /* count size */ 85 | while( line[i] != '\0' && isdigit( line[i] )) i++; 86 | 87 | ret = (char *)malloc( sizeof( char ) * (i+1) ); 88 | strncpy( ret, line, i ); 89 | ret[i] = '\0'; 90 | 91 | return ret; 92 | } 93 | 94 | 95 | 96 | /* ******************************************************************************* */ 97 | 98 | /* create new nodes */ 99 | basic_token * newToken( void ) 100 | { 101 | basic_token * bt = (basic_token *)malloc( sizeof(basic_token) ); 102 | if( !bt ) return NULL; 103 | 104 | bt->token_id = 0; 105 | bt->l_data = 0; 106 | bt->c_data = NULL; 107 | bt->next = NULL; 108 | bt->nextData = NULL; 109 | return bt; 110 | } 111 | 112 | basic_token * appendToken( basic_token * existingTokens, basic_token * theNewToken ) 113 | { 114 | basic_token * mp; 115 | 116 | if( !theNewToken ) return existingTokens; /* nothing to add. bail out */ 117 | if( !existingTokens ) return theNewToken; /* nothing to start with, make a new one */ 118 | 119 | mp = existingTokens; 120 | 121 | while( mp->next ) mp = mp->next; 122 | mp->next = theNewToken; 123 | 124 | return existingTokens; 125 | } 126 | 127 | 128 | 129 | basic_line * newLine( void ) 130 | { 131 | basic_line * bl = (basic_line *)malloc( sizeof(basic_line) ); 132 | if( !bl ) return NULL; 133 | 134 | bl->lineNumber = kNoLineNumber; 135 | bl->temp = -1; 136 | bl->tokens = NULL; 137 | bl->continuation = NULL; 138 | bl->continuationParent = NULL; 139 | bl->next = NULL; 140 | bl->prev = NULL; 141 | bl->nextData = NULL; 142 | return bl; 143 | } 144 | 145 | 146 | void clearRuntime( basic_program * bp ) 147 | { 148 | if( !bp ) return; 149 | 150 | bp->runningLine = NULL; 151 | bp->nextLineNumberToExecute = 0; 152 | bp->nGosubLevels = 0; 153 | } 154 | 155 | 156 | 157 | basic_program * newProgram( void ) 158 | { 159 | basic_program * bp = (basic_program *)malloc( sizeof(basic_program) ); 160 | if( !bp ) return NULL; 161 | 162 | bp->exitNow = 0; 163 | bp->listing = NULL; 164 | bp->variables = NULL; 165 | bp->traceOn = 0; 166 | 167 | bp->firstData = NULL; 168 | bp->currentData = NULL; 169 | 170 | clearRuntime( bp ); 171 | 172 | return bp; 173 | } 174 | 175 | 176 | /* uncreate node trees */ 177 | void deleteTokens( basic_token * tokens ) 178 | { 179 | basic_token * n; 180 | while( tokens ) 181 | { 182 | if( tokens->c_data ) free( tokens->c_data ); 183 | n = tokens->next; 184 | free( tokens ); 185 | tokens = n; 186 | } 187 | } 188 | 189 | void deleteLines( basic_line * lines ) 190 | { 191 | basic_line * n; 192 | while( lines ) { 193 | if( lines->tokens ) deleteTokens( lines->tokens ); 194 | if( lines->continuation ) deleteLines( lines->continuation ); 195 | n = lines->next; 196 | free( lines ); 197 | lines = n; 198 | } 199 | } 200 | 201 | void deleteProgram( basic_program * bp ) 202 | { 203 | free( bp->listing ); 204 | free( bp ); 205 | } 206 | 207 | 208 | /* ******************************************************************************* */ 209 | /* bookkeepping */ 210 | 211 | /* countSteps 212 | * 213 | * count the number of tokens in a line 214 | * NOTE: this is a fictitious number, with value based on data structure content 215 | */ 216 | long countSteps( basic_line * bl ) 217 | { 218 | long count = 1; /* the line number is one step */ 219 | 220 | basic_token * t; 221 | 222 | if( !bl ) return 0; 223 | 224 | if( !bl->tokens ) return 0; 225 | 226 | t = bl->tokens; 227 | 228 | while( t ) 229 | { 230 | if( t->c_data ) count += strlen( t->c_data ); 231 | else count++; 232 | 233 | t = t->next; 234 | } 235 | return count; 236 | } 237 | 238 | 239 | /* programSize 240 | * 241 | * count the number of steps in a program 242 | * NOTE: this is a fictitious number, with value based on data structure content 243 | */ 244 | long programSize( basic_program * bp ) 245 | { 246 | long count = 0; 247 | basic_line * l; 248 | 249 | if( !bp ) return 0; 250 | if( !bp->listing ) return 0; 251 | 252 | l = bp->listing; 253 | while( l ) 254 | { 255 | count += countSteps( l ); 256 | l = l->next; 257 | } 258 | 259 | return count; 260 | } 261 | 262 | 263 | /* ******************************************************************************* */ 264 | /* some stuff to help with dealing with token lists */ 265 | 266 | /* lineArgc 267 | * return the number of elements in the token list of this line 268 | */ 269 | int lineArgc( basic_line * bl ) 270 | { 271 | int ret = 0; 272 | basic_token * tt; 273 | 274 | if( !bl ) return 0; 275 | 276 | tt = bl->tokens; 277 | 278 | while( tt ) { 279 | ret++; 280 | tt = tt->next; 281 | } 282 | 283 | return ret; 284 | } 285 | 286 | 287 | /* argvToken 288 | * retrieves the token at the specified index 289 | */ 290 | basic_token * argvToken( basic_line * bl, int idx ) 291 | { 292 | basic_token * tt = bl->tokens; 293 | 294 | while( idx > 0 && tt ) 295 | { 296 | tt = tt->next; 297 | idx--; 298 | } 299 | return tt; 300 | } 301 | 302 | /* argvLong 303 | * retrieves the long data at the specified index 304 | */ 305 | long argvLong( basic_line * bl, int idx ) 306 | { 307 | basic_token * tt = argvToken( bl, idx ); 308 | if( !tt ) return 0; 309 | return tt->l_data; 310 | } 311 | 312 | 313 | /* argvChar 314 | * retrieves the string data at the specified index 315 | */ 316 | char * argvChar( basic_line * bl, int idx ) 317 | { 318 | basic_token * tt = argvToken( bl, idx ); 319 | if( !tt ) return NULL; 320 | return tt->c_data; 321 | } 322 | 323 | 324 | /* argvTokenID 325 | * retrieves the string data at the specified index 326 | */ 327 | int argvTokenID( basic_line * bl, int idx ) 328 | { 329 | basic_token * tt = argvToken( bl, idx ); 330 | if( !tt ) return -1; 331 | return tt->token_id; 332 | } 333 | 334 | 335 | /* argvMatch 336 | * 337 | * matches a basic_line passed in with various possible token lists 338 | * A = aplhanumeric (variable name) 339 | * Q = Quoted (immediate string) 340 | * 0 = number (immediate value) (zero) 341 | * V = Alphanumeric (variable name) OR Number (immediate Value) 342 | * ; = Semicolon 343 | * , = comma 344 | * - = dash 345 | * E = require end of list 346 | * ex: 347 | * int matchedLIST = argvMatch( tl, { "E", "0E", "0-E", "-0E", "00E", "0-0E", NULL } ); 348 | * 349 | * returns >= 0 on match 350 | * returns -1 on not found (invalid parameter list) 351 | */ 352 | int argvMatch( basic_token * tl, char ** matchList ) 353 | { 354 | /* 355 | char *s1; 356 | char *s2; 357 | */ 358 | char ** ml = matchList; 359 | char buf[512]; 360 | basic_token * inx = tl; 361 | int pos = 0; 362 | char add; 363 | 364 | char lastc; 365 | 366 | if( !matchList ) return kErrorNonsense; 367 | 368 | /* okay. here's what we're going to do here */ 369 | /* 1 build a list of what we have in the token list */ 370 | /* (a simplified type-only version) */ 371 | while( pos < 511 && inx ) 372 | { 373 | switch( inx->token_id ) { 374 | case( kToken_Alpha ): add = 'A'; break; 375 | case( kToken_Number ): add = '0'; break; 376 | case( kToken_Colon ): add = ':'; break; 377 | case( kToken_Semicolon ): add = ';'; break; 378 | case( kToken_Quote ): add = 'Q'; break; 379 | case( kToken_Comma ): add = ','; break; 380 | case( kToken_Dash ): add = '-'; break; 381 | case( kToken_Equals ): add = '='; break; 382 | default: 383 | add = 'T'; /* TOKEN probably... */ 384 | break; 385 | } 386 | 387 | buf[pos++] = add; 388 | inx=inx->next; 389 | } 390 | buf[pos++] = 'E'; 391 | buf[pos] = '\0'; 392 | 393 | /* 2 scroll through the matchList and see what matches */ 394 | pos = 0; 395 | ml = matchList; 396 | while( *ml ) { 397 | char * tml = *ml; 398 | if( strlen( tml ) > 1 ) lastc = tml[ strlen( *ml )-1 ]; 399 | else lastc = tml[0]; 400 | 401 | /* first, let's do an explicit exact check */ 402 | if( lastc == 'E' && !strcmp( *ml, buf )) { 403 | return pos; 404 | 405 | } else if( lastc != 'E' && !strncmp( *ml, buf, strlen( *ml )) ) { 406 | return pos; 407 | } 408 | 409 | #ifdef NEVER 410 | /* okay, maybe that didn't work out. let's check again */ 411 | s1 = *ml; /* user provided */ 412 | s2 = buf; /* our generated list */ 413 | while( s1 && s2 ) 414 | { 415 | char c1 = *s1; 416 | char c2 = *s2; 417 | 418 | /* for each character, 419 | * if c1 != c2 420 | * if( c1 is a 'v' and c2 isn't '0' or 'A' ) 421 | * then next; 422 | */ 423 | if( c1 != c2 ) { 424 | if( c1 == 'V' ) { 425 | if( c2 != '0' && c2 != 'A' ) { 426 | break; 427 | } 428 | } 429 | } 430 | 431 | s1++; s2++; 432 | } 433 | #endif 434 | pos++; 435 | ml++; 436 | } 437 | 438 | /* okay. nothing was found. let's check for one of the same length */ 439 | ml = matchList; 440 | while( *ml ) { 441 | if( strlen( *ml ) == strlen( buf )) return( kErrorParamType ); 442 | ml++; 443 | } 444 | 445 | 446 | /* nothing applicable, send this error message */ 447 | return( kErrorParams ); 448 | } 449 | 450 | 451 | /* ******************************************************************************* */ 452 | 453 | /* findLineNumber 454 | * 455 | * finds a specific line number in a program 456 | */ 457 | basic_line * findLineNumber( basic_program * bp, long lineNo ) 458 | { 459 | basic_line * l; 460 | 461 | if( !bp ) return NULL; 462 | if( !bp->listing ) return NULL; 463 | 464 | l = bp->listing; 465 | 466 | while( l ) 467 | { 468 | if( l->lineNumber == lineNo ) return l; 469 | l = l->next; 470 | } 471 | 472 | return NULL; 473 | } 474 | 475 | /* findLowestLineNumber 476 | * 477 | * finds the first line with the number equal to or greater than the specified line 478 | */ 479 | basic_line * findLowestLineNumber( basic_program * bp, long lineNo ) 480 | { 481 | basic_line * l; 482 | if( !bp ) return NULL; 483 | if( !bp->listing ) return NULL; 484 | 485 | if( lineNo <= 0 ) return bp->listing; 486 | 487 | l = bp->listing; 488 | 489 | while( l ) 490 | { 491 | if( l->lineNumber >= lineNo ) return l; 492 | l = l->next; 493 | } 494 | return NULL; 495 | } 496 | 497 | 498 | /* removeLineNumber 499 | * 500 | * removes the specified line number from the list 501 | */ 502 | void removeLineNumber( basic_program * bp, long lineNo ) 503 | { 504 | basic_line * l; 505 | 506 | if( !bp ) return; 507 | if( !bp->listing ) return; 508 | 509 | /* the few places we can be: 510 | 511 | 1) first element on the list 512 | 2) middle of the list 513 | 3) end of the list 514 | */ 515 | 516 | l = bp->listing; 517 | while( l ) 518 | { 519 | if( l->lineNumber == lineNo ) { 520 | 521 | /* check for head of the line */ 522 | if( l == bp->listing ) { 523 | /* head of the list */ 524 | bp->listing = bp->listing->next; 525 | if( bp->listing ) { 526 | bp->listing->prev = NULL; 527 | } 528 | } else if( l->next ) { 529 | /* item is in the middle of the list */ 530 | l->prev->next = l->next; 531 | l->next->prev = l->prev; 532 | } else { 533 | /* item is the tail of the list */ 534 | l->prev->next = NULL; 535 | } 536 | 537 | /* just in cases */ 538 | l->prev = NULL; 539 | l->next = NULL; 540 | 541 | /* remove the line! */ 542 | deleteLines( l ); 543 | return; 544 | } 545 | l = l->next; 546 | } 547 | } 548 | 549 | 550 | /* insertLine 551 | * 552 | * adds the specified line in to the program 553 | */ 554 | void insertLine( basic_program * bp, basic_line * theLine ) 555 | { 556 | basic_line * l; 557 | 558 | if( !bp || !theLine ) return; 559 | 560 | /* the few places we can be: 561 | -> NULL 1) no list, insert at the head 562 | 563 | -> Z -> NULL 2) insert at head, before first item 564 | 565 | -> A -> Z -> NULL 3) insert in the middle 566 | 567 | -> A -> NULL 4) insert at tail 568 | */ 569 | 570 | 571 | /* 1 */ 572 | if( !bp->listing ) { 573 | /* first line! */ 574 | bp->listing = theLine; 575 | theLine->prev = NULL; 576 | theLine->next = NULL; 577 | return; 578 | } 579 | 580 | 581 | /* 2 */ 582 | if( bp->listing->lineNumber > theLine->lineNumber ) 583 | { 584 | /* insert at the head, with content past it */ 585 | theLine->next = bp->listing; 586 | bp->listing->prev = theLine; 587 | bp->listing = theLine; 588 | return; 589 | } 590 | 591 | /* test for 3 */ 592 | l = bp->listing; 593 | while( l->next ) 594 | { 595 | if( l->next->lineNumber > theLine->lineNumber ) 596 | { 597 | theLine->next = l->next; 598 | theLine->prev = l; 599 | theLine->next->prev = theLine; 600 | theLine->prev->next = theLine; 601 | return; 602 | } 603 | l = l->next; 604 | } 605 | 606 | /* 4 */ 607 | l->next = theLine; 608 | theLine->prev = l; 609 | } 610 | 611 | 612 | /* ******************************************************************************* */ 613 | 614 | 615 | char * catTokenToString( basic_token * t, char * toBuf, int bufsize ) 616 | { 617 | char tstr[32]; 618 | 619 | if( !t ) return toBuf; 620 | 621 | if( t->token_id < kToken_User ) { 622 | strncat( toBuf, findTokenStringFromID( t->token_id ), bufsize ); 623 | 624 | } else { 625 | switch( t->token_id ) 626 | { 627 | case( kToken_Alpha ): 628 | strncat( toBuf, t->c_data, bufsize ); 629 | break; 630 | 631 | case( kToken_Quote ): 632 | strncat( toBuf, "\"", bufsize ); 633 | strncat( toBuf, t->c_data, bufsize ); 634 | strncat( toBuf, "\"", bufsize ); 635 | break; 636 | 637 | case( kToken_Number ): 638 | snprintf( tstr, 32, "%ld", t->l_data ); 639 | strncat( toBuf, tstr, bufsize ); 640 | break; 641 | 642 | case( kToken_Colon ): 643 | strncat( toBuf, ":", bufsize ); 644 | break; 645 | 646 | case( kToken_Semicolon ): 647 | strncat( toBuf, ";", bufsize ); 648 | break; 649 | 650 | case( kToken_Comma ): 651 | strncat( toBuf, ",", bufsize ); 652 | break; 653 | 654 | case( kToken_User ): 655 | case( kToken_ERROR ): 656 | case( kToken_Space ): 657 | case( kToken_Newline ): 658 | case( kToken_EOS ): 659 | strncat( toBuf, "?", bufsize ); 660 | break; 661 | } 662 | } 663 | 664 | return toBuf; 665 | } 666 | 667 | /* return a string suitible for LIST of the tokens */ 668 | char * stringizeLine( basic_line * l, char * toBuf, int bufsize ) 669 | { 670 | int prependSpace = 0; 671 | basic_token * tt; 672 | 673 | if( !l ) return toBuf; 674 | 675 | if( l->lineNumber != kNoLineNumber ) 676 | { 677 | snprintf( toBuf, bufsize, "%ld ", l->lineNumber ); 678 | } else { 679 | toBuf[0] = '\0'; 680 | } 681 | 682 | tt = l->tokens; 683 | while( tt ) { 684 | if( prependSpace ) { 685 | strncat( toBuf, " ", bufsize ); 686 | } 687 | prependSpace++; 688 | catTokenToString( tt, toBuf, bufsize ); 689 | tt = tt->next; 690 | }; 691 | 692 | if( l->continuation ) { 693 | strncat( toBuf, " : ", bufsize ); 694 | stringizeLine( l->continuation, toBuf + strlen(toBuf), bufsize - strlen(toBuf) ); 695 | } 696 | 697 | return toBuf; 698 | } 699 | 700 | 701 | 702 | /* ******************************************************************************* */ 703 | 704 | 705 | int tokenTypeFromString( char *c, int disableLut ) 706 | { 707 | int tidx = 0; 708 | 709 | if( !c ) return kToken_ERROR; 710 | 711 | if( *c >= '0' && *c <= '9') return kToken_Number; 712 | if( *c == 10 ) return kToken_Newline; 713 | if( *c == ':' ) return kToken_Colon; 714 | if( *c == ';' ) return kToken_Semicolon; 715 | if( *c == '"' ) return kToken_Quote; 716 | if( *c == ',' ) return kToken_Comma; 717 | if( *c == '\0' ) return kToken_EOS; 718 | if( isspace( *c ) ) return kToken_Space; 719 | 720 | if( disableLut && isalnum( *c )) { 721 | return kToken_Alpha; 722 | } 723 | 724 | while( token_lut[tidx].cmd != NULL ) 725 | { 726 | if( isSameToken( token_lut[tidx].cmd, c )) 727 | { 728 | return( token_lut[tidx].token ); 729 | } 730 | tidx++; 731 | } 732 | 733 | return kToken_Alpha; 734 | } 735 | 736 | int bytesToNextToken( char * line ) 737 | { 738 | int startToken; 739 | int idx = 1; 740 | 741 | if( !line ) return 0; 742 | 743 | startToken = tokenTypeFromString( line, 0 ); 744 | 745 | #ifdef NEVER 746 | if( startToken == kToken_Alpha ) 747 | { 748 | /* check for an alphanumeric label */ 749 | while( line[idx] != '\0' && line[idx] != '$' 750 | && ( isalnum( line[idx] ) || line[idx] != '_' )) 751 | { 752 | idx++; 753 | } 754 | return idx; 755 | } 756 | #endif 757 | 758 | /* check for something else */ 759 | if( startToken >= kToken_User ) { 760 | while( line[idx] != '\0' 761 | && tokenTypeFromString( line+idx, (startToken == kToken_Alpha) ) == startToken ) 762 | { 763 | idx++; 764 | } 765 | } 766 | return idx; 767 | } 768 | 769 | /* tokenize a string into the proper location */ 770 | basic_line * tokenizeLine( char * line ) 771 | { 772 | char * pstr; 773 | char * tstr; 774 | char * lno; 775 | int thisToken; 776 | int comment = 0; 777 | basic_line * bl; 778 | basic_token * bt; 779 | basic_line * temp; 780 | 781 | if( !line ) return NULL; 782 | 783 | /* remove newline character at the end of the line */ 784 | stringChomp( line ); 785 | 786 | /* advance past initial whitespace */ 787 | while( isspace( *line ) ) line++; 788 | 789 | /* check for empty string */ 790 | if( strlen( line ) == 0 ) return NULL; 791 | 792 | /* okay! now, let's tokenize this mofo! */ 793 | bl = newLine(); 794 | 795 | /* now, check for a line number */ 796 | lno = extractNumberFromString( line ); 797 | if( strlen( lno )) { 798 | bl->lineNumber = atol( lno ); 799 | line += strlen( lno ); /* advance past line number */ 800 | } 801 | 802 | /* und overiteraten remainingtext, mit builden das tokenz */ 803 | while( *line != '\0' ) 804 | { 805 | /* 806 | char * newToken = stringUntilDifferentToken( line ); 807 | printf( "TOK [%s]\n", newToken ); 808 | line += strlen( newToken ); 809 | */ 810 | tstr = NULL; 811 | pstr = NULL; 812 | thisToken = tokenTypeFromString( line, 0 ); 813 | 814 | if( comment ) { 815 | while( isspace( *line ) ) line++; 816 | 817 | bt = newToken(); 818 | bt->token_id = kToken_Alpha; 819 | bt->c_data = strdup( line ); 820 | bl->tokens = appendToken( bl->tokens, bt ); 821 | line += strlen( line ); 822 | 823 | } else if( thisToken < kToken_User ) { 824 | /* it's a basic token */ 825 | basic_tlut * lutval = findLutTokenFromID( thisToken ); 826 | line += strlen( lutval->cmd ); 827 | 828 | bt = newToken(); 829 | bt->token_id = thisToken; 830 | bl->tokens = appendToken( bl->tokens, bt ); 831 | 832 | /* check for REM statement */ 833 | if( thisToken == 0x8f ) { comment = 1; } 834 | 835 | } else if( thisToken == kToken_Colon ) { 836 | /* COLON is a new statement. add it to the continuation value */ 837 | line++; /* move past the colon */ 838 | bl->continuation = tokenizeLine( line ); 839 | if( bl->continuation ) { 840 | bl->continuation->continuationParent = bl; 841 | } 842 | 843 | /* fill in the line numbers */ 844 | temp = bl->continuation; 845 | while( temp ) { 846 | temp->lineNumber = bl->lineNumber; 847 | temp = temp->continuation; 848 | } 849 | 850 | /* and force a completion of the line */ 851 | line += strlen( line ); 852 | 853 | } else if( thisToken == kToken_Space ) { 854 | /* eat it. */ 855 | line++; 856 | 857 | } else if( thisToken == kToken_Quote ) { 858 | /* it's a quoted string */ 859 | char * quotedText = extractQuotedFromString( line ); 860 | if( quotedText ) { 861 | char * new_c_data = strdup( quotedText ); 862 | line += strlen( quotedText ) + 2; /* +2 to account for quotes */ 863 | 864 | bt = newToken(); 865 | bt->token_id = thisToken; 866 | bt->c_data = new_c_data; 867 | bl->tokens = appendToken( bl->tokens, bt ); 868 | free( quotedText ); 869 | } 870 | 871 | } else if( thisToken == kToken_Number ) { 872 | char * theNumber = extractNumberFromString( line ); 873 | if( theNumber ) 874 | { 875 | line += strlen( theNumber ); 876 | pstr = strdup( theNumber ); 877 | 878 | bt = newToken(); 879 | bt->token_id = thisToken; 880 | bt->l_data = atol( theNumber ); 881 | bl->tokens = appendToken( bl->tokens, bt ); 882 | 883 | free( theNumber ); 884 | } 885 | 886 | } else { 887 | int sz = bytesToNextToken( line ); 888 | char * label = (char *) malloc( sizeof( char ) * (sz+1) ); 889 | 890 | strncpy( label, line, sz ); 891 | label[sz] = '\0'; 892 | 893 | bt = newToken(); 894 | bt->token_id = thisToken; 895 | bt->c_data = label; 896 | bl->tokens = appendToken( bl->tokens, bt ); 897 | 898 | line += sz; 899 | } 900 | 901 | 902 | /* display it */ 903 | tstr = findTokenStringFromID( thisToken ); 904 | 905 | if( pstr ) free( pstr ); 906 | } 907 | 908 | return bl; 909 | } 910 | 911 | /* consume a string , and return the tokens 912 | * if the line has a line number, it will get inserted to the program listing 913 | */ 914 | basic_line * consumeString( basic_program * bp, char * inString ) 915 | { 916 | basic_line * bl; 917 | 918 | if( !bp || !inString ) return NULL; 919 | 920 | bl = tokenizeLine( inString ); 921 | 922 | if( bl ) { 923 | if( bl->lineNumber >= 0 ) { 924 | removeLineNumber( bp, bl->lineNumber ); 925 | if( bl->tokens ) insertLine( bp, bl ); 926 | } 927 | } 928 | 929 | return bl; 930 | } 931 | -------------------------------------------------------------------------------- /engine/basic_tokenizer.h: -------------------------------------------------------------------------------- 1 | /* basic_tokenizer 2 | * 3 | * BleuLlama BASIC 4 | * Copyright (c) 2011 Scott Lawrence 5 | */ 6 | 7 | /* LICENSE: 8 | * 9 | * Copyright (C) 2011 by Scott Lawrence 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in 19 | * all copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | */ 29 | 30 | 31 | #ifndef __basic_tokenizer_h__ 32 | #define __basic_tokenizer_h__ 33 | 34 | #include "basic_variable.h" 35 | 36 | 37 | 38 | /* ******************************************************************************** */ 39 | 40 | /* The structure of the parsed in line is essentially: 41 | * 42 | * A list of LINEs 43 | * each LINE has a list of COMMAND 44 | * each COMMAND is a list of TOKENs 45 | */ 46 | 47 | typedef struct basic_token { 48 | int token_id; 49 | long l_data; 50 | char * c_data; 51 | struct basic_token * next; 52 | struct basic_token * shunt; 53 | 54 | struct basic_token * nextData; 55 | } basic_token; 56 | 57 | 58 | 59 | typedef struct basic_line { 60 | long lineNumber; 61 | int temp; /* used by renum */ 62 | struct basic_token * tokens; 63 | struct basic_line * next; 64 | struct basic_line * prev; 65 | 66 | struct basic_line * continuation; /* tree added with ':' operator */ 67 | struct basic_line * continuationParent; 68 | 69 | struct basic_token * nextData; 70 | } basic_line; 71 | #define kNoLineNumber -1 72 | 73 | 74 | 75 | #define kGosubLevels 256 76 | 77 | typedef struct basic_program { 78 | int exitNow; 79 | struct basic_line * listing; 80 | 81 | int traceOn; 82 | basic_line * runningLine; 83 | long nextLineNumberToExecute; 84 | 85 | basic_line * gosubList[kGosubLevels]; /* list of lines that called the gosub last */ 86 | int nGosubLevels; /* starts at 0 - # of gosub calls so far */ 87 | 88 | basic_variable * variables; 89 | 90 | basic_token * firstData; 91 | basic_token * currentData; 92 | } basic_program; 93 | 94 | 95 | /* ******************************************************************************** */ 96 | 97 | /* create new nodes */ 98 | basic_token * newToken( void ); 99 | basic_line * newLine( void ); 100 | basic_program * newProgram( void ); 101 | 102 | /* list manips */ 103 | basic_token * appendToken( basic_token * existingTokens, basic_token * theNewToken ); 104 | 105 | /* uncreate node trees */ 106 | void deleteTokens( basic_token * tokens ); 107 | void deleteLines( basic_line * lines ); 108 | void deleteProgram( basic_program * bp ); 109 | 110 | /* bookkeepping */ 111 | long countSteps( basic_line * lines ); 112 | long programSize( basic_program * bp ); 113 | void clearRuntime( basic_program * bp ); 114 | 115 | /* lineArgc 116 | * return the number of elements in the token list of this line 117 | */ 118 | int lineArgc( basic_line * bl ); 119 | 120 | /* argvToken 121 | * retrieves the token at the specified index 122 | */ 123 | basic_token * argvToken( basic_line * bl, int idx ); 124 | 125 | /* argvLong 126 | * retrieves the long data at the specified index 127 | */ 128 | long argvLong( basic_line * bl, int idx ); 129 | 130 | /* argvChar 131 | * retrieves the string data at the specified index 132 | */ 133 | char * argvChar( basic_line * bl, int idx ); 134 | 135 | /* argvTokenID 136 | * retrieves the string data at the specified index 137 | */ 138 | int argvTokenID( basic_line * bl, int idx ); 139 | 140 | /* argvMatch 141 | * match the token list to a passed in string rep 142 | * A = aplhanumeric (variable name) 143 | * Q = Quoted (immediate string) 144 | * 0 = number (immediate value) (zero) 145 | * ; = Semicolon 146 | * , = comma 147 | * - = dash 148 | * E = require end of list 149 | */ 150 | int argvMatch( basic_token * tl, char ** matchList ); 151 | 152 | 153 | basic_line * findLineNumber( basic_program * bp, long lineNo ); 154 | basic_line * findLowestLineNumber( basic_program * bp, long lineNo ); 155 | 156 | void removeLineNumber( basic_program * bp, long lineNo ); 157 | 158 | /* return a string suitible for LIST of the tokens */ 159 | char * catTokenToString( basic_token * tt, char * toBuf, int bufSize ); 160 | char * stringizeLine( basic_line * l, char * toBuf, int bufsize ); 161 | 162 | 163 | 164 | /* tokenize a string into the proper location */ 165 | basic_line * tokenizeLine( char * line ); 166 | 167 | /* consume a string , and return the tokens 168 | * if the line has a line nummber, it will get inserted to the program listing 169 | */ 170 | basic_line * consumeString( basic_program * bp, char * inString ); 171 | 172 | #endif 173 | -------------------------------------------------------------------------------- /engine/basic_variable.c: -------------------------------------------------------------------------------- 1 | /* basic_variable 2 | * 3 | * BleuLlama BASIC 4 | * Copyright (c) 2011 Scott Lawrence 5 | */ 6 | 7 | /* LICENSE: 8 | * 9 | * Copyright (C) 2011 by Scott Lawrence 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in 19 | * all copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | */ 29 | 30 | #include 31 | #include /* malloc */ 32 | #include /* strdup */ 33 | #include "basic_variable.h" 34 | 35 | #ifndef __BASIC_VARIABLE_H__ 36 | #define __BASIC_VARIABLE_H__ 37 | 38 | #define kVar_Undefined 0 39 | #define kVar_String 1 40 | #define kVar_Number 2 41 | 42 | typedef struct basic_variable { 43 | char * name; /* the lookup name */ 44 | int type; /* see kVar_* above */ 45 | long l_data; 46 | char * c_data; 47 | struct basic_variable * next; 48 | } basic_variable; 49 | #endif 50 | 51 | 52 | /* ********************************************************************** */ 53 | 54 | /* getVariableType 55 | * 56 | * determine the type based on the name 57 | */ 58 | int getVariableType( char * name ) 59 | { 60 | if( !name ) return kVar_Undefined; 61 | if( strlen(name) == 0 ) return kVar_Undefined; 62 | if( name[strlen(name)-1] == '$' ) return kVar_String; 63 | return kVar_Number; 64 | } 65 | 66 | /* newVaraible 67 | * 68 | * allocates a new variable and sets its type based on the name 69 | * Name -> numerical variable 70 | * Name$ -> string variable 71 | */ 72 | basic_variable * newVariable( char * name ) 73 | { 74 | basic_variable * bv; 75 | 76 | if( !name ) return NULL; 77 | 78 | bv = (basic_variable *) malloc( sizeof( basic_variable )); 79 | bv->name = strdup( name ); 80 | if( name[strlen(name)-1] == '$' ) { 81 | bv->type = kVar_String; 82 | } else { 83 | bv->type = kVar_Number; 84 | } 85 | bv->protect = 0; 86 | bv->l_data = 0; 87 | bv->c_data = strdup( "00" ); 88 | bv->forStartLine = 0; 89 | return bv; 90 | } 91 | 92 | 93 | /* deleteVariables 94 | * 95 | * delete the entire list of variables 96 | */ 97 | basic_variable * deleteVariables( basic_variable * list, int force ) 98 | { 99 | basic_variable * newList = NULL; 100 | basic_variable * next; 101 | 102 | while( list ) { 103 | next = list->next; 104 | if( list->protect && !force ) { 105 | /* protect item, don't delete it. prepend it. */ 106 | list->next = newList; 107 | newList = list; 108 | } else { 109 | /* delete it. */ 110 | if( list->c_data ) free( list->c_data ); 111 | if( list->name ) free( list->name ); 112 | free( list ); 113 | 114 | } 115 | list = next; 116 | } 117 | return newList; 118 | } 119 | 120 | 121 | /* clearVariableAux 122 | * 123 | * clears auxiliary data in the variables structure for runtime 124 | */ 125 | void clearVariableAux( basic_variable * list ) 126 | { 127 | while( list ) 128 | { 129 | list->forStartLine = 0; 130 | list = list->next; 131 | } 132 | } 133 | 134 | /* ********************************************************************** */ 135 | 136 | /* dumpVariables 137 | * 138 | * print out the list of variables for debugging 139 | */ 140 | void dumpVariables( basic_variable * list ) 141 | { 142 | printf( "Variable List:\n" ); 143 | #ifdef DEBUGWORTHY 144 | while( list ) 145 | { 146 | switch( list->type ) { 147 | case( kVar_String ): printf( "STR " ); break; 148 | case( kVar_Number ): printf( "NUM " ); break; 149 | case( kVar_Undefined ): printf( "UND " ); break; 150 | default: printf( "UNK " ); break; 151 | } 152 | 153 | printf( "%20s \"%s\" %ld\n", 154 | list->name?list->name:"UNK?", 155 | list->c_data?list->c_data:"UNK?", 156 | list->l_data ); 157 | 158 | list = list->next; 159 | } 160 | #endif 161 | 162 | while( list ) { 163 | switch( list->type ) { 164 | case( kVar_String ): 165 | printf( "%16s : \"%s\" %s\n", 166 | list->name?list->name:"ERR", 167 | list->c_data?list->c_data:"ERR", 168 | list->protect?"(protected)":"" 169 | ); 170 | break; 171 | case( kVar_Number ): 172 | printf( "%16s : %ld %s\n", 173 | list->name?list->name:"ERR", 174 | list->l_data, 175 | list->protect?"(protected)":"" 176 | ); 177 | break; 178 | 179 | } 180 | list = list->next; 181 | } 182 | } 183 | 184 | 185 | /* ********************************************************************** */ 186 | 187 | 188 | /* findVariable 189 | * 190 | * find the specified variable in the list 191 | * returns NULL if not found 192 | */ 193 | basic_variable * findVariable( basic_variable * list, char * name ) 194 | { 195 | basic_variable * el = list; 196 | 197 | while( el ) { 198 | if( !strcmp( name, el->name )) return el; 199 | el = el->next; 200 | } 201 | return NULL; 202 | } 203 | 204 | /* protectVariable 205 | * 206 | * find the variable and protect it. 207 | * if it does not exist, create it and protect it. 208 | */ 209 | basic_variable * protectVariable( basic_variable * list, char * name, int protect ) 210 | { 211 | basic_variable * v; 212 | 213 | if( !name ) return list; 214 | 215 | v = findVariable( list, name ); 216 | if( !v ) { 217 | list = setVariableSmart( list, name, "0" ); 218 | v = findVariable( list, name ); 219 | } 220 | 221 | if( v ) { 222 | v->protect = protect; 223 | } 224 | return list; 225 | } 226 | 227 | /* isProtectedVariable 228 | * 229 | * returns 1 if the variable is protected 230 | */ 231 | int isProtectedVariable( basic_variable * list, char * name ) 232 | { 233 | basic_variable * v; 234 | 235 | if( !name || !list ) return 0; 236 | 237 | v = findVariable( list, name ); 238 | if( v ) { 239 | return v->protect; 240 | } 241 | return 0; 242 | } 243 | 244 | 245 | /* ********************************************************************** */ 246 | 247 | /* setVariableNumber 248 | * 249 | * find and set the variable. If it doesn't exist yet, allocate it 250 | */ 251 | basic_variable * setVariableNumber( basic_variable * list, char * name, long value ) 252 | { 253 | basic_variable * tn; 254 | if( !name ) return list; 255 | 256 | /* look for the element */ 257 | tn = findVariable( list, name ); 258 | 259 | if( tn ) { 260 | /* if found, set the value, return list */ 261 | if( !tn->protect ) { 262 | tn->l_data = value; 263 | } 264 | } else { 265 | /* if not found, allocate, add, set, return list */ 266 | tn = newVariable( name ); 267 | tn->l_data = value; 268 | tn->next = list; 269 | list = tn; 270 | } 271 | return list; 272 | } 273 | 274 | 275 | /* setVariableString 276 | * 277 | * find and set the variable. If it doesn't exist yet, allocate it 278 | */ 279 | basic_variable * setVariableString( basic_variable * list, char * name, char * value ) 280 | { 281 | basic_variable * tn; 282 | if( !name || !value ) return list; 283 | 284 | /* look for the element */ 285 | tn = findVariable( list, name ); 286 | 287 | if( tn ) { 288 | /* if found, set the value, return list */ 289 | if( !tn->protect ) { 290 | if( tn->c_data ) free( tn->c_data ); 291 | tn->c_data = strdup( value ); 292 | } 293 | } else { 294 | /* if not found, allocate, add, set, return list */ 295 | tn = newVariable( name ); 296 | if( tn->c_data ) free( tn->c_data ); 297 | tn->c_data = strdup( value ); 298 | tn->next = list; 299 | list = tn; 300 | } 301 | return list; 302 | } 303 | 304 | 305 | /* setVariableSmart 306 | * 307 | * Sets a string or number as appropriate 308 | */ 309 | basic_variable * setVariableSmart( basic_variable * list, char * name, char * value ) 310 | { 311 | int type; 312 | if( !name || !value ) return list; 313 | 314 | type = getVariableType( name ); 315 | if( type == kVar_Number ) { 316 | return setVariableNumber( list, name, atol( value )); 317 | } 318 | return setVariableString( list, name, value ); 319 | } 320 | 321 | 322 | /* getVariableNumber 323 | * 324 | * find and return the specified number 325 | */ 326 | long getVariableNumber( basic_variable * list, char * name ) 327 | { 328 | basic_variable * tn; 329 | if( !list || !name ) return 0; 330 | 331 | /* look for the element */ 332 | tn = findVariable( list, name ); 333 | 334 | if( tn ) { 335 | /* if found, return the number */ 336 | return( tn->l_data ); 337 | } 338 | 339 | /* if not found, return 0 */ 340 | return 0; 341 | } 342 | 343 | 344 | /* getVariableString 345 | * 346 | * find and return the specified string 347 | */ 348 | char * getVariableString( basic_variable * list, char * name ) 349 | { 350 | basic_variable * tn; 351 | if( !list || !name ) return ""; 352 | 353 | /* look for the element */ 354 | tn = findVariable( list, name ); 355 | 356 | if( tn ) { 357 | /* if found, return the string */ 358 | return( tn->c_data ); 359 | } 360 | 361 | /* if not found, return "" */ 362 | return ""; 363 | } 364 | -------------------------------------------------------------------------------- /engine/basic_variable.h: -------------------------------------------------------------------------------- 1 | /* basic_variable 2 | * 3 | * BleuLlama BASIC 4 | * Copyright (c) 2011 Scott Lawrence 5 | */ 6 | 7 | /* LICENSE: 8 | * 9 | * Copyright (C) 2011 by Scott Lawrence 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in 19 | * all copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | */ 29 | 30 | #ifndef __BASIC_VARIABLE_H__ 31 | #define __BASIC_VARIABLE_H__ 32 | 33 | #define kVar_Undefined 0 34 | #define kVar_String 1 35 | #define kVar_Number 2 36 | 37 | typedef struct basic_variable { 38 | char * name; /* the lookup name */ 39 | int protect; /* 1 if not deletable, readonly */ 40 | int type; /* see kVar_* above */ 41 | long l_data; 42 | char * c_data; 43 | struct basic_variable * next; 44 | 45 | long forStartLine; /* for using the variable in a FOR */ 46 | } basic_variable; 47 | 48 | 49 | int getVariableType( char * name ); 50 | basic_variable * newVariable( char * name ); 51 | basic_variable * deleteVariables( basic_variable * list, int force ); 52 | void clearVariableAux( basic_variable * list ); 53 | 54 | void dumpVariables( basic_variable * list ); 55 | 56 | basic_variable * findVariable( basic_variable * list, char * name ); 57 | basic_variable * protectVariable( basic_variable * list, char * name, int protect ); 58 | int isProtectedVariable( basic_variable * list, char * name ); 59 | 60 | basic_variable * setVariableNumber( basic_variable * list, char * name, long value ); 61 | basic_variable * setVariableString( basic_variable * list, char * name, char * value ); 62 | 63 | /* this next one picks the right one, and does atol() if necessary */ 64 | basic_variable * setVariableSmart( basic_variable * list, char * name, char * value ); 65 | 66 | long getVariableNumber( basic_variable * list, char * name ); 67 | char * getVariableString( basic_variable * list, char * name ); 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /engine/basic_version.h: -------------------------------------------------------------------------------- 1 | /* BleuLlama BASIC 2 | * 3 | * A BASIC interpreter 4 | * Copyright (c) 2011 Scott Lawrence 5 | */ 6 | 7 | /* LICENSE: 8 | * 9 | * Copyright (C) 2011 by Scott Lawrence 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in 19 | * all copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | */ 29 | 30 | 31 | /* ************************************************************ 32 | * Version history 33 | */ 34 | 35 | #define BASIC_VERSION "0.15" 36 | #define BASIC_BUILDNAME "Intersections" 37 | /* 38 | * 0.15 2011-Feb-11 "Intersections" 39 | * Statements: DATA, READ, RESTORE, SWAP, IF/THEN 40 | * IF/THEN only allows for integer compares, not strings 41 | * 42 | * 0.14 2011-Feb-10 "Voice" 43 | * Babylon 5 (Voice Of The Resistance) 44 | * Statements: ON, FOR-NEXT-(STEP) 45 | * ON implemented. (ON _ GOTO _,_ ON _ GOSUB _,_ ) 46 | * LINE CONTINUATIONS!!! : 47 | * 48 | * 0.13 2011-Feb-9 "Spark Plug" 49 | * Aah Megamisama 50 | * Statements: PROTECT, UNPROTECT, VAC ALL, LABEL 51 | * LS renamed to FILES (Amiga BASIC) 52 | * CATALOG is the same as FILES (Atari BASIC) 53 | * argMatch created to lex parse 54 | * added variable protection 55 | * LOAD "filename",1 to automatically run it 56 | * comma in PRINT injects a tab. (AmigaBASIC) 57 | * 58 | * 0.12 2011-Feb-8 "Take out the Vorlon" 59 | * Babylon 5 60 | * Statements: HEAD, INPUT 61 | * file rearrangement, smarter value storage a,a$ -> auto 62 | * 63 | * 0.11 2011-Feb-7 "Pizza Stabs" 64 | * Was listening to ???? 65 | * fixed the memory crash bug in the tokenizer 66 | * Statements: VAC, VARIABLES 67 | * String evaluator 68 | * 69 | * 0.10 2011-Feb-6 "Superb Owl" 70 | * SuperBowl Sunday 71 | * Variable engine added 72 | * PRINT command first version. 73 | * RENUM fixed. LIST now does -NUM NUM- support 74 | * basic evaluation kinda working 75 | * 76 | * 0.09 2011-Feb-5 "Delenn" 77 | * Babylon 5 rewatching 78 | * Core backbone is complete 79 | * argc/argv for token lists implemented 80 | * LIST now supports TI-style line number listing LIST, LIST A, LIST A-B 81 | * Runtime execution basics 82 | * Statements: RUN, END, GOTO, RENUM, GOSUB, RETURN, TRON, TROFF 83 | * readme.txt added 84 | * 85 | * 0.08 2011-Feb-2 "Valen" 86 | * Babylon 5 rewatching 87 | * Rewrite started 88 | * new core (linked lists), tokenizer, stringizer, structures 89 | * Statements: bye, rem, ls, load, save, list info 90 | * 91 | * 0.07 2011-Feb-2 92 | * Changed "quit" to Atari 8Bit "bye" 93 | * 94 | * 0.06 2011-Feb-1 95 | * Proper line number insertion/removal 96 | * Statements: help, hex 97 | * Fixed: rem, : ; tokens 98 | * moved the globals into a handle 99 | * 100 | * 0.05 2011-Jan-31 101 | * Tokenizer 102 | * Statements: load, save, list, new, ls, info, quit 103 | * 104 | * 0.00-0.04 105 | * The first version, with tokenized bytecode rather than linked list 106 | * Scrapped, and mostly rewritten as this version. 107 | */ 108 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* BleuLlama BASIC 2 | * 3 | * A BASIC interpreter 4 | * Copyright (c) 2011 Scott Lawrence 5 | */ 6 | 7 | /* TODO: 8 | REM command needs to store to end of line 9 | */ 10 | 11 | /* LICENSE: 12 | * 13 | * Copyright (C) 2011 by Scott Lawrence 14 | * 15 | * Permission is hereby granted, free of charge, to any person obtaining a copy 16 | * of this software and associated documentation files (the "Software"), to deal 17 | * in the Software without restriction, including without limitation the rights 18 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | * copies of the Software, and to permit persons to whom the Software is 20 | * furnished to do so, subject to the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be included in 23 | * all copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 31 | * THE SOFTWARE. 32 | */ 33 | 34 | /* Reference: 35 | * http://www.devili.iki.fi/Computers/Commodore/C64/Programmers_Reference/Chapter_1/page_008.html 36 | * http://www.commodore.ca/manuals/funet/cbm/programming/cbm-basic-tokens.txt 37 | * http://www.c64-wiki.com/index.php/C64-Commands 38 | */ 39 | 40 | #include 41 | #include /* exit */ 42 | #include /* signal catching */ 43 | #include /* strcmp */ 44 | #include /* mkdir on OS X */ 45 | #include /* mkdir on MinGW */ 46 | #include /* for nanosleep */ 47 | 48 | #include "basic_version.h" 49 | #include "basic_errors.h" 50 | #include "basic_tokenizer.h" 51 | #include "basic_evaluator.h" 52 | #include "basic_string.h" 53 | 54 | /* because windows is braindead */ 55 | #if defined(_WIN32) || defined( __MINGW32__ ) 56 | #ifndef SIGHUP 57 | #define SIGHUP -9 58 | #endif 59 | #endif 60 | 61 | 62 | /************************************************************************/ 63 | /* misc info */ 64 | 65 | 66 | void startInfo( basic_program * bp ) 67 | { 68 | cmd_info( bp, NULL ); 69 | printf( "Ready.\n" ); 70 | } 71 | 72 | 73 | /************************************************************************/ 74 | /* Command line, interactive stuff */ 75 | 76 | int breakCount = 0; 77 | 78 | /* signalBreak 79 | * 80 | * our unix signal handler 81 | */ 82 | void signalBreak( int sig ) 83 | { 84 | if( sig == SIGHUP || sig == SIGINT ) { 85 | printf( "BREAK %d/5!\n", breakCount+1 ); 86 | breakCount++; 87 | if( breakCount >= 5 ) { 88 | exit( 0 ); 89 | } 90 | } 91 | } 92 | 93 | 94 | /* main 95 | * 96 | * do some main stuff. 97 | */ 98 | int main( int argc, char ** argv ) 99 | { 100 | basic_program * bp; 101 | basic_line * bl; 102 | char lnbuf[kInputBufferSize]; 103 | 104 | /* set up our ctrl-c catchers */ 105 | (void) signal( SIGINT, signalBreak ); 106 | (void) signal( SIGHUP, signalBreak ); 107 | 108 | /* make the documents directory, if we don't already have it */ 109 | #if defined(_WIN32) || defined( __MINGW32__ ) 110 | mkdir( kDocumentsDirectory ); 111 | #else 112 | mkdir( kDocumentsDirectory, 0755 ); 113 | #endif 114 | 115 | /* set up our program space */ 116 | bp = newProgram(); 117 | if( !bp ) { 118 | errorReport( kErrorMalloc, 1 ); 119 | return 0; 120 | } 121 | 122 | /* dump out version info and stuff */ 123 | startInfo( bp ); 124 | 125 | /* and loop in our minishell! */ 126 | do { 127 | /* print our prompt, get user input */ 128 | fprintf( stdout, ">: " ); fflush( stdout ); 129 | fgets( lnbuf, kInputBufferSize, stdin); 130 | 131 | /* tokenize and absorb the string into our listing */ 132 | bl = consumeString( bp, lnbuf ); 133 | 134 | /* if it was not absorbed, it has a negative line number */ 135 | if( bl ) { 136 | if( bl->lineNumber == kNoLineNumber ) { 137 | /* so immediately evaluate it. */ 138 | evaluateLine( bp, bl ); 139 | /* and any continuations it might have. */ 140 | while( bl->continuation ) { 141 | bl = bl->continuation; 142 | evaluateLine( bp, bl ); 143 | } 144 | deleteLines( bl ); 145 | } else { 146 | /* let's print out the line */ 147 | /* and reuse lnbuf */ 148 | if( bp->traceOn ) { 149 | stringizeLine( bl, lnbuf, 1024 ); 150 | printf( "%s\n", lnbuf ); 151 | } 152 | } 153 | } 154 | 155 | /* reset our break counter */ 156 | breakCount = 0; 157 | 158 | /* and run the program, if we should... */ 159 | while( run_poll( bp ) && !breakCount ) 160 | { 161 | /* for now... */ 162 | run_poll( bp ); 163 | 164 | /* if we're doing a trace, display the line */ 165 | if( bp->traceOn ) { 166 | dumpVariables( bp->variables ); 167 | } 168 | 169 | /* let's not kill the system, and give it a retroey speed */ 170 | usleep( 1 * 1000 ); 171 | } 172 | 173 | /* also, dump the variables each line entered */ 174 | if( bp->traceOn ) { 175 | dumpVariables( bp->variables ); 176 | } 177 | 178 | /* force the run poll to not continue */ 179 | /* HACK for now. */ 180 | /* need to figure this out. */ 181 | bp->runningLine = NULL; 182 | bp->nextLineNumberToExecute = -1; 183 | 184 | } while( !bp->exitNow ); 185 | 186 | return 0; 187 | } 188 | -------------------------------------------------------------------------------- /programs/continuation.bas: -------------------------------------------------------------------------------- 1 | 10 a = 0 2 | 20 PRINT a ; " " ; : a = a + 1 : GOTO 20 3 | -------------------------------------------------------------------------------- /programs/datatest.bas: -------------------------------------------------------------------------------- 1 | 10 REM *** NUMBERS 2 | 20 RESTORE 3 | 30 FOR z = 0 TO 10 4 | 40 READ a 5 | 50 PRINT z ; ": " ; a 6 | 60 NEXT z 7 | 70 REM *** Strings: 8 | 80 RESTORE textStrings 9 | 90 READ b$ : 90 READ c$ 10 | 100 PRINT b$ ; " ... " ; c$ ; "!" 11 | 1000 REM 12 | 1010 REM ************ DATA 13 | 1020 REM 14 | 1030 DATA 2 , 4 , 6 , 8 , 10 15 | 1040 DATA 1 , 3 , 5 , 7 , 9 16 | 1045 LABEL textStrings 17 | 1050 DATA "hello" , "goodbye" 18 | -------------------------------------------------------------------------------- /programs/fib.bas: -------------------------------------------------------------------------------- 1 | 10 REM fibonacci sequence 2 | 20 LET a = 1 3 | 30 LET b = 1 4 | 35 FOR z = 0 TO 20 5 | 40 LET c = a + b 6 | 45 PRINT a ; " " ; 7 | 50 LET a = b 8 | 60 LET b = c 9 | 70 NEXT z 10 | -------------------------------------------------------------------------------- /programs/for.bas: -------------------------------------------------------------------------------- 1 | 10 FOR a = 0 TO 3 2 | 20 FOR b = 10 TO 13 3 | 30 PRINT " " ; a ; " " ; b 4 | 40 NEXT b 5 | 50 NEXT a 6 | -------------------------------------------------------------------------------- /programs/gosubs.bas: -------------------------------------------------------------------------------- 1 | 100 REM force gosub to fail 2 | 110 A = 0 3 | 120 PRINT A 4 | 130 A = A + 1 5 | 140 GOSUB 120 6 | -------------------------------------------------------------------------------- /programs/look.bas: -------------------------------------------------------------------------------- 1 | 10 PRINT "LOOK AROUND YOU " ; 2 | 20 GOTO 10 3 | -------------------------------------------------------------------------------- /programs/ontest.bas: -------------------------------------------------------------------------------- 1 | 2 | 10 a = 0; 3 | 20 print "A = "; A 4 | 30 gosub SplitIt 5 | 40 A=A+1 6 | 50 goto 20 7 | 8 | 9 | 60 REM ****************** 10 | 70 LABEL SplitIt 11 | 80 ON A GOSUB 100,200,300,FourHundred,500 12 | 90 RETURN 13 | 14 | 15 | 100 REM ****************** 16 | 110 PRINT "Line 100" 17 | 120 RETURN 18 | 19 | 200 REM ****************** 20 | 210 PRINT "Line 200" 21 | 220 RETURN 22 | 23 | 300 REM ****************** 24 | 310 PRINT "Line 300" 25 | 320 RETURN 26 | 27 | 400 REM ****************** 28 | 410 LABEL FourHundred 29 | 420 PRINT "Line 400" 30 | 430 RETURN 31 | 32 | 500 REM ****************** 33 | 510 PRINT "End." 34 | 520 END 35 | -------------------------------------------------------------------------------- /programs/sample1.bas: -------------------------------------------------------------------------------- 1 | 10 REM ************************** 2 | 20 REM a sample basic file 3 | 30 REM ************************** 4 | 40 INPUT "Starting value" ; a 5 | 50 PRINT a ; " " ; 6 | 60 a = a + 1 7 | 70 GOTO 50 8 | -------------------------------------------------------------------------------- /programs/simplecontinuation.bas: -------------------------------------------------------------------------------- 1 | 10 PRINT : PRINT "Hello" ; 2 | 20 PRINT ", " : PRINT "World" ; 3 | 30 PRINT "!" 4 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | BleuLlama Basic 3 | vx.xx 4 | 2011-xxx-xx 5 | 6 | Scott Lawrence 7 | @yorgle 8 | http://umlautllama.com 9 | 10 | 11 | ---------------------------------------- 12 | Overview 13 | 14 | BleuLlama Basic (blbasic) is an implementation of the BASIC language, with a few 15 | specific goals. 16 | 17 | 1) Load, Run, and Save programs that are similar to existing BASIC 18 | programs for traditional classic systems. In particular: 19 | 20 | - Commodore 64 21 | - Tandy/Casio/Sharp Pocket Computers 22 | - Texas Instruments TI 99/4A 23 | - Atari 400/800 24 | 25 | Minus of course, the platform specific stuff (expecting certain 26 | values at various PEEK locations, or device-specific behaviors.) 27 | 28 | 2) Portable, and easy to port to new host systems. 29 | 30 | 3) Open, unencumbered license. (MIT License) 31 | 32 | 33 | ---------------------------------------- 34 | Modes 35 | 36 | Like most implementations of BASIC, there are different modes that 37 | are imposed when using the interface. The user does not switch 38 | between them like one does on pocket computers, but rather their 39 | operations happen based on the input the user provides for each 40 | line entered. 41 | 42 | -- Immediate Mode -- 43 | 44 | This is encountered when the user types a command at the prompt. 45 | For example: 46 | >: LIST [RETURN] 47 | >: PRINT A [RETURN] 48 | >: A = A + 2 [RETURN] 49 | 50 | 51 | -- Edit Program Mode -- 52 | 53 | This is encountered when the user puts a line number before a 54 | command. For example: 55 | 56 | >: 10 PRINT A [RETURN] 57 | >: 20 A = A + 2 [RETURN] 58 | 59 | The user can also replace lines with new ones: 60 | 61 | >: 10 PRINT A [RETURN] 62 | >: 10 PRINT B [RETURN] 63 | 64 | The user can also eliminate lines from their program by entering a 65 | line number with no commands: 66 | 67 | >: 10 [RETURN] 68 | 69 | Unlike many implementations of BASIC, just about all of the 70 | traditionally "immediate" calls can be made in Edit Program mode. 71 | That is, the user can actually specify a line such as: 72 | 73 | >: 10 LIST [RETURN] 74 | 75 | Which is completely valid. Often these things are flagged out as 76 | being invalid, but by allowing such things, it simplifies the engine 77 | quite a bit, and adds flexibility to the user. 78 | 79 | 80 | ---------------------------------------- 81 | Usage Notes 82 | 83 | The line is scanned along its length to look for "tokens". This 84 | means that as long as you observe what tokens/keywords you are 85 | entering, you can eliminate whitespace. 86 | 87 | >: 10PRINT"HELLO" 88 | 89 | expands to: 10 PRINT "HELLO" 90 | 91 | >: 10PRINTSINA+3 92 | 93 | expands to: 10 PRINT SIN A + 3 94 | 95 | However, if you mistype an expected token, it will likely be 96 | assumed to be a variable. (Also note that variable names are not 97 | limited to just one or two characters.) 98 | 99 | >: 10PRINTSONA+3 100 | 101 | expands to: 10 PRINT SONA + 3 102 | 103 | If you start a string, it will continue through known keywords until 104 | it hits whitespace or a non-alphanumeric character. 105 | 106 | >:10PRINTOSIN+3 107 | 108 | expands to: 10 PRINT OSIN + 3 109 | 110 | 111 | No limits have been set otherwise on the internal implementation 112 | to make it run on classic systems. For example, the program is not 113 | stored specifically as a flat bytecode array, but rather as modern 114 | linked lists. 115 | 116 | Since all function calls (where applicable) pass a handle to a 117 | container which holds the program and variables, you can run multiple 118 | basic interpreters in the same codespace/thread. 119 | 120 | Likewise, since all methods are called explicitly, they can be 121 | wrapped with the appropriate semaphores and run thread-safe. 122 | 123 | The interface in this package is a demonstrative interface. It is 124 | a simple command shell, but it is easily expandable/replaceable 125 | with another interface specific to the system it is being ported 126 | to. That said, it is a fully functional BASIC shell. 127 | 128 | 129 | ---------------------------------------- 130 | Language Notes 131 | 132 | Lines can have multiple statements on each list. A colon ":" should be 133 | used to separate statements. For example: 134 | 135 | 136 | 10 w$ = "World" 137 | 20 PRINT : PRINT "Hello"; 138 | 30 PRINT ", "; 139 | 40 PRINT w$; : PRINT "!" 140 | 141 | displays: 142 | 143 | Hello, World! 144 | 145 | Entire routines can also be put on a single line 146 | 147 | 100 a = a - 100 : RETURN 148 | 149 | 150 | 151 | On some classic systems, line numbers had to be between 1 and 9999, 152 | or 1 and 32767. In this implementation, line numbers must be between 153 | 1 and 2147483647. 154 | 155 | 156 | ---------------------------------------- 157 | Command List 158 | 159 | Most commands can be run in run mode by just typing them in at the 160 | user prompt, as well as in program mode. Where applicable, examples 161 | of usage have also been provided here. 162 | 163 | 164 | -- File and Listing commands -- 165 | 166 | LOAD "FILENAME.BAS" 167 | Loads the specified filename from the "programs" folder. 168 | LOAD "FILENAME.BAS",1 169 | Loads the specified filename from the "programs" folder 170 | and then start running it. 171 | 172 | SAVE "FILENAME.BAS" 173 | Saves the program in memory to the "programs" folder. 174 | 175 | LIST 176 | Prints to the screen the program in memory. 177 | LIST LINE 178 | Prints to the screen the specific line specified. 179 | LIST MIN- 180 | lists all lines starting at and including MIN 181 | LIST -MAX 182 | Lists all lines up to and including MAX 183 | LIST MIN MAX 184 | LIST MIN-MAX 185 | Prints to the screen the inclusive range specified. 186 | 187 | FILES 188 | CATALOG 189 | Prints a list of files available in the "programs" folder. 190 | 191 | NEW 192 | Erases the program in memory, and clears variables. 193 | 194 | RENUM 195 | renumber the program from 100, increments of 10 196 | RENUM START 197 | renumber the program from START, increments of 10 198 | RENUM START STEP 199 | renumber the program from START, increments of STEP 200 | 201 | 202 | -- System commands -- 203 | 204 | BYE 205 | Quit out of the interpreter back to your command shell. 206 | 207 | HELP 208 | Display a list of commands and keywords known. 209 | 210 | INFO 211 | Prints out version information, and number of "steps" used. 212 | 213 | VARIABLES 214 | Prints out a list of all defined variables and their contents. 215 | 216 | VAC ALL 217 | Clear all variables in memory. 218 | VAC 219 | Clear only unprotected variables. 220 | 221 | PROTECT var 222 | PROTECT var$ 223 | Protects a variable from writing. (make it read-only) 224 | 225 | UNPROTECT var 226 | UNPROTECT var$ 227 | Unprotects a variable for writing. (make it read-write) 228 | 229 | SWAP var,var 230 | SWAP var$,var$ 231 | Swap two variables in-place 232 | 233 | 234 | -- Commands -- 235 | 236 | RUN 237 | Start automatic program execution 238 | 239 | TRON 240 | Turn TRace ON 241 | 242 | TROFF 243 | Turn TRace OFF 244 | 245 | 246 | GOTO LINENO 247 | continues runtime operation from the specified line number 248 | 249 | GOSUB LINENO 250 | calls a subroutine at line LINENO 251 | Max depth of 256 levels. 252 | NOTE: Casio-Tandy=8 levels. C64=24 levels, miSoft=128 levels 253 | 254 | ON a GOTO 10,20,var,var 255 | ON a 10,20,var,var 256 | ON a GOSUB 10,20,var,var 257 | Checks the value of 'a', and picks the corresponding value 258 | in the list, (first item is 1) and GOTOs it or GOSUBs it. 259 | If "GOTO" or "GOSUB" is omitted, it is implied to be a "GOTO" list. 260 | Both the switcher and the list can contain explicit numbers or 261 | variables or labels. It the variable is <1 or >number of things on 262 | the list, execution just continues on the next line. 263 | 264 | IF a b THEN c 265 | IF a b THEN GOTO c 266 | IF a b THEN GOSUB c 267 | Compares a with b. If the comparison is true, then the 268 | clause is executed with either a GOTO or GOSUB. If GOTO is 269 | omitted, it is implied. the comparisons that are valid are: 270 | = is equal to <> is not equal to 271 | < is less than <= is less or equal to 272 | > is greater than >= is greater or equal to 273 | 274 | FOR a = b TO c 275 | FOR a = b TO c STEP d 276 | NEXT a 277 | Iterative FOR-NEXT loop. b, c, d can be immediate numbers, or 278 | variables containing the desired numbers. 'a' will be managed 279 | by the foor loop mechanism. 280 | NOTE: due to current implementations: 281 | d must be positive 282 | b must be less than c 283 | the for loop will ALWAYS exceute through once. 284 | 285 | LABEL name 286 | Creates a label to be used with GOTO or GOSUB. They 287 | internally create protected variables, and are regenerated 288 | at runtime. 289 | Note, that if you change or remove the labels between runs, 290 | there might be ghosts of the old labels in the variables 291 | list. 292 | 293 | RETURN 294 | return from a subroutine 295 | 296 | PRINT 297 | PRINT 42 298 | PRINT "hello" 299 | PRINT "hello " ; yourName$ 300 | Prints a list of things to the screen. All things on the list 301 | will be printed joined together. If a semicolon is put at the 302 | end of the list, no newline will be printed out. Subsequent 303 | PRINT or other output will appear on that line. 304 | 305 | INPUT a 306 | INPUT a$ 307 | INPUT "Prompt " ; a$ 308 | INPUT "Prompt ";a 309 | Prompts the user for input, which will be stored into the 310 | variable specified. All INPUT statements will print a "? ", 311 | after the user specified prompt if one is provided. 312 | 313 | DATA 1,2,3 314 | DATA "a","b","c",d 315 | Store a list of data to be read out later. 316 | 317 | READ a 318 | READ a$ 319 | Read from the current data item into variable 'a' or 'a$'. 320 | This will also advance to the next data item. 321 | 322 | RESTORE 323 | Reset the data item list to the first data item in the program. 324 | RESTORE 100 325 | RESTORE x 326 | Reset the data item list to the next item on the specified line. 327 | 'x' can be a variable containing a line number, or a label. 328 | 329 | 330 | ---------------------------------------- 331 | Known Issues 332 | 333 | 1) Order of operations is not implemented yet. Operations will happen 334 | in the order they're on the line, rather than: 335 | Functions (SIN, COS, TAN, etc) 336 | Powers 337 | Multiplication and Division 338 | Addition and Subtraction 339 | 340 | 2) Arrays are not implemented yet. 341 | 342 | 3) RENUM command is faulty. (currently disabled) 343 | 344 | ---------------------------------------- 345 | Errors 346 | 347 | INCOMPLETE, INCORRECT 348 | THESE NEED TO BE REVISITED. 349 | 350 | 100 LOAD couldn't open file for reading 351 | 101 LOAD parameters are invalid 352 | 102 LOAD filename is invalid 353 | 103 SAVE couldn't open file for saving 354 | 104 SAVE couldn't save with no program in memory 355 | 105 SAVE filename not specified 356 | 106 SAVE filename is invalid 357 | 358 | 107 RENUM invalid number of parameters 359 | 108 RENUM numbers specified are invalid 360 | 109 LIST invalid number of parameters 361 | 110 RUN invalid number of parameters 362 | 363 | 111 GOTO invalid number of parameters 364 | 112 GOTO line number specified was not found 365 | 113 GOSUB invalid number of parameters 366 | 114 GOSUB max depth of gosubs has been reached 367 | 115 GOSUB target line number was not found 368 | 369 | 116 RETURN invalid number of parameters 370 | 117 RETURN no return value in stack to go to 371 | 372 | 373 | ---------------------------------------- 374 | Platforms 375 | 376 | Tested platforms: 377 | 378 | OS X 10.6.6, GCC 4.2.1 (Mac) 379 | MinGW/MSYS (Windows) 380 | 381 | 382 | 383 | ---------------------------------------- 384 | License 385 | 386 | (MIT License) 387 | 388 | Copyright (C) 2011 by Scott Lawrence 389 | 390 | Permission is hereby granted, free of charge, to any person obtaining a copy 391 | of this software and associated documentation files (the "Software"), to deal 392 | in the Software without restriction, including without limitation the rights 393 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 394 | copies of the Software, and to permit persons to whom the Software is 395 | furnished to do so, subject to the following conditions: 396 | 397 | The above copyright notice and this permission notice shall be included in 398 | all copies or substantial portions of the Software. 399 | 400 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 401 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 402 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 403 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 404 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 405 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 406 | THE SOFTWARE. 407 | -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | error testing is messy 2 | 3 | Organization of tokens is messy and arbitrary 4 | 5 | Synonyms: REM ' 6 | PRINT ? 7 | BYE QUIT, EXIT 8 | 9 | REM should go to end of line OR to ':' 10 | 11 | 12 | Evaluation notes: 13 | Figure out how to mark evaluation lists for shunting 14 | 15 | Evaluate RPN (output from shunting) 16 | 17 | 18 | RENUM issues: 19 | 20 | ON - check for adjusting line numbers. this could get messy. 21 | 22 | continuations need line number adjustment as well. 23 | 24 | RENUM might have to work backwards. this could get messy. 25 | 26 | Perhaps basic_line needs a flag of what this line number used 27 | to be, then do a second pass over the full list to adjust all of the numbers 28 | 29 | 30 | goto out of FOR! 31 | --------------------------------------------------------------------------------