aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/.editorconfig5
-rw-r--r--src/a.php13
-rw-r--r--src/b.php286
3 files changed, 304 insertions, 0 deletions
diff --git a/src/.editorconfig b/src/.editorconfig
new file mode 100644
index 0000000..819fb4e
--- /dev/null
+++ b/src/.editorconfig
@@ -0,0 +1,5 @@
+root = true
+
+[*.php]
+indent_style = space
+indent_size = 4
diff --git a/src/a.php b/src/a.php
new file mode 100644
index 0000000..1003341
--- /dev/null
+++ b/src/a.php
@@ -0,0 +1,13 @@
+<?php
+for ($i = 1; $i <= 100; $i++) {
+ if ($i % 15 === 0) {
+ echo "FizzBuzz";
+ } elseif ($i % 3 === 0) {
+ echo "Fizz";
+ } elseif ($i % 5 === 0) {
+ echo "Buzz";
+ } else {
+ echo $i;
+ }
+ echo "\n";
+}
diff --git a/src/b.php b/src/b.php
new file mode 100644
index 0000000..6746a58
--- /dev/null
+++ b/src/b.php
@@ -0,0 +1,286 @@
+<?php
+
+declare(strict_types=1);
+
+enum Word {
+ case PhpTag;
+ case LessThan;
+ case LeftParen;
+ case RightParen;
+ case LeftBrace;
+ case RightBrace;
+ case Semicolon;
+ case Modulo;
+ case Increment;
+ case StrictlyEqual;
+ case Assign;
+ case Echo_;
+ case For_;
+ case If_;
+ case ElseIf_;
+ case Else_;
+}
+
+readonly class Variable {
+ public function __construct(
+ public string $name,
+ ) {}
+}
+
+function split_into_words(string $input): array {
+ $i = 0;
+ $result = [];
+
+ while ($i < strlen($input)) {
+ $first = $input[$i];
+ if ($first === '<') {
+ $second = $input[$i + 1];
+ if ($second === '=') {
+ $result[] = Word::LessThan;
+ $i += 2;
+ } else {
+ $result[] = Word::PhpTag;
+ $i += 5;
+ }
+ } else if ($first === '(') {
+ $result[] = Word::LeftParen;
+ $i += 1;
+ } else if ($first === ')') {
+ $result[] = Word::RightParen;
+ $i += 1;
+ } else if ($first === '{') {
+ $result[] = Word::LeftBrace;
+ $i += 1;
+ } else if ($first === '}') {
+ $result[] = Word::RightBrace;
+ $i += 1;
+ } else if ($first === ';') {
+ $result[] = Word::Semicolon;
+ $i += 1;
+ } else if ($first === '%') {
+ $result[] = Word::Modulo;
+ $i += 1;
+ } else if ($first === '+') {
+ $result[] = Word::Increment;
+ $i += 2;
+ } else if ($first === '=') {
+ $second = $input[$i + 1];
+ if ($second === '=') {
+ $result[] = Word::StrictlyEqual;
+ $i += 3;
+ } else {
+ $result[] = Word::Assign;
+ $i += 1;
+ }
+ } else if (ctype_space($first)) {
+ $i += 1;
+ } else if (ctype_digit($first)) {
+ $j = $i;
+ while (ctype_digit($input[$j])) {
+ $j += 1;
+ }
+ $result[] = (int) substr($input, $i, $j - $i);
+ $i = $j;
+ } else if (ctype_alpha($first)) {
+ $j = $i;
+ while (ctype_alpha($input[$j])) {
+ $j += 1;
+ }
+ $result[] = match (substr($input, $i, $j - $i)) {
+ 'echo' => Word::Echo_,
+ 'for' => Word::For_,
+ 'if' => Word::If_,
+ 'elseif' => Word::ElseIf_,
+ 'else' => Word::Else_,
+ };
+ $i = $j;
+ } else if ($first === '$') {
+ $i += 1;
+ $j = $i;
+ while (ctype_alpha($input[$j])) {
+ $j += 1;
+ }
+ $result[] = new Variable(substr($input, $i, $j - $i));
+ $i = $j;
+ } else if ($first === '"') {
+ $i += 1;
+ $j = $i;
+ $s = '';
+ while ($input[$j] !== '"') {
+ if ($input[$j] === '\\') {
+ $j += 1;
+ if ($input[$j] === 'n') {
+ $s .= "\n";
+ } else {
+ $s .= $input[$j];
+ }
+ } else {
+ $s .= $input[$j];
+ }
+ $j += 1;
+ }
+ $result[] = $s;
+ $i = $j + 1;
+ }
+ }
+
+ return $result;
+}
+
+class Php {
+ private array $words;
+ private int $position;
+ private array $variables;
+
+ public function __construct(array $words) {
+ $this->words = $words;
+ $this->position = 0;
+ $this->variables = [];
+ }
+
+ public function runPhp(): void {
+ $this->expectWord(Word::PhpTag);
+ $this->runStatements();
+ }
+
+ private function runStatements(bool $doRun = true): void {
+ while (true) {
+ $first = $this->words[$this->position] ?? null;
+ if ($first === Word::For_) {
+ $this->runForStatement($doRun);
+ } else if ($first === Word::If_) {
+ $this->runIfStatement($doRun);
+ } else if ($first === Word::Echo_) {
+ $this->runEchoStatement($doRun);
+ } else {
+ break;
+ }
+ }
+ }
+
+ private function runForStatement(bool $doRun = true): void {
+ $this->position += 1; // skip 'for'
+ $this->expectWord(Word::LeftParen);
+ $this->calculateExpression($doRun);
+ $this->expectWord(Word::Semicolon);
+ $condition_position = $this->position;
+ while (true) {
+ $condition_result = $this->calculateExpression($doRun);
+ $this->expectWord(Word::Semicolon);
+ $update_position = $this->position;
+ if (!$condition_result) {
+ $this->skipExpression();
+ $this->expectWord(Word::RightParen);
+ $this->expectWord(Word::LeftBrace);
+ $this->skipProgram();
+ $this->expectWord(Word::RightBrace);
+ break;
+ }
+ $this->skipExpression();
+ $this->expectWord(Word::RightParen);
+ $this->expectWord(Word::LeftBrace);
+ $this->runStatements($doRun);
+ $this->expectWord(Word::RightBrace);
+ $this->position = $update_position;
+ $this->calculateExpression($doRun);
+ $this->position = $condition_position;
+ }
+ }
+
+ private function runIfStatement(bool $doRun = true): void {
+ $this->position += 1; // skip 'if' or 'elseif'
+ $this->expectWord(Word::LeftParen);
+ $condition = $this->calculateExpression($doRun);
+ $this->expectWord(Word::RightParen);
+ $this->expectWord(Word::LeftBrace);
+ $this->runStatements($doRun && $condition);
+ $this->expectWord(Word::RightBrace);
+ $next_word = $this->words[$this->position] ?? null;
+ if ($next_word === Word::ElseIf_) {
+ $this->runIfStatement($doRun && !$condition);
+ } else if ($next_word === Word::Else_) {
+ $this->position += 1; // skip 'else'
+ $this->expectWord(Word::LeftBrace);
+ $this->runStatements($doRun && !$condition);
+ $this->expectWord(Word::RightBrace);
+ }
+ }
+
+ private function runEchoStatement(bool $doRun = true): void {
+ $this->position += 1; // skip 'echo'
+ $value = $this->calculateExpression($doRun);
+ if ($doRun) {
+ echo $value;
+ }
+ $this->expectWord(Word::Semicolon);
+ }
+
+ private function calculateExpression(bool $doRun = true): int|string|bool|null {
+ $left_hand_side = $this->getNextWord();
+ while (true) {
+ $next_word = $this->words[$this->position];
+ if ($next_word === Word::Assign) {
+ $this->position += 1; // skip '='
+ $right_hand_side = $this->getNextWord();
+ if ($doRun) {
+ assert($left_hand_side instanceof Variable);
+ $left_hand_side = $this->variables[$left_hand_side->name] = $right_hand_side;
+ }
+ } else if ($next_word === Word::LessThan) {
+ $this->position += 1; // skip '<='
+ $right_hand_side = $this->getNextWord();
+ $left_hand_side = $this->convertWordToValue($left_hand_side) <= $this->convertWordToValue($right_hand_side);
+ } else if ($next_word === Word::StrictlyEqual) {
+ $this->position += 1; // skip '==='
+ $right_hand_side = $this->getNextWord();
+ $left_hand_side = $this->convertWordToValue($left_hand_side) === $this->convertWordToValue($right_hand_side);
+ } else if ($next_word === Word::Modulo) {
+ $this->position += 1; // skip '%'
+ $right_hand_side = $this->getNextWord();
+ $left_hand_side = $this->convertWordToValue($left_hand_side) % $this->convertWordToValue($right_hand_side);
+ } else if ($next_word === Word::Increment) {
+ $this->position += 1; // skip '++'
+ if ($doRun) {
+ $left_hand_side = $this->variables[$left_hand_side->name] = $this->variables[$left_hand_side->name] + 1;
+ }
+ } else {
+ return $this->convertWordToValue($left_hand_side);
+ }
+ }
+ }
+
+ private function getNextWord(): int|string|Variable {
+ $word = $this->words[$this->position];
+ $this->position += 1;
+ return $word;
+ }
+
+ private function convertWordToValue(int|string|bool|Variable $word): int|string|bool|null {
+ if ($word instanceof Variable) {
+ return $this->variables[$word->name];
+ } else {
+ return $word;
+ }
+ }
+
+ private function skipExpression(): void {
+ $this->calculateExpression(doRun: false);
+ }
+
+ private function skipProgram(): void {
+ $this->runStatements(doRun: false);
+ }
+
+ private function expectWord(Word $expected_word): void {
+ if ($this->words[$this->position] !== $expected_word) {
+ throw new RuntimeException("Expected $expected_word at position $this->position");
+ }
+ $this->position += 1;
+ }
+}
+
+$input = file_get_contents('./a.php');
+$words = split_into_words($input);
+
+$php = new Php($words);
+$php->runPhp();