)
}
}
)
(
}
{
)
)
(
)
(
(
{
}
)
(
)
}
)
)
{
(
(
)
)
}
)
(
}

Concatenative JavaScript

  1. const vars = {}
  2.  
  3. const as = s => v => vars[s] = v
  4.  
  5. const def = s => v => {
  6.   vars[s] = v
  7. }
  8.  
  9. const set = (last, prop, val) => {
  10.   last[prop] = val
  11. }
  12.  
  13. const put = (last, prop, val) => {
  14.   last[prop] = val
  15.   return last
  16. }
  17.  
  18. const of = k => run.last[k]
  19. const $ = k => () => vars[k]
  20.  
  21. const run = (prog, log) => {
  22.   run.stack = []
  23.   let curr
  24.   for (let i = 0; i < prog.length; i++) {
  25.     let val = prog[i]
  26.     if (val != null && val === $) { 
  27.       run.stack.push(val())
  28.     } else if (typeof val === 'function') {
  29.       let ctx = null
  30.       if (document.body[val.name]) { 
  31.         ctx = document.body 
  32.         val = val.bind(ctx)
  33.       } else if (document[val.name]) { 
  34.         ctx = document
  35.         val = val.bind(ctx)
  36.       } 
  37.  
  38.       if (log) console.log('exec', 
  39.         val.name == 'bound ' ? '\u03BB' : val.name, run.stack)
  40.  
  41.       curr = val.apply(ctx, run.stack)
  42.       if (curr != run.stack) { 
  43.         run.stack = curr != null ? [curr] : []
  44.         if (typeof curr === 'object') {
  45.           run.last = curr
  46.         }
  47.       }
  48.     } else {
  49.       if (val != null) run.stack.push(val)
  50.     }
  51.   }
  52. }
  53.  
  54. const showStack = () => {
  55.   console.log('- stack:', run.stack)
  56.   console.log('  -> last object', run.last)
  57.   return run.stack
  58. }
  59.  
  60. const _ = new Proxy({}, { 
  61.   get: (o, key) => (() => (...a) => 
  62.     run.last[key].apply(run.last, a)
  63.   )()
  64. })
  65.  
  66. // try it out
  67.  
  68. run( 
  69.   [ 'div', 
  70.     document.createElement, 
  71.     document.body.appendChild, 
  72.     as`myDiv`,
  73.     'innerHTML', 'mini <b>concatenative</b>', set,
  74.     'style', of, 'color', 'green', set,
  75.  
  76.     'just logging the element', $`myDiv`, console.log, 
  77.  
  78.     'canvas', document.createElement, 
  79.     document.body.appendChild, 
  80.     'width', 500, put,
  81.     'height', 500, put,
  82.     def`myCanvas`,
  83.  
  84.     '2d',
  85.     _.getContext,
  86.     'fillStyle', 'red', set,
  87.     100, 30, _.translate,
  88.     0, 0, 10, 100, _.fillRect,
  89.     20, 0, 10, 100, _.fillRect,
  90.     .2, _.rotate,
  91.     -30, 25, 100, 10, _.fillRect,
  92.     -20, 55, 100, 10, _.fillRect,
  93.  
  94.     {}, 
  95.     'name', 'zevan', put,
  96.     'age', 82, put,
  97.     JSON.stringify,
  98.     as`person`,
  99.  
  100.     showStack
  101.   ]
  102. )
  103. console.log(vars);

I recently spent some time reading about and playing with the FORTH language. I keep finding myself creating weird vaguely FORTH inspired snippets now… This one processes an array that’s in a concatenative format like this:

[ 'hello world', Date.now(), console.log ]

Arguments are collected on a stack and passed to a function when a function is hit in the array. There are some odd helper functions for setting properties, setting nested properties, running functions with a specific context etc…


Here is GPT4’s mostly correct explanation of what it does

This is a rather unusual piece of JavaScript code that creates a simple “programming language” based on the concept of a stack machine, and runs a “program” in this language. It resembles some sort of DSL (domain-specific language) or script-like construct.

