@dramxx/brolang 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/bro.js ADDED
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env node
2
+ 'use strict'
3
+
4
+ const path = require('path')
5
+ const fs = require('fs')
6
+ const { spawn } = require('child_process')
7
+ const repl = require('repl')
8
+
9
+ const { transpile, substituteKeywords } = require('../src/transpiler')
10
+ const { buildRuntimeContext } = require('../src/runtime')
11
+ const { version } = require('../package.json')
12
+
13
+ // ── ANSI colors ───────────────────────────────────────────────────────────────
14
+ const red = (s) => `\x1b[31m${s}\x1b[0m`
15
+ const green = (s) => `\x1b[32m${s}\x1b[0m`
16
+ const cyan = (s) => `\x1b[1;36m${s}\x1b[0m`
17
+ const gray = (s) => `\x1b[90m${s}\x1b[0m`
18
+
19
+ // ── CLI entry ─────────────────────────────────────────────────────────────────
20
+ const [,, cmd, ...args] = process.argv
21
+
22
+ const HELP = `
23
+ BroLang ${version} — JavaScript for people who peaked in 1997
24
+
25
+ Usage: bro <command> [options]
26
+
27
+ Commands:
28
+ run <file> Transpile and execute a .bro file
29
+ build <file> [-o out] Transpile a .bro file to JavaScript
30
+ check <file> Check a .bro file for transpile errors
31
+ repl Start an interactive BroLang REPL
32
+
33
+ Options:
34
+ -v, --version Show version
35
+ -h, --help Show this help
36
+ `.trim()
37
+
38
+ if (!cmd || cmd === '-h' || cmd === '--help') {
39
+ console.log(HELP)
40
+ process.exit(0)
41
+ }
42
+
43
+ if (cmd === '-v' || cmd === '--version') {
44
+ console.log(version)
45
+ process.exit(0)
46
+ }
47
+
48
+ if (cmd === 'run') runFile(args[0])
49
+ else if (cmd === 'build') buildFile(args)
50
+ else if (cmd === 'check') checkFile(args[0])
51
+ else if (cmd === 'repl') startRepl()
52
+ else {
53
+ console.error(red(`bro: unknown command '${cmd}'. Run 'bro --help' for usage.`))
54
+ process.exit(1)
55
+ }
56
+
57
+ // ── helpers ───────────────────────────────────────────────────────────────────
58
+
59
+ function resolveFile(file) {
60
+ if (!file) {
61
+ console.error(red('bro: no file specified'))
62
+ process.exit(1)
63
+ }
64
+ let p = path.resolve(file)
65
+ if (!fs.existsSync(p) && !file.endsWith('.bro')) {
66
+ p = path.resolve(file + '.bro')
67
+ }
68
+ if (!fs.existsSync(p)) {
69
+ console.error(red(`bro: file not found: ${file}`))
70
+ process.exit(1)
71
+ }
72
+ return p
73
+ }
74
+
75
+ // ── commands ──────────────────────────────────────────────────────────────────
76
+
77
+ function runFile(file) {
78
+ const filePath = resolveFile(file)
79
+ const source = fs.readFileSync(filePath, 'utf8')
80
+
81
+ let js
82
+ try {
83
+ js = transpile(source)
84
+ } catch (err) {
85
+ console.error(red('transpile error:'), err.message)
86
+ process.exit(1)
87
+ }
88
+
89
+ const hasModuleSyntax = /^(?:import|export)\s/m.test(js)
90
+ const ext = hasModuleSyntax ? '.mjs' : '.js'
91
+ const tmp = path.join(path.dirname(filePath), `__bro_${process.pid}_${Date.now()}${ext}`)
92
+
93
+ fs.writeFileSync(tmp, js, 'utf8')
94
+
95
+ const child = spawn(process.execPath, [tmp], { stdio: 'inherit' })
96
+ child.on('exit', (code, signal) => {
97
+ fs.unlink(tmp, () => {})
98
+ if (signal) process.kill(process.pid, signal)
99
+ else process.exit(code ?? 0)
100
+ })
101
+ child.on('error', (err) => {
102
+ console.error(red('run error:'), err.message)
103
+ fs.unlink(tmp, () => {})
104
+ process.exit(1)
105
+ })
106
+ }
107
+
108
+ function buildFile(args) {
109
+ let file = null
110
+ let out = null
111
+ for (let i = 0; i < args.length; i++) {
112
+ if ((args[i] === '-o' || args[i] === '--out') && args[i + 1]) {
113
+ out = args[++i]
114
+ } else if (!file) {
115
+ file = args[i]
116
+ }
117
+ }
118
+
119
+ const filePath = resolveFile(file)
120
+ const source = fs.readFileSync(filePath, 'utf8')
121
+
122
+ let js
123
+ try {
124
+ js = transpile(source)
125
+ } catch (err) {
126
+ console.error(red('transpile error:'), err.message)
127
+ process.exit(1)
128
+ }
129
+
130
+ const outPath = out ? path.resolve(out) : filePath.replace(/\.bro$/, '.js')
131
+ fs.writeFileSync(outPath, js, 'utf8')
132
+ console.log(green('built:'), outPath)
133
+ }
134
+
135
+ function checkFile(file) {
136
+ const filePath = resolveFile(file)
137
+ const source = fs.readFileSync(filePath, 'utf8')
138
+ try {
139
+ transpile(source)
140
+ console.log(green(`✓ ${file} — looks good, bro`))
141
+ } catch (err) {
142
+ console.error(red(`✗ ${file} — ${err.message}`))
143
+ process.exit(1)
144
+ }
145
+ }
146
+
147
+ function startRepl() {
148
+ console.log(cyan('BroLang REPL') + gray(' (.exit to quit)\n'))
149
+
150
+ const rtFns = buildRuntimeContext()
151
+
152
+ const r = repl.start({
153
+ prompt: cyan('bro> '),
154
+ useGlobal: true,
155
+ ignoreUndefined: true,
156
+ })
157
+
158
+ Object.assign(r.context, rtFns)
159
+
160
+ const origEval = r.eval
161
+ r.eval = function broEval(input, ctx, filename, cb) {
162
+ origEval.call(r, substituteKeywords(input), ctx, filename, cb)
163
+ }
164
+ }
@@ -0,0 +1,43 @@
1
+ // classes.bro — OOP but make it bro
2
+
3
+ crew Animal {
4
+ setup(name, sound) {
5
+ me.name = name
6
+ me.sound = sound
7
+ }
8
+
9
+ speak() {
10
+ fuckoff me.name + " says " + me.sound
11
+ }
12
+
13
+ static describe() {
14
+ fuckoff "Animals are cool bro"
15
+ }
16
+ }
17
+
18
+ crew Dog extends Animal {
19
+ setup(name) {
20
+ super(name, "woof")
21
+ me.tricks = []
22
+ }
23
+
24
+ learn(trick) {
25
+ me.tricks.stuffin(trick)
26
+ }
27
+
28
+ showoff() {
29
+ sus (me.tricks.howmany === 0) {
30
+ spam(me.name + " doesn't know any tricks yet, sad")
31
+ fuckoff
32
+ }
33
+ spam(me.name + " knows: " + me.tricks.glue(", "))
34
+ }
35
+ }
36
+
37
+ lilbro dog = fresh Dog("Rex")
38
+ spam(dog.speak())
39
+ dog.learn("sit")
40
+ dog.learn("roll over")
41
+ dog.learn("vibe")
42
+ dog.showoff()
43
+ spam(Animal.describe())
@@ -0,0 +1,46 @@
1
+ // hello.bro — the classic first program, but make it bro
2
+
3
+ bro greet(name) {
4
+ fuckoff "Yo, " + name + "! Welcome to BroLang."
5
+ }
6
+
7
+ lilbro name = "Chad"
8
+ spam(greet(name))
9
+
10
+ // Arrays
11
+ lilbro nums = [1, 2, 3, 4, 5]
12
+ lilbro doubled = nums.cook(bro(n) { fuckoff n * 2 })
13
+ lilbro evens = nums.keeponly(bro(n) { fuckoff n % 2 === 0 })
14
+ lilbro total = nums.smashdown(bro(acc, n) { fuckoff acc + n }, 0)
15
+
16
+ spam("doubled:", doubled)
17
+ spam("evens:", evens)
18
+ spam("total:", total)
19
+ spam("count:", nums.howmany)
20
+
21
+ // Control flow
22
+ sus (total > 10) {
23
+ spam("big number energy")
24
+ } nah sus (total > 5) {
25
+ spam("mid number energy")
26
+ } nah {
27
+ spam("smol number energy")
28
+ }
29
+
30
+ // Loop
31
+ grind(lilbro i = 0; i < 3; i++) {
32
+ spam("lap", i)
33
+ }
34
+
35
+ // Error handling
36
+ bro riskyBusiness() {
37
+ yolo {
38
+ yeet fresh Error("something broke lol")
39
+ } lol(err) {
40
+ omfg("caught:", err.message)
41
+ } anyway {
42
+ spam("tried my best")
43
+ }
44
+ }
45
+
46
+ riskyBusiness()
@@ -0,0 +1,6 @@
1
+ lilbro p = fresh pinkyswear(bro(win, fail) {
2
+ win("sorted!")
3
+ })
4
+
5
+ p.thendo(bro(v) { spam(v) })
6
+ .otherwise(bro(e) { omfg(e) })
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@dramxx/brolang",
3
+ "version": "0.1.0",
4
+ "description": "JavaScript transpiler for people who peaked in 1997",
5
+ "license": "MIT",
6
+ "author": "dmaxx",
7
+ "keywords": [
8
+ "brolang",
9
+ "transpiler",
10
+ "javascript",
11
+ "language",
12
+ "cli",
13
+ "bro"
14
+ ],
15
+ "engines": {
16
+ "node": ">=14.0.0"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/dramxx/brolang.git"
21
+ },
22
+ "homepage": "https://github.com/dramxx/brolang#readme",
23
+ "bugs": {
24
+ "url": "https://github.com/dramxx/brolang/issues"
25
+ },
26
+ "files": [
27
+ "bin",
28
+ "src",
29
+ "examples",
30
+ "README.md"
31
+ ],
32
+ "bin": {
33
+ "bro": "./bin/bro.js"
34
+ },
35
+ "scripts": {
36
+ "test": "node test/transpiler.test.js"
37
+ }
38
+ }
@@ -0,0 +1,67 @@
1
+ 'use strict'
2
+
3
+ // Multi-word patterns — checked FIRST, only horizontal whitespace is skipped
4
+ // between parts (no newline-crossing to avoid surprises).
5
+ // Format: [broLangPattern, jsEquivalent]
6
+ const MULTI_WORD_MAP = [
7
+ ['nocap bro', 'async function'],
8
+ ['nah sus', 'else if'],
9
+ ]
10
+
11
+ // Single-word keyword substitutions.
12
+ // With word-boundary matching, ordering only matters to break ties on prefix
13
+ // overlaps — there are none here, so order is just for readability.
14
+ const KEYWORD_MAP = [
15
+ // Declarations
16
+ ['lilbro', 'let'],
17
+ ['crew', 'class'],
18
+ ['setup', 'constructor'],
19
+ ['fresh', 'new'],
20
+
21
+ // Functions / async
22
+ ['bro', 'function'],
23
+ ['nocap', 'async'],
24
+ ['holdmybeer', 'await'],
25
+ ['fuckoff', 'return'],
26
+
27
+ // Control flow
28
+ ['sus', 'if'],
29
+ ['nah', 'else'],
30
+ ['pickone', 'switch'],
31
+ ['when', 'case'],
32
+ ['whatever', 'default'],
33
+
34
+ // Loops
35
+ ['nosyping', 'for'],
36
+ ['vibing', 'for'],
37
+ ['grind', 'for'],
38
+ ['keepgoing', 'while'],
39
+ ['doitonce', 'do'],
40
+ ['gtfo', 'break'],
41
+ ['peace', 'break'],
42
+ ['skip', 'continue'],
43
+
44
+ // Error handling
45
+ ['yolo', 'try'],
46
+ ['lol', 'catch'],
47
+ ['anyway', 'finally'],
48
+ // 'yeet' is handled separately (throw vs delete heuristic)
49
+
50
+ // Modules
51
+ ['shareall', 'export default'],
52
+ ['yoink', 'import'],
53
+ ['share', 'export'],
54
+
55
+ // Operators / misc
56
+ ['ispartof', 'instanceof'],
57
+ ['wtf', 'typeof'],
58
+ ['me', 'this'],
59
+
60
+ // Literals
61
+ ['cool', 'true'],
62
+ ['notcool', 'false'],
63
+ ['shit', 'null'],
64
+ ['idk', 'undefined'],
65
+ ]
66
+
67
+ module.exports = { KEYWORD_MAP, MULTI_WORD_MAP }
package/src/runtime.js ADDED
@@ -0,0 +1,169 @@
1
+ 'use strict'
2
+
3
+ // The runtime is injected as a plain JS block at the top of every transpiled file.
4
+ // It's self-contained (no require/import) so the output works standalone.
5
+ const RUNTIME_CODE = `
6
+ /* ── BroLang Runtime ── */
7
+ const spam = (...a) => console.log(...a)
8
+ const omfg = (...a) => console.error(...a)
9
+ const omg = (...a) => console.warn(...a)
10
+ const spreadshit = (...a) => console.table(...a)
11
+ const debugshit = (...a) => console.debug(...a)
12
+ const clearshit = () => console.clear()
13
+ const timeshit = (l) => console.time(l)
14
+ const timeshitdone = (l) => console.timeEnd(l)
15
+
16
+ const laterbruh = (fn, ms) => setTimeout(fn, ms)
17
+ const keepspamming = (fn, ms) => setInterval(fn, ms)
18
+ const nevermind = (id) => clearTimeout(id)
19
+ const shutup = (id) => clearInterval(id)
20
+
21
+ const dice = () => Math.random()
22
+ const lowball = (n) => Math.floor(n)
23
+ const highball = (n) => Math.ceil(n)
24
+ const ballpark = (n) => Math.round(n)
25
+ const bigbro = (...a) => Math.max(...a)
26
+ const smolbro = (...a) => Math.min(...a)
27
+ const nominus = (n) => Math.abs(n)
28
+ const whatroot = (n) => Math.sqrt(n)
29
+ const tothepower = (b, e) => Math.pow(b, e)
30
+ const PI = Math.PI
31
+
32
+ const listkeys = (o) => Object.keys(o)
33
+ const listvals = (o) => Object.values(o)
34
+ const listpairs = (o) => Object.entries(o)
35
+ const mash = (t, ...s) => Object.assign(t, ...s)
36
+ const frozensolid = (o) => Object.freeze(o)
37
+ const sealitup = (o) => Object.seal(o)
38
+
39
+ const textify = (v) => JSON.stringify(v)
40
+ const textifypretty = (v, n=2) => JSON.stringify(v, null, n)
41
+ const untext = (s) => JSON.parse(s)
42
+
43
+ const isalist = (x) => Array.isArray(x)
44
+ const isnothing = (x) => x === null || x === undefined
45
+ const exists = (x) => x !== null && x !== undefined
46
+ const freshobj = (proto) => Object.create(proto)
47
+
48
+ const grab = (...a) => fetch(...a)
49
+
50
+ const pinkyswear = Object.assign(
51
+ function(fn) { return new Promise(fn) },
52
+ {
53
+ sorted: (v) => Promise.resolve(v),
54
+ noped: (e) => Promise.reject(e),
55
+ waitforall: (a) => Promise.all(a),
56
+ firstfinish: (a) => Promise.race(a),
57
+ nodrama: (a) => Promise.allSettled(a),
58
+ }
59
+ )
60
+
61
+ // DOM helpers (no-op stubs in Node; real functions in browser)
62
+ const findthisshit = typeof document !== 'undefined' ? (id) => document.getElementById(id) : () => null
63
+ const findme = typeof document !== 'undefined' ? (sel) => document.querySelector(sel) : () => null
64
+ const findallofthem = typeof document !== 'undefined' ? (sel) => document.querySelectorAll(sel): () => []
65
+ const makething = typeof document !== 'undefined' ? (tag) => document.createElement(tag) : () => null
66
+
67
+ // Array prototype extensions
68
+ Array.prototype.cook = Array.prototype.map
69
+ Array.prototype.keeponly = Array.prototype.filter
70
+ Array.prototype.smashdown = Array.prototype.reduce
71
+ Array.prototype.vibecheck = Array.prototype.forEach
72
+ Array.prototype.findone = Array.prototype.find
73
+ Array.prototype.findindex = Array.prototype.findIndex
74
+ Array.prototype.gotthis = Array.prototype.includes
75
+ Array.prototype.allfine = Array.prototype.every
76
+ Array.prototype.anyonegood = Array.prototype.some
77
+ Array.prototype.stuffin = Array.prototype.push
78
+ Array.prototype.yeetlast = Array.prototype.pop
79
+ Array.prototype.stufffirst = Array.prototype.unshift
80
+ Array.prototype.yeetfirst = Array.prototype.shift
81
+ Array.prototype.glue = Array.prototype.join
82
+ Array.prototype.cutout = Array.prototype.slice
83
+ Array.prototype.surgery = Array.prototype.splice
84
+ Array.prototype.sortitout = Array.prototype.sort
85
+ Array.prototype.flipit = Array.prototype.reverse
86
+ Array.prototype.flatten = Array.prototype.flat
87
+ Array.prototype.flatmash = Array.prototype.flatMap
88
+ Object.defineProperty(Array.prototype, 'howmany', {
89
+ get() { return this.length },
90
+ configurable: true,
91
+ })
92
+
93
+ // String prototype extensions
94
+ String.prototype.cutout = String.prototype.slice
95
+ String.prototype.chop = String.prototype.split
96
+ String.prototype.cleanitup = String.prototype.trim
97
+ String.prototype.cleanfront = String.prototype.trimStart
98
+ String.prototype.cleanback = String.prototype.trimEnd
99
+ String.prototype.gotthis = String.prototype.includes
100
+ String.prototype.swapout = String.prototype.replace
101
+ String.prototype.swapallout = String.prototype.replaceAll
102
+ String.prototype.smallify = String.prototype.toLowerCase
103
+ String.prototype.bigify = String.prototype.toUpperCase
104
+ String.prototype.startswith = String.prototype.startsWith
105
+ String.prototype.endswith = String.prototype.endsWith
106
+ String.prototype.pad = String.prototype.padStart
107
+ String.prototype.padback = String.prototype.padEnd
108
+ String.prototype.atpos = String.prototype.charAt
109
+ String.prototype.numcode = String.prototype.charCodeAt
110
+ Object.defineProperty(String.prototype, 'howlong', {
111
+ get() { return this.length },
112
+ configurable: true,
113
+ })
114
+
115
+ // Promise prototype extensions
116
+ Promise.prototype.thendo = Promise.prototype.then
117
+ Promise.prototype.otherwise = Promise.prototype.catch
118
+ Promise.prototype.nomatterwhat = Promise.prototype.finally
119
+
120
+ // DOM prototype extensions (browser only)
121
+ if (typeof EventTarget !== 'undefined') {
122
+ EventTarget.prototype.whenthishappens = EventTarget.prototype.addEventListener
123
+ EventTarget.prototype.stoplistening = EventTarget.prototype.removeEventListener
124
+ }
125
+ if (typeof Node !== 'undefined') {
126
+ Node.prototype.addtodoc = Node.prototype.appendChild
127
+ Node.prototype.kickout = Node.prototype.removeChild
128
+ }
129
+ if (typeof Element !== 'undefined') {
130
+ Element.prototype.attr = Element.prototype.setAttribute
131
+ Element.prototype.getattr = Element.prototype.getAttribute
132
+ Object.defineProperty(Element.prototype, 'text', {
133
+ get() { return this.textContent },
134
+ set(v) { this.textContent = v },
135
+ configurable: true,
136
+ })
137
+ Object.defineProperty(Element.prototype, 'html', {
138
+ get() { return this.innerHTML },
139
+ set(v) { this.innerHTML = v },
140
+ configurable: true,
141
+ })
142
+ Object.defineProperty(Element.prototype, 'classes', {
143
+ get() { return this.classList },
144
+ configurable: true,
145
+ })
146
+ }
147
+ /* ── End BroLang Runtime ── */
148
+ `
149
+
150
+ function getRuntimeCode() {
151
+ return RUNTIME_CODE
152
+ }
153
+
154
+ // Runtime function object for REPL injection (evaluated fresh each call)
155
+ function buildRuntimeContext() {
156
+ const ctx = {}
157
+ // Evaluate the runtime block to capture var-declared names
158
+ // We rebuild it using Function so const → visible in returned ctx
159
+ const lines = RUNTIME_CODE
160
+ .split('\n')
161
+ .filter(l => /^\s*const\s+/.test(l))
162
+ .map(l => l.trim())
163
+ const names = lines.map(l => l.match(/^const\s+(\w+)/)[1])
164
+ const body = RUNTIME_CODE + '\nreturn {' + names.join(',') + '}'
165
+ // eslint-disable-next-line no-new-func
166
+ return new Function(body)()
167
+ }
168
+
169
+ module.exports = { getRuntimeCode, buildRuntimeContext }