├── package.json ├── main.js ├── readme.md └── halfwit.js /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "halfwit", 3 | "version": "1.0.1", 4 | "description": "Halfwit is an experimental golfing language that fits most commands in half a byte.", 5 | "main": "main.js", 6 | "scripts": { 7 | "test": "echo \"No tests\"" 8 | }, 9 | "author": "", 10 | "license": "ISC" 11 | } 12 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | #!usr/bin/env node 2 | let halfwit = require('./halfwit.js') 3 | 4 | let [filename, flags, ...input] = process.argv.slice(2) 5 | 6 | if(!filename) console.log('Usage: node main.js [single string of flags] [inputs]'), process.exit() 7 | 8 | let code; 9 | 10 | if(flags && flags.includes('e')) { 11 | code = filename; 12 | } else { 13 | code = require('fs').readFileSync(filename); 14 | } 15 | 16 | for(let i = 0; i < input.length; i++) { 17 | let result; 18 | try { 19 | result = eval(input[i]) 20 | } catch(e) { 21 | result = input[i] 22 | } 23 | input[i] = result; 24 | } 25 | 26 | console.log(halfwit(code, input, flags)) -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Halfwit 2 | 3 | Halfwit is an experimental golfing language that fits most commands in half a byte. It's stack-based. 4 | 5 | ## Usage 6 | 7 | ```sh 8 | npm install halfwit 9 | node main.js [single string of flags] [inputs] 10 | ``` 11 | 12 | Or clone this repo. 13 | 14 | Or in node.js: 15 | 16 | ```js 17 | const halfwit = require('halfwit') 18 | ``` 19 | 20 | Or in a website: 21 | 22 | ```html 23 | 24 | ``` 25 | 26 | Or, you can [try it online](https://dso.surge.sh/#halfwit) at [DSO](https://dso.surge.sh). 27 | 28 | ## Flags 29 | 30 | Halfwit has several flags: 31 | 32 | - `e` - execute the filename as code 33 | - `A` - print as chars 34 | - `c` - print compiled code 35 | - `C` - count source length in Halfwit codepage 36 | - `s` - compress list / int 37 | - `t` - print tokens 38 | - `p` - print parsed tokens 39 | - `h` - print flag help menu 40 | 41 | ## Builtins 42 | 43 | Some builtins take up .5 bytes, some 1, and some 1.5. 44 | 45 | ## .5 bytes 46 | 47 | - `[` - condition (`[...;`) 48 | - `M` - map (`M...;`) 49 | - `{` - while (condition) do stuff (`{...;...;`), condition = first part 50 | - `;`; - terminate structure 51 | - `+` - Addition, vectorising 52 | - `*` - Multiplication, vectorising 53 | - `J` - join - append / prepend / pair 54 | - `>` - Starts a compressed int - `>...<` / adds to a compressed int list `>...>...<` 55 | - `<` - end int / intlist (see above) / length / range 0...n-1 56 | - `N` - negate / reverse 57 | - `f` - flatten / square 58 | - `b` - base conversion: 59 | - Int, int -> to_base 60 | - Int, list -> to-custom-base 61 | - List, int -> from-base 62 | - list, list -> from-custom-base 63 | - `:` - Duplicate 64 | - `$` - Swap 65 | - `?` - Take input 66 | - `k` - Digraph modifier 67 | 68 | Note that all of these can be represented as a single character as well as the encoding. 69 | 70 | ## 1 byte 71 | 72 | - `k*` / `e` - exponentiation, vectorising 73 | - `k+` / `/` - floor division 74 | - `k[` / `|` - `[...|...;` else in an if block, otherwise `|...;` -> if not 75 | - `kM` / `(` -> for (same stack) - `(...;` 76 | - `k;` / `%` - modulo (vectorising) 77 | - `k{` / `E` - Eqiuvalent of `(n` 78 | - `kN` / `n` - 79 | - in `(` / `M` / `F`: Push context (loop var) 80 | - in global scope: Push 1 81 | - `kJ` / `S` - Sum / is prime 82 | - `k?` / `i` - index value (s) into list / bitwise xor 83 | - `k<` / `l` - Comparison (less than) 84 | - `kf` / `F` - Filter - `F...;` 85 | - `kb` / `R` - Reduce `R...;` 86 | - `k:` / `_` - Pop stack 87 | - `k>` / `}` - rotate stack right 88 | - `k$` / `r` - range (inclusive) 89 | - `kk` : useful constants 90 | 91 | ## 1.5 bytes 92 | 93 | - `kk[` / `0` - 100 94 | - `kkM` / `1` - 256 95 | - `kk{` / `2` - 26 96 | - `kk;` / `3` - 50 97 | - `kk+` / `4` - [0, 1] 98 | - `kk*` / `5` - 128 99 | - `kkJ` / `6` - 64 100 | - `kk>` / `7` - 32 101 | - `kk<` / `8` - 16 102 | - `kkN` / `9` - 1000 103 | 104 | 105 | - `kkf` / `s` - sort / prime factors 106 | - `kkb` / `Z`: 107 | - list, list -> zip 108 | - int, list -> repeat (vectorised) 109 | - list, int -> repeat 110 | - int, int -> repeat 111 | - `kk:` / `D` - is a divisible by B? (vectorising) 112 | - `kk$` / `W` - stack = [stack] 113 | - `kk?` / `,` - prettyprint with trailing newline 114 | - `kkk` / `.` - Print as chars without trailing newline 115 | 116 | ## Not scored 117 | 118 | These are debugging instructions that aren't included in the codepage. 119 | 120 | - `x` - Print stack -------------------------------------------------------------------------------- /halfwit.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | let halfwit = (code, inputs, flags = '') => { 3 | let string_so_far = '', in_int = false, tokens = []; 4 | 5 | if (flags.includes('h')) { 6 | return `- e - execute the filename as code 7 | - A - print as chars 8 | - c - print compiled code 9 | - C - count source length in Halfwit codepage 10 | - s - compress list / int 11 | - t - print tokens 12 | - p - print parsed tokens 13 | - h - print flag help menu` 14 | } 15 | 16 | if (flags.includes('C')) { 17 | let count = 0; 18 | for(let byte of code) { 19 | if ('[M{;+*>') + '<'; 39 | } else result = '>' + int_compress(value) + '<' 40 | return result; 41 | } 42 | 43 | const aliases = { 44 | 'k*': 'e', 'k+': '/', 'k[': '|', 'kM': '(', 45 | 'k;': '%', 'k{': 'E', 'kN': 'n', 'kJ': 'S', 46 | 'k?': 'i', 'k<': 'l', 'kf': 'F', 'kb': 'R', 47 | 'k:': '_', 'k>': '}','k$': 'r' , 48 | 'kk[': '0', 'kkM': '1', 'kk{': '2', 'kk;': '3', 49 | 'kk+': '4', 'kk*': '5', 'kkJ': '6', 'kk>': '7', 50 | 'kk<': '8', 'kkN': '9', 'kkf': 's', 'kkb': 'Z', 51 | 'kk:': 'D', 'kk$': 'W', 'kk?': ',', 'kk[': '.', 52 | } 53 | for (let char = 0; char < code.length; char++) { 54 | if (in_int) { 55 | if (code[char] == '<') { 56 | in_int = false; 57 | tokens.push(string_so_far + '<'); 58 | string_so_far = ''; 59 | } else { 60 | string_so_far += code[char]; 61 | } 62 | } else { 63 | if (code[char] == '>') { 64 | in_int = true; 65 | string_so_far += '>'; 66 | 67 | continue; 68 | } else if (code[char] == 'k') { 69 | string_so_far += 'k'; 70 | } else if (string_so_far) { 71 | tokens.push(string_so_far + code[char]) 72 | string_so_far = ''; 73 | } else { 74 | tokens.push(code[char]) 75 | } 76 | if (string_so_far.length == 3) { 77 | tokens.push(string_so_far); 78 | string_so_far = ''; 79 | } 80 | } 81 | } 82 | if (string_so_far) tokens.push(string_so_far); 83 | tokens = tokens.map(value => (aliases[value] ?? value)) 84 | .flatMap(value => value == 'E' ? ['(','n'] : value) 85 | 86 | let struct_stack = [], elems = []; 87 | 88 | for (let token of tokens) { 89 | if (token == '[') { 90 | struct_stack.push('if'); 91 | elems.push(['if']) 92 | } else if (token == 'M') { 93 | struct_stack.push('map'); 94 | elems.push(['map']) 95 | } else if (token == '{') { 96 | struct_stack.push('while','cond'); 97 | elems.push(['while']); 98 | } else if (token == ';') { 99 | if (struct_stack.at(-1) == 'cond') { 100 | elems.push(['cond']); 101 | struct_stack.pop() 102 | } else { 103 | elems.push(['end_' + struct_stack.pop()]) 104 | } 105 | } else if (token == '|') { 106 | if (struct_stack.at(-1) == 'if') { 107 | elems.push(['else']); 108 | } else { 109 | elems.push(['if_not']) 110 | struct_stack.push('if_not') 111 | } 112 | } else if (token == '(') { 113 | struct_stack.push('for'); 114 | elems.push(['for']) 115 | } else if (token == 'n') { 116 | if (['for','map','filter'].some(value => struct_stack.includes(value))) { 117 | elems.push(['ctx']) 118 | } else { 119 | elems.push(['const', 1n]); 120 | } 121 | } else if (token == 'F') { 122 | struct_stack.push('filter'); 123 | elems.push(['filter']) 124 | } else if (token == 'R') { 125 | struct_stack.push('reduce'); 126 | elems.push(['reduce']) 127 | } else if (token[0] == '>') { 128 | let str = '', strs = []; 129 | for (let i = 1; i < token.length; i++) { 130 | if (token[i] == '>' || token[i] == '<') { 131 | strs.push(str); 132 | str = ''; 133 | } else str += token[i]; 134 | } 135 | if (str) strs.push(str) 136 | let nums = strs.map(val => { 137 | for (let key in aliases) { 138 | val = val.replaceAll(aliases[key], key) 139 | } 140 | let int = 0n; 141 | for (let char of val) { 142 | int = int * 14n + BigInt('[M{;+*JNfb:$?k'.indexOf(char)); 143 | } 144 | return int 145 | }) 146 | if (nums.length == 1) nums = nums[0] 147 | elems.push(['const', nums]) 148 | } else { 149 | elems.push(['elem', token]) 150 | } 151 | } 152 | 153 | for (let item of struct_stack.reverse()) { 154 | elems.push(['end_' + item]) 155 | } 156 | 157 | let a = value => Array.isArray(value) 158 | let range = value => [...Array(Number(value)).keys()].map(BigInt) 159 | let tail = value => value[value.length - 1] 160 | 161 | let cycle_pop = value => { 162 | let head = value[0] ?? 0n; 163 | value.push(value.shift()) 164 | return head; 165 | } 166 | 167 | let pop = (stack, amount = -1) => { 168 | if (amount == -1) return stack.pop() ?? cycle_pop(tail(input_stack) ?? 0n); 169 | if (!amount) return []; 170 | let res; 171 | if (stack.length) res = stack.pop(); 172 | else res = cycle_pop(tail(input_stack)) ?? 0n; 173 | return [res, ...pop(stack, amount - 1)] 174 | } 175 | let vectorise = func => { 176 | let inner = 177 | func.length == 1 ? 178 | val => a(val) ? val.map(inner) : func(val) 179 | : (left, right) => { 180 | if (a(left) && a(right)) { 181 | if (left.length > right.length) { 182 | return left.map((val, i) => inner(val, right[i] ?? 0n)) 183 | } else { 184 | return right.map((val, index) => inner(left[index] ?? 0n, val)) 185 | } 186 | } else if (a(left)) { 187 | return left.map(val => inner(val, right)) 188 | } else if (a(right)) { 189 | return right.map(val => inner(left, val)) 190 | } else { 191 | return func(left, right) 192 | } 193 | } 194 | return inner 195 | } 196 | let sqrt = (value) => { 197 | if (value < 0n) { 198 | throw 'square root of negative numbers is not supported' 199 | } 200 | 201 | if (value < 2n) { 202 | return value; 203 | } 204 | 205 | function newtonIteration(n, x0) { 206 | const x1 = ((n / x0) + x0) >> 1n; 207 | if (x0 === x1 || x0 === (x1 - 1n)) { 208 | return x0; 209 | } 210 | return newtonIteration(n, x1); 211 | } 212 | 213 | return newtonIteration(value, 1n); 214 | } 215 | let isPrime = (num) => { 216 | if (num % 2n == 0) return false; 217 | if (num < 2n) return false; 218 | for (let i = 3n; i < sqrt(n); i += 2n) { 219 | if (num % i == 0) return false; 220 | } 221 | return true; 222 | } 223 | let compare = (left, right) => { 224 | if (typeof left == 'undefined') return -1; 225 | if (typeof right == 'undefined') return 1; 226 | if (a(left) && a(right)) { 227 | for (let i = 0; i < Math.max(left.length, right.length); i++) { 228 | if (compare(left[i], right[i])) { 229 | return compare(left[i], right[i]) 230 | } 231 | } 232 | return 0; 233 | } 234 | if (a(left)) return 1; 235 | if (a(right)) retuurn -1 236 | return left == right ? 0 : left > right ? 1 : -1; 237 | } 238 | let primeFactors = (num) => { 239 | let factors = []; 240 | for (let i = 2n; i <= num; i++) { 241 | while (num % i == 0n) { 242 | factors.push(i); 243 | num /= i; 244 | } 245 | } 246 | return factors; 247 | } 248 | let repr = (value) => JSON.stringify(value, 249 | (_, v) => typeof v == 'bigint' ? 250 | v.toString() + 'n': v 251 | )?.replace(/"(\d+n)"/g, '$1') 252 | let toStr = value => { 253 | if (!a(value)) { 254 | return String.fromCharCode(Number(value)) 255 | } else if (value.every(a)){ 256 | return value.map(val => String.fromCharCode(...val.map(Number))).join`\n` 257 | } else { 258 | return String.fromCharCode(...value.map(Number)) 259 | } 260 | } 261 | let onerange = value => a(value) ? value : [...Array(Number(value))].map((_,x) => BigInt(x + 1)) 262 | let bool = value => (a(value) ? value.length : value != 0n) ? 1n : 0n 263 | let elements = { 264 | '+': [ vectorise((a, b) => a + b), 2, 1], 265 | '*': [ vectorise((left, right) => left * right), 2, 1], 266 | 'J': [ (left, right) => { 267 | if (a(left)) return left.concat(right); 268 | if (a(right)) return [left, ...right] 269 | return [left, right] 270 | }, 2, 1], 271 | 'N': [ vectorise(val => -val), 1, 1], 272 | 'f': [ val => a(val) ? val.flat() : val * val, 1, 1], 273 | 'b': [ (left, right) => { 274 | if (a(left)) { 275 | if (!a(right)) right = range(right) 276 | let base = BigInt(right.length), res = 0n; 277 | for (let item of left){ 278 | res *= base; 279 | res += BigInt(right.indexOf(item)) 280 | } 281 | return res; 282 | } else { 283 | if (!a(right)) right = range(right); 284 | let res = [], base = BigInt(right.length); 285 | while (left) { 286 | res.unshift(right[left % base]) 287 | left /= base 288 | } 289 | return res; 290 | } 291 | }, 2, 1], 292 | ':': [ val => [val, val], 1, 2], 293 | '$': [ (left, right) => [right, left], 2, 2], 294 | '?': [ () => cycle_pop(input_stack[0]), 0, 1], 295 | 'e': [ vectorise((left, right) => left ** right), 2, 1], 296 | '/': [ vectorise((left, right) => left / right), 2, 1], 297 | '%': [ vectorise((left, right) => left % right), 2, 1], 298 | 'S': [ value => a(value) ? value.reduce(elements['+'][0]) : BigInt(isPrime(value)), 1, 1], 299 | 'i': [ (left, right) => { 300 | let index = (list, elem) => { 301 | length = BigInt(list.length) 302 | if (elem >= 0n){ 303 | return list[elem % length] 304 | } else { 305 | return list[length + (elem + 1) % BigInt(length) - 1] 306 | } 307 | } 308 | if (a(left) && a(right)) { 309 | return right.map(val => index(left, val)) 310 | } else if (a(left)){ 311 | return index(left, right) 312 | } else if (a(right)){ 313 | return index(right, left) 314 | } else { 315 | return left ^ right 316 | } 317 | }, 2, 1], 318 | 'l': [ (left, right) => compare(left, right) == -1 ? 1n : 0n , 2, 1], 319 | '_': [ () => [], 1, 0], 320 | 'r': [ vectorise(val => Array(Number(val)).map((_, i) => i + 1)), 1, 1], 321 | '0': [ () => 100n, 0, 1], 322 | '1': [ () => 256n, 0, 1], 323 | '2': [ () => 26n, 0, 1], 324 | '3': [ () => 50n, 0, 1], 325 | '4': [ () => [0n, 1n], 0, 1], 326 | '5': [ () => 128n, 0, 1], 327 | '6': [ () => 64n, 0, 1], 328 | '7': [ () => 32n, 0, 1], 329 | '8': [ () => 16n, 0, 1], 330 | '9': [ () => 1000n, 0, 1], 331 | 's': [ val => a(val) ? val.sort(compare) : primeFactors(val), 1, 1], 332 | 'Z': [ (left, right) => { 333 | let repeat = (val, int) => Array(Number(int)).fill(val); 334 | if (a(left) && a(right)) { 335 | if (left.length > right.length) [left, right] = [right, left]; 336 | return right.map((val, i) => [val, left[i] ?? 0n]) 337 | } 338 | if (a(right)){ 339 | return right.map(val => repeat(val, left)) 340 | } 341 | return repeat(left, right) 342 | }, 2, 1], 343 | 'D': [ vectorise((left, right) => left % right === 0n) ], 344 | ',': [ value => (printed = true) && (output += repr(value) + '\n'), 1, 0], 345 | '.': [ value => { printed = true; output += toStr(value)}, 1, 0], 346 | 'x': [ () => console.debug(repr(stack)), 1, 0], 347 | 'p': [ () => console.debug(repr(pop(stack))), 1, 0], 348 | }; 349 | let compiled = ''; 350 | let hash = () => 'x' + Math.random().toString(16).slice(2); 351 | for (let token of elems) { 352 | if (token[0] == 'elem' ) { 353 | if (token[1] == '}') compiled += 'stack.unshift(stack.pop());' 354 | else if (token[1] == 'W') compiled += 'stack=[stack];' 355 | else { 356 | let elem = elements[token[1]] 357 | if (!elem) continue; 358 | compiled += [`(`,`stack.push(`,`stack.push(...`][elem[2]] + `elements['${token[1]}'][0](...pop(stack,${elem[1]}).reverse()));` 359 | } 360 | } else if (token[0] == 'const') { 361 | compiled += `stack.push(${repr(token[1])});` 362 | } else if (token[0] == 'if') { 363 | compiled += `if(bool(pop(stack))){` 364 | } else if (token[0] == 'else') { 365 | compiled += `}else{` 366 | } else if (token[0] == 'if_not') { 367 | compiled += `if(!bool(pop(stack))){` 368 | } else if (token[0] == 'for') { 369 | let variable = hash(), var2 = hash(); 370 | compiled += `let ${variable}=pop(stack);if(!bool(a(${variable})))${variable}=range(${variable});for(let ${var2} of ${variable}){let ctx=${var2};` 371 | } else if (token[0] == 'ctx') { 372 | compiled += `stack.push(ctx??1n);` 373 | } else if (token[0] == 'map') { 374 | compiled += `;stack.push(onerange(pop(stack)).map(value=>{let ctx=value;input_stack.push([value]);let stack=[];` 375 | } else if (token[0] == 'end_map') { 376 | compiled += `var res=pop(stack);input_stack.pop();return res}));` 377 | } else if (token[0] == 'filter') { 378 | compiled += `;stack.push(onerange(pop(stack)).filter(value=>{let ctx=value;input_stack.push([value]);let stack=[];` 379 | } else if (token[0] == 'end_filter') { 380 | compiled += `var res=pop(stack);input_stack.pop();return bool(res)}));` 381 | } else if (token[0] == 'reduce') { 382 | compiled += `;stack.push(onerange(pop(stack)).reduce((current,next)=>{input_stack.push([next, current]);let stack=[];` 383 | } else if (token[0] == 'end_reduce') { 384 | compiled += `var res=pop(stack);input_stack.pop();return res}));` 385 | } else if (token[0] == 'end_for') { 386 | compiled += `}` 387 | } else if (token[0] == 'end_if') { 388 | compiled += `}` 389 | } else if (token[0] == 'end_if_not') { 390 | compiled += `}` 391 | } else if (token[0] == 'while') { 392 | compiled += 'while(1){' 393 | } else if (token[0] == 'cond') { 394 | compiled += `if(!bool(pop(stack)))break;` 395 | } else if (token[0] == 'end_while') { 396 | compiled += `}` 397 | } 398 | } 399 | if (flags.includes('p')) console.log(repr(elems)) 400 | if (flags.includes('c')) console.log(compiled) 401 | if (flags.includes('t')) console.log(repr(tokens)) 402 | 403 | let output = '', stack = [], printed = false, input_stack = [inputs]; 404 | inputs = vectorise(value => 405 | typeof value == 'number' ? 406 | BigInt(value) 407 | : typeof value == 'string' ? 408 | [...value].map(char => BigInt(char.charCodeAt())) 409 | : value) (inputs) 410 | 411 | eval(compiled); 412 | if (!printed){ 413 | if (flags.includes('A')) output += toStr(stack.pop()) 414 | else output += repr(stack.pop() ?? 0n) 415 | } 416 | return output; 417 | } 418 | 419 | if (typeof module !== "undefined" && module.exports) { 420 | module.exports = halfwit; 421 | } else { 422 | window.halfwit = halfwit 423 | } --------------------------------------------------------------------------------