├── examples ├── calculation.basic └── banking.basic ├── README.md └── basic.php /examples/calculation.basic: -------------------------------------------------------------------------------- 1 | ' BASIC example file 2 | ' 3 | ' by Jamie Rumbelow 4 | ' http://jamierumbelow.net 5 | 6 | ' I'm not a BASIC programmer, so don't expect much of this. 7 | ' It's simply a way of demonstrating a bit of what my Basic 8 | ' interpreter can do. 9 | 10 | pi = (314159265 / 100000000) 11 | 12 | print "π: " + pi 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Basic 2 | ===== 3 | 4 | I got bored one day so I wrote a BASIC parser. It's a pretty basic dialect BASIC with no support for functions or anything complex like that. The usage is simple 5 | 6 | php basic.php name_of_basic_file.basic 7 | 8 | Check out the example file included in this repo for the syntax. I've commented the code a lot; writing this was a learning exercise, and reading it could be pretty useful too if you're interested in writing parsers. -------------------------------------------------------------------------------- /examples/banking.basic: -------------------------------------------------------------------------------- 1 | ' BASIC example file 2 | ' 3 | ' by Jamie Rumbelow 4 | ' http://jamierumbelow.net 5 | 6 | ' I'm not a BASIC programmer, so don't expect much of this. 7 | ' It's simply a way of demonstrating a bit of what my Basic 8 | ' interpreter can do. 9 | 10 | ' Start off by welcoming the user 11 | goto welcome 12 | 13 | ' The welcome section 14 | welcome: 15 | print "Welcome to the BASIC Bank" 16 | print "=========================" 17 | print "" 18 | 19 | goto new_account 20 | 21 | ' Setup a new account 22 | new_account: 23 | print "What would you like to name your account?" 24 | 25 | input account_name 26 | account_value = 0 27 | 28 | goto main 29 | 30 | ' Withdraw 31 | withdraw: 32 | print "How much would you like to withdraw?" 33 | 34 | input withdrawal_amount 35 | account_value = account_value - withdrawal_amount 36 | 37 | goto main 38 | 39 | ' Deposit 40 | deposit: 41 | print "How much would you like to deposit?" 42 | 43 | input deposit_amount 44 | account_value = account_value + deposit_amount 45 | 46 | goto main 47 | 48 | ' The main loop 49 | main: 50 | print "" 51 | print "Welcome to your " + account_name + " bank account." 52 | print "" 53 | print "You have £" + account_value + " in your account" 54 | print "" 55 | print "What would you like to do? (withdraw, deposit, exit)" 56 | input command 57 | 58 | if command = "exit" then leave 59 | if command = "withdraw" then withdraw 60 | if command = "deposit" then deposit 61 | 62 | ' Leave 63 | leave: 64 | exit 65 | -------------------------------------------------------------------------------- /basic.php: -------------------------------------------------------------------------------- 1 | 8 | * @version 1.0.0 9 | * @copyright Copyright (c) 2010 Jamie Rumbelow 10 | * @license MIT License 11 | * @package basic 12 | **/ 13 | 14 | /** 15 | * The main Basic class 16 | * 17 | * @package basic 18 | * @author Jamie Rumbelow 19 | **/ 20 | class Basic { 21 | 22 | /** 23 | * A big long list of constants for tokens. Each token represents 24 | * something in the code 25 | */ 26 | const TOKEN_WORD = 1; 27 | const TOKEN_NUMBER = 2; 28 | const TOKEN_STRING = 3; 29 | const TOKEN_LABEL = 4; 30 | const TOKEN_EQUALS = 5; 31 | const TOKEN_OPERATOR = 6; 32 | const TOKEN_LEFT_PARENTHESIES = 7; 33 | const TOKEN_RIGHT_PARENTHESIES = 8; 34 | const TOKEN_EOF = 9; 35 | 36 | /** 37 | * These constants represent the tokeniser's current state; the 38 | * tokeniser is built as a state machine. To clarify, if we are 39 | * tokenising and we come across a string, we need to remember 40 | * that we're inside a string. We then need to break out of it when 41 | * the string is terminated. 42 | */ 43 | const S_DEFAULT = 1; 44 | const S_WORD = 2; 45 | const S_NUMBER = 3; 46 | const S_STRING = 4; 47 | const S_COMMENT = 5; 48 | 49 | /** 50 | * The Basic class acts as the lexer and intepreter, so we want to keep 51 | * track of a few bits during interpretation, including variables 52 | */ 53 | static public $variables = array(); 54 | static public $statements = array(); 55 | static public $labels = array(); 56 | static public $current_statement = 0; 57 | 58 | /** 59 | * This function runs the BASIC source through the interpretation 60 | * pipeline; tokenising, parsing and executing the code. 61 | * 62 | * @param string $source The BASIC source code 63 | * @return void 64 | * @author Jamie Rumbelow 65 | */ 66 | public function interpret($source) { 67 | // Tokenise 68 | $tokens = $this->tokenise($source); 69 | 70 | // Parse 71 | $parser = new Parser($tokens); 72 | $parser->parse(); 73 | 74 | // Loop through the statements and execute them 75 | while (self::$current_statement < count(self::$statements)) { 76 | self::$statements[self::$current_statement]->execute(); 77 | self::$current_statement++; 78 | } 79 | } 80 | 81 | /** 82 | * This function tokenises the source code. Tokenising, or lexing, involves 83 | * looking through the source code and replacing the syntax with tokens that the 84 | * parser can read quickly. Each token represents something meaningful to 85 | * the program, like a variable, operator or string. 86 | * 87 | * @param string $source The source code 88 | * @return array $tokens The array of tokens 89 | * @author Jamie Rumbelow 90 | **/ 91 | public function tokenise($source) { 92 | // Our final array of tokens 93 | $tokens = array(); 94 | 95 | // The current state of our tokeniser 96 | $state = S_DEFAULT; 97 | $token = ""; 98 | 99 | // Keep a one-to-one mapping of all the single-character tokens here 100 | // in an array that we can pull out later. 101 | $character_tokens = array( 102 | "=" => TOKEN_EQUALS, 103 | "+" => TOKEN_OPERATOR, 104 | "-" => TOKEN_OPERATOR, 105 | "*" => TOKEN_OPERATOR, 106 | "/" => TOKEN_OPERATOR, 107 | "<" => TOKEN_OPERATOR, 108 | ">" => TOKEN_OPERATOR, 109 | "(" => TOKEN_LEFT_PARENTHESIES, 110 | ")" => TOKEN_RIGHT_PARENTHESIES 111 | ); 112 | 113 | // Scan through each character of the source code at 114 | // a time and build up a tokenised representation of the source 115 | for ($i = 0; $i < strlen($source); $i++) { 116 | // Get the current character 117 | $char = $source{$i}; 118 | 119 | // Switch the state 120 | switch ($state) { 121 | 122 | /** 123 | * The "default" state: routine code parsing. We can use this opportunity 124 | * to check for single-char tokens, as well as change state if we need to. 125 | */ 126 | case S_DEFAULT: 127 | // Is our character inside the single character tokens array? If 128 | // so, get the token type and add a new token. 129 | if (isset($character_tokens[$char])) { 130 | $tokens[] = new Token($char, $character_tokens[$char]); 131 | } 132 | 133 | // Is our character a letter? If it is, we're about to start a 'word'. 134 | // Words can represent either a label (for gotos) or a variable 135 | else if (ctype_alpha($char)) { 136 | $token .= $char; 137 | $state = S_WORD; 138 | } 139 | 140 | // Is our character a digit? If so, we're about to start a number. 141 | else if (is_numeric($char)) { 142 | $token .= $char; 143 | $state = S_NUMBER; 144 | } 145 | 146 | // Is our character a quote? We're about to start a string 147 | else if ($char == '"') { 148 | $state = S_STRING; 149 | } 150 | 151 | // Is our character a single quote? Comment time 152 | else if ($char == "'") { 153 | $state = S_COMMENT; 154 | } 155 | 156 | break; 157 | 158 | /** 159 | * The "word" state. We check the next character. If it's a letter or digit, 160 | * continue the word. If it ends with a colon, it's a label, otherwise it's a word. 161 | */ 162 | case S_WORD: 163 | // Is our character a letter or digit? If it is, we're continuing the word 164 | if (ctype_alnum($char) || $char == '_') { 165 | $token .= $char; 166 | } 167 | 168 | // Is our character a colon? It's a label 169 | else if ($char == ":") { 170 | $tokens[] = new Token($token, TOKEN_LABEL); 171 | $token = ""; 172 | $state = S_DEFAULT; 173 | } 174 | 175 | // Our word has ended 176 | else { 177 | // Add the token 178 | $tokens[] = new Token($token, TOKEN_WORD); 179 | 180 | // Reset the state 181 | $token = ""; 182 | $state = S_DEFAULT; 183 | 184 | // Reprocess the current character in S_DEFAULT 185 | $i--; 186 | } 187 | 188 | break; 189 | 190 | /** 191 | * The number state. If the next character is numeric, we're continuing the number. 192 | * Otherwise, add the new token. 193 | */ 194 | case S_NUMBER: 195 | // Is it numeric? 196 | if (is_numeric($char)) { 197 | $token .= $char; 198 | } 199 | 200 | // We're done. Add the token 201 | else { 202 | // Add the token 203 | $tokens[] = new Token($token, TOKEN_NUMBER); 204 | 205 | // Reset the state 206 | $token = ""; 207 | $state = S_DEFAULT; 208 | 209 | // Reprocess the current character in S_DEFAULT 210 | $i--; 211 | } 212 | 213 | break; 214 | 215 | /** 216 | * The string state. Any character can be in a string except a quote, so whack it on. 217 | */ 218 | case S_STRING: 219 | // Is it a quote? 220 | if ($char == '"') { 221 | // Add the token 222 | $tokens[] = new Token($token, TOKEN_STRING); 223 | 224 | // Reset the state 225 | $token = ""; 226 | $state = S_DEFAULT; 227 | } 228 | 229 | // Continue with our string 230 | else { 231 | $token .= $char; 232 | } 233 | 234 | /** 235 | * The comment state. Comments are terminated by a newline, so check for that. We're just 236 | * ignoring it if it's a comment, because the parser doesn't give a damn. 237 | */ 238 | case S_COMMENT: 239 | // Is it a newline? 240 | if ($char == "\n") { 241 | // Reset the state 242 | $state = S_DEFAULT; 243 | } 244 | 245 | break; 246 | } 247 | } 248 | 249 | return $tokens; 250 | } 251 | } 252 | 253 | /** 254 | * Token represents a single token in the lexer. 255 | * It's just a simple structure to store data. 256 | * 257 | * @package basic 258 | * @author Jamie Rumbelow 259 | **/ 260 | class Token { 261 | public $token; 262 | public $type; 263 | 264 | public function __construct($token, $type) { 265 | $this->token = $token; 266 | $this->type = $type; 267 | } 268 | 269 | public function __tostring() { 270 | return (string)$this->type . ": <" . $this->token . ">"; 271 | } 272 | } 273 | 274 | /** 275 | * The parser takes in an array of tokens and generates 276 | * something called an AST (Abstract Syntax Tree). This is a 277 | * data structure that contains all the statements and expressions 278 | * inside the code. 279 | * 280 | * One of the reasons we tokenise the code first is that we can keep 281 | * multiple levels in the AST, whereas the tokeniser is stuck at one level. 282 | * 283 | * @package basic 284 | * @author Jamie Rumbelow 285 | **/ 286 | class Parser { 287 | public $tokens = array(); 288 | public $position = 0; 289 | public $line = 1; 290 | 291 | public function __construct($tokens) { 292 | $this->tokens = $tokens; 293 | } 294 | 295 | /** 296 | * The top level parsing function. This function loops through the 297 | * tokens and routes over to other methods that handle the language. 298 | * 299 | * @return array 300 | * @author Jamie Rumbelow 301 | */ 302 | public function parse() { 303 | // Keep track of statements and labels 304 | $statements = array(); 305 | $labels = array(); 306 | 307 | // Infinite loop; we'll use $this->position to keep 308 | // track of when we're done 309 | while (TRUE) { 310 | // Is this a label? 311 | if ($this->match(TOKEN_LABEL)) { 312 | // Record this label, linking it to the current index of the 313 | // statements. This is so we can route the program flow later 314 | $labels[$this->previous()->token] = (count($statements) > 0) ? count($statements) - 1 : 0; 315 | } 316 | 317 | // Is it an assignment? 318 | else if ($this->match(TOKEN_WORD, TOKEN_EQUALS)) { 319 | // Create a new assignment statement with the current token text (the variable's name), and 320 | // parse the expression 321 | $this->position++; 322 | $statements[] = new AssignmentStatement($this->previous(1)->token, $this->expression()); 323 | } 324 | 325 | // Is it a print statement? 326 | else if ($this->current()->token == "print") { 327 | // Parse the expression and create new print statement 328 | $this->position++; 329 | $statements[] = new PrintStatement($this->expression()); 330 | } 331 | 332 | // Is it an input statement? 333 | else if ($this->current()->token == "input") { 334 | // Get the next token (variable name) and create new input statement 335 | // We're using next_token() to ensure that the next token is indeed a TOKEN_WORD. 336 | $statements[] = new InputStatement($this->next_token(TOKEN_WORD)->token); 337 | $this->position++; 338 | $this->position++; 339 | } 340 | 341 | // Is it a goto statement? 342 | else if ($this->current()->token == "goto") { 343 | // Similar to above, get the next token (label to go to) and create new goto statement 344 | $statements[] = new GotoStatement($this->next_token(TOKEN_WORD)->token); 345 | $this->position++; 346 | $this->position++; 347 | } 348 | 349 | // Is it an if statement? 350 | else if ($this->current()->token == "if") { 351 | // This is where it gets slightly more complex. We first want to parse an expression, 352 | // which is the condition. 353 | $this->position++; 354 | $condition = $this->expression(); 355 | 356 | // Then we want the label to go to 357 | $label = $this->next_token(TOKEN_WORD)->token; 358 | $this->position++; 359 | $this->position++; 360 | 361 | // Create the new statement 362 | $statements[] = new IfThenStatement($condition, $label); 363 | } 364 | 365 | // Is it an exit statement? 366 | else if ($this->current()->token == "exit") { 367 | // Create new print statement 368 | $this->position++; 369 | $statements[] = new ExitStatement(); 370 | } 371 | 372 | // We're not sure what token this is, it's probably the end of the file. So, bye! 373 | else { 374 | break; 375 | } 376 | } 377 | 378 | // Store the statements and labels in the intepreter 379 | Basic::$statements = $statements; 380 | Basic::$labels = $labels; 381 | } 382 | 383 | /** 384 | * Get the current token 385 | * 386 | * @return Token 387 | * @author Jamie Rumbelow 388 | **/ 389 | public function current() { 390 | return $this->tokens[$this->position]; 391 | } 392 | 393 | /** 394 | * Get the next token, optionally offset 395 | * 396 | * @return Token 397 | * @author Jamie Rumbelow 398 | **/ 399 | public function next($offset = 0) { 400 | return $this->tokens[$this->position + 1 + $offset]; 401 | } 402 | 403 | /** 404 | * Get the previous token, optionally offset 405 | * 406 | * @return Token 407 | * @author Jamie Rumbelow 408 | **/ 409 | public function previous($offset = 0) { 410 | return $this->tokens[$this->position - 1 - $offset]; 411 | } 412 | 413 | /** 414 | * Get the next token, ensuring it is a specific type 415 | * 416 | * @return Token 417 | * @author Jamie Rumbelow 418 | **/ 419 | public function next_token($type) { 420 | $token = $this->tokens[$this->position + 1]; 421 | 422 | // Check the token and type match 423 | if ($token->type == $type) { 424 | return $token; 425 | } else { 426 | return FALSE; 427 | } 428 | } 429 | 430 | /** 431 | * Get the next token, ensuring it is has a particular word as it's text 432 | * 433 | * @return Token 434 | * @author Jamie Rumbelow 435 | **/ 436 | public function next_token_word($word) { 437 | $token = $this->tokens[$this->position + 1]; 438 | 439 | // Check the token and type match 440 | if ($token->token == $word) { 441 | return $token; 442 | } else { 443 | return FALSE; 444 | } 445 | } 446 | 447 | /** 448 | * Match the current token with $token_one, and the next 449 | * token with $token_two, if we pass it. Then move to the next token. 450 | * 451 | * If one token is passed, will return TRUE or FALSE if the current token matches. 452 | * If two are passed, BOTH are required to match 453 | * 454 | * @param string $token_one The first token 455 | * @param string | boolean $token_two The second token 456 | * @return boolean 457 | * @author Jamie Rumbelow 458 | */ 459 | public function match($token_one, $token_two = FALSE) { 460 | if (!$token_two) { 461 | // Compare and return 462 | if ($this->current()->type == $token_one) { 463 | // Increment the position 464 | $this->position++; 465 | 466 | return TRUE; 467 | } else { 468 | return FALSE; 469 | } 470 | } 471 | 472 | // We have two tokens 473 | else { 474 | // Check the first compares with the current 475 | if ($this->current()->type == $token_one) { 476 | // Check the second compares 477 | if ($this->next()->type == $token_two) { 478 | // Increment the position 479 | $this->position++; 480 | 481 | // And success 482 | return TRUE; 483 | } else { 484 | return FALSE; 485 | } 486 | } else { 487 | return FALSE; 488 | } 489 | } 490 | } 491 | 492 | /** 493 | * Parse an expression. We siphon this off to operator(), 494 | * as we start at the bottom of the precedence stack and rise up 495 | * and binary operators (+, -, et cetera) are the lowest. 496 | * 497 | * @author Jamie Rumbelow 498 | **/ 499 | public function expression() { 500 | return $this->operator(); 501 | } 502 | 503 | /** 504 | * Parses a series of binary operator expressions into a single 505 | * expression. We do this by building the expression bit by bit. 506 | * 507 | * @author Jamie Rumbelow 508 | */ 509 | public function operator() { 510 | // Look up what's to the left 511 | $expression = $this->atomic(); 512 | 513 | // As long as we have operators, keep building operator expressions 514 | while ($this->match(TOKEN_OPERATOR) || $this->match(TOKEN_EQUALS)) { 515 | // Get the operator 516 | $operator = $this->previous()->token; 517 | 518 | // Look to the right, another atomic 519 | $right = $this->atomic(); 520 | 521 | // Set the expression 522 | $expression = new OperatorExpression($expression, $operator, $right); 523 | } 524 | 525 | // Return the final expression 526 | return $expression; 527 | } 528 | 529 | /** 530 | * Look for an atomic expression, which is a single literal 531 | * value such as a string or or a number. It's also possible we've 532 | * got another expression wrapped in parenthesis. 533 | * 534 | * @author Jamie Rumbelow 535 | **/ 536 | public function atomic() { 537 | // Is it a word? Words reference variables 538 | if ($this->match(TOKEN_WORD)) { 539 | return new VariableExpression($this->previous()->token); 540 | } 541 | 542 | // A number? Parse it as a float 543 | else if ($this->match(TOKEN_NUMBER)) { 544 | return new NumberExpression(floatval($this->previous()->token)); 545 | } 546 | 547 | // A string? 548 | else if ($this->match(TOKEN_STRING)) { 549 | return new StringExpression($this->previous()->token); 550 | } 551 | 552 | // Left parenthesis, a new expression 553 | else if ($this->match(TOKEN_LEFT_PARENTHESIES)) { 554 | // Parse the expression and find the closing parenthesis 555 | $expression = $this->expression(); 556 | $this->position++; 557 | 558 | // Return the expression 559 | return $expression; 560 | } 561 | 562 | // Give up & throw an error 563 | throw new BasicParserException("Couldn't parse expression"); 564 | } 565 | } 566 | 567 | /** 568 | * The base Statement interface. Statements do stuff when executed 569 | */ 570 | interface Statement { 571 | public function execute(); 572 | } 573 | 574 | /** 575 | * The base Expression interface. Expressions return values when evaluated 576 | **/ 577 | interface Expression { 578 | public function evaluate(); 579 | } 580 | 581 | /** 582 | * A "print" statement evaluates an expression, converts the result to a 583 | * string, and displays it to the user. 584 | */ 585 | class PrintStatement implements Statement { 586 | public function __construct($expression) { 587 | $this->expression = $expression; 588 | } 589 | 590 | public function execute() { 591 | print $this->expression->evaluate() . "\n"; 592 | } 593 | } 594 | 595 | /** 596 | * A "input" statement gets a line of input from the user and assigns it 597 | * to a variable. 598 | */ 599 | class InputStatement implements Statement { 600 | public function __construct($variable) { 601 | $this->variable = $variable; 602 | } 603 | 604 | public function execute() { 605 | Basic::$variables[$this->variable] = trim(fgets(fopen("php://stdin","r"))); 606 | } 607 | } 608 | 609 | /** 610 | * An assignment statement assigns a variable with a value 611 | */ 612 | class AssignmentStatement implements Statement { 613 | public function __construct($variable, $value) { 614 | $this->variable = $variable; 615 | $this->value = $value; 616 | } 617 | 618 | public function execute() { 619 | Basic::$variables[$this->variable] = $this->value->evaluate(); 620 | } 621 | } 622 | 623 | /** 624 | * A goto statement moves the program execution flow to a labelled point. 625 | */ 626 | class GotoStatement implements Statement { 627 | public function __construct($label) { 628 | $this->label = $label; 629 | } 630 | 631 | public function execute() { 632 | if (isset(Basic::$labels[$this->label])) { 633 | Basic::$current_statement = (int)Basic::$labels[$this->label]; 634 | } 635 | } 636 | } 637 | 638 | /** 639 | * An if-then statement jumps to 640 | */ 641 | class IfThenStatement implements Statement { 642 | public function __construct($expression, $label) { 643 | $this->expression = $expression; 644 | $this->label = $label; 645 | } 646 | 647 | public function execute() { 648 | if ($this->expression->evaluate()) { 649 | $goto = new GotoStatement($this->label); 650 | $goto->execute(); 651 | } 652 | } 653 | } 654 | 655 | /** 656 | * A simple statement to exit program flow 657 | */ 658 | class ExitStatement implements Statement { 659 | public function execute() { 660 | exit; 661 | } 662 | } 663 | 664 | /** 665 | * A variable expression evaluates to the value of the variable 666 | */ 667 | class VariableExpression implements Expression { 668 | public function __construct($variable) { 669 | $this->variable = $variable; 670 | } 671 | 672 | public function evaluate() { 673 | if (isset(Basic::$variables[$this->variable])) { 674 | return Basic::$variables[$this->variable]; 675 | } else { 676 | return FALSE; 677 | } 678 | } 679 | } 680 | 681 | /** 682 | * A number expression evaluates to a number 683 | */ 684 | class NumberExpression implements Expression { 685 | public function __construct($number) { 686 | $this->number = $number; 687 | } 688 | 689 | public function evaluate() { 690 | return $this->number; 691 | } 692 | } 693 | 694 | /** 695 | * A string expression evaluates to a string 696 | */ 697 | class StringExpression implements Expression { 698 | public function __construct($string) { 699 | $this->string = $string; 700 | } 701 | 702 | public function evaluate() { 703 | return $this->string; 704 | } 705 | } 706 | 707 | /** 708 | * An operator expression evaluates two expressions and then operates 709 | * on them. 710 | */ 711 | class OperatorExpression implements Expression { 712 | public function __construct($left, $operator, $right) { 713 | $this->left = $left; 714 | $this->operator = $operator; 715 | $this->right = $right; 716 | } 717 | 718 | public function evaluate() { 719 | $left = $this->left->evaluate(); 720 | $right = $this->right->evaluate(); 721 | 722 | switch ($this->operator) { 723 | case '=': 724 | if (is_string($left)) { 725 | return (bool)($left == (string)$right); 726 | } else { 727 | return (bool)($left == (int)$right); 728 | } 729 | break; 730 | 731 | case '+': 732 | if (is_string($left)) { 733 | return $left .= (string)$right; 734 | } else { 735 | return $left + (int)$right; 736 | } 737 | break; 738 | 739 | case '-': 740 | return $left - $right; 741 | break; 742 | 743 | case '*': 744 | return $left * $right; 745 | break; 746 | 747 | case '/': 748 | return $left / $right; 749 | break; 750 | 751 | case '<': 752 | return (bool)($left < $right); 753 | break; 754 | 755 | case '>': 756 | return (bool)($left > $right); 757 | break; 758 | } 759 | 760 | throw new BasicParserException("Unknown operator '".$this->operator."'"); 761 | } 762 | } 763 | 764 | /** 765 | * A basic parser exception class 766 | **/ 767 | class BasicParserException extends Exception { } 768 | 769 | // We need a file argument 770 | if (!isset($argv[1])) { 771 | echo "\033[0;32mUsage: php basic.php \n"; 772 | echo "\tWhere is the BASIC file to parse\n\033[0m"; 773 | } else { 774 | // Get the file 775 | $source = file_get_contents($argv[1]); 776 | 777 | // Create a new parser 778 | $basic = new Basic(); 779 | $basic->interpret($source); 780 | } --------------------------------------------------------------------------------