initial commit: basic interpreter

This commit is contained in:
DaInfLoop 2024-06-27 21:26:37 +01:00
commit da1b79c094
6 changed files with 333 additions and 0 deletions

178
.gitignore vendored Normal file
View file

@ -0,0 +1,178 @@
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Caches
.cache
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store
test
bun.lockb

19
README.md Normal file
View file

@ -0,0 +1,19 @@
# Brainfuck Interpreter
This is a project I worked on for Hack Club [Arcade](https://hackclub.com/arcade).
## Usage
<!-- (On release) The interpreter can be found on [my CDN](https://haroon.hackclub.app/cdn/bf):
```sh
$ curl -LO https://haroon.hackclub.app/cdn/bf
```
Once installed, y-->You can run `bun index.ts <file>.bf` to interpret a file. As of right now, there is no binary.
## Testing
```
$ bun index.ts test.bf
```
The contents of `test.bf` is a brainfuck program that outputs Hello World.

93
index.ts Normal file
View file

@ -0,0 +1,93 @@
const memory = Array<number>(3_000).fill(0);
let CI = 0 // Current Instruction
let MA = 0 // Memory Address
let stack: number[] = [];
let inp = "";
let out = "";
function interpret(program: string) {
let fin = false;
while (!fin) {
console.log(CI, program.at(CI))
switch (program.at(CI)) {
case '>':
// Move MA to right
if (MA == memory.length - 1)
// Expand memory if run out of space
memory.push(...Array<number>(10).fill(0))
MA++
break;
case '>':
// Move MA to left
if (MA > 0) MA--
break;
case '+':
// Increment memory location at MA
memory[MA]++
break;
case '-':
// Decrease memory location at MA
memory[MA]--
break;
case '.':
// Output character with value stored at MA
out += String.fromCharCode(memory[MA]);
break;
case ',':
// Read input character and store at at cell at MA
break;
case '[':
// Jump past the next ]
if (memory[MA]) { // If current memory value isn't non-zero, push the current instruction to the stack
stack.push(CI)
} else {
let c = 0; // Count variable, allows to find the matching bracket
while (1) {
CI++
if (!program[CI]) break; // If this is the end of the program, break
if (program[CI] == "[") c++ // If the current instruction is a [, increase the matching count
else if (program[CI] == "]") { // If this is a ]
if (c) c-- // If c is 0, this is a matching bracket, otherwise decrease count to help find it
else break // We are now at the matching ], finish
}
}
}
break;
case ']':
// Jump to the matching [ if non-zero
// Set CI to last instruction value in stack
CI = stack.pop()! - 1;
break;
case undefined:
// End of program
fin = true;
break;
default:
break;
}
CI++
}
console.log(out, memory.filter(x=>x))
}
const argv = Bun.argv.slice(2);
if (argv.length) {
const file = Bun.file(argv[0]);
file.text().then(interpret);
}

15
package.json Normal file
View file

@ -0,0 +1,15 @@
{
"name": "brainf-interpreter",
"module": "index.ts",
"type": "module",
"devDependencies": {
"@types/bun": "latest",
"@types/yargs": "^17.0.32"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"yargs": "^17.7.2"
}
}

1
test.bf Normal file
View file

@ -0,0 +1 @@
++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---

27
tsconfig.json Normal file
View file

@ -0,0 +1,27 @@
{
"compilerOptions": {
// Enable latest features
"lib": ["ESNext", "DOM"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
}
}