Here are what the main components do:

  1. vars: This is a dictionary that stores variables defined in the program.
  2. as: This is a function that takes a string s and returns a function that takes a value v, storing v in vars under the key s.
  3. def: Similar to as, it defines a variable in vars.
  4. set: This function is used to set the property of an object. It takes the object last, the property name prop, and the value val, and assigns val to the property prop of the object last.
  5. put: Similar to set, it sets the property of an object and returns the updated object.
  6. of: This function gets the property with name k of the last object returned by the program.
  7. $: This function is a getter for variables in vars.
  8. run: This is the main function that runs the program. The program is an array of actions, where an action can be a function, an object with a name of ‘get’ (which pushes the result of calling the function onto the stack), or a value (which is pushed onto the stack).
  9. showStack: This function logs the current state of the stack and the last object returned.
  10. _: This creates a new Proxy object that allows arbitrary methods to be called on the last object returned by the program.

The run function is then called with a list of actions to perform. The actions include creating a div and canvas element, appending them to the body of the document, setting their properties, and drawing on the canvas. It also creates an object with a name and age, converts it to JSON, and stores it in a variable. Finally, it shows the state of the stack and logs all the variables.

Proxy Constants

  1. const spec = {
  2.   get(o, key) { 
  3.     return o[key] != null ? 
  4.       o[key] : o[key] = Objector()
  5.   }
  6. };
  7.  
  8. const Objector = () => new Proxy({}, spec);
  9.  
  10. const events = Objector();
  11.  
  12. events.graphics.RENDERED;
  13. events.graphics.ERASED;
  14. events.ui.LOADING;
  15. events.ui.LOADED;
  16. events.files.OPENED;
  17. events.files.CLOSED;
  18.  
  19. const { ERASED } = events.graphics;
  20. console.log('a', ERASED === events.graphics.ERASED);
  21. console.log('b', ERASED === events.files.CLOSED);

This is somewhat evil… I’ve never liked these java style constants. Maybe I’ll write up a detailed alternative method some time.

// humor // proxies // tricks

Proxy Quick DOM

  1. const spec = {
  2.   get(o, key) {
  3.     return o[key] != null ? 
  4.       o[key] : o[key] = (...args) => {
  5.         const el = document.createElement(key);
  6.         args.forEach(arg => { 
  7.           if (typeof arg === 'string') {
  8.             const span = document.createElement('span');
  9.             span.innerHTML = arg;
  10.             el.appendChild(span);
  11.           } else if (typeof arg === 'object') {
  12.             if (arg.tagName != null) {
  13.               el.appendChild(arg);
  14.             } else {
  15.               for (let i in arg) {
  16.                 el.setAttribute(i, arg[i]);
  17.               }
  18.             }
  19.           }
  20.         });
  21.         return el;
  22.       }
  23.   },
  24.   set(o, key, v) {
  25.     o[key] = v;
  26.   }
  27. }
  28.  
  29. const dom = new Proxy({}, spec);
  30.  
  31. document.body.appendChild(
  32.   dom.div(
  33.     dom.button('cool'), 
  34.     dom.h2('some text', { style: 'font-style: italic' }), 
  35.     dom.br(), 
  36.     dom.input({ placeholder: 'zevan' })
  37.   )
  38. );
  39.  
  40. const { div, input, label } = dom;
  41. document.body.appendChild(
  42.   div(
  43.     label(
  44.       'Slider:',
  45.       { 
  46.         for: 'slider', 
  47.         style: 'padding:1em;display:block' 
  48.       },
  49.       input({ id: 'slider', type: 'range' })
  50.     )
  51.   )
  52. );

In this snippet a proxy is used that makes all html node tagNames valid methods. Each method can take strings and HTMLElements as arguments in any order to create a dom structure. This may look familiar to people who have looked a bit deeper into the inner workings of some of the popular UI libraries of the last decade.

// dom // hacks // javascript // proxies // tricks // ui
snippet.zone ~ 2021-24 /// {s/z}