Civet icon indicating copy to clipboard operation
Civet copied to clipboard

Templates

Open edemaine opened this issue 2 years ago • 1 comments
trafficstars

Nim-style templates seem like a clean alternative to macros (#467), which don't require a stable AST/API. Essentially they are inlined functions, where the AST of the template gets plugged in wherever it gets called.

Phase 1: No Arguments

template show
  console.log `num is ${num}; data is ${JSON.stringify data}` if debugging

debugging := true
function f()
  num := 5
  data := [1, 2, 3]
  show()
function g(num, data)
  debugging := num % 2
  show()
↓↓↓
const debugging = true
function f() {
  const num = 5
  const data = [1, 2, 3]
  if (debugging) console.log(`num is ${num}; data is ${JSON.stringify(data)}`)
}
function g(num, data) {
  debugging := num % 2
  if (debugging) console.log(`num is ${num}; data is ${JSON.stringify(data)}`)
}

Note how show() uses the local variables debugging/num/data wherever it gets invoked. The idea is that this can reduce repeated code, without having to add lots of arguments.

Phase 2: Arguments

Template arguments make for more interesting examples, but requires further processing, especially for ...rest. Here's an example where you really couldn't do it with a function, because it uses return to pop out of the promise function.

template run(func, ...args)
  try
    func ...args
  catch
    console.warn `Failed to run ${func.name} with ${JSON.stringify args}`
    return reject `error during ${func.name}`

new Promise (resolve, reject) =>
  run step1, 'hello'
  run step2, 'goodbye'
  resolve 'done'
↓↓↓
new Promise((resolve, reject) => {
  try {
    step1(...['hello'])  // or step1('hello')
  } catch {
    console.warn(`Failed to run ${step1.name} with ${JSON.stringify(['hello'])}`)
    return reject(`error during ${step1.name}`)
  }
  try {
    step2(...['goodbye'])
  } catch {
    console.warn(`Failed to run ${step2.name} with ${JSON.stringify(['goodbye'])}`)
    return reject(`error during ${step2.name}`)
  }})()
  return resolve('done')
})

The idea is that each use of an argument to a template gets replaced by the parse tree that is passed in. This lets you automate defining variables (or functions or classes), for example:

nextId .= 0
template id(v)
  v := nextId++
id foo
id bar
↓↓↓
let nextId = 0
const foo = nextId++
const bar = nextId++

If you used a function, you'd need to use different syntax, like foo := id().

Phase 3: import/export

Likely we'll want to do this in a single file first. Supporting export/import of templates would require a lot more work, as we'd have to examine other files. But eventually it could look like:

// foo.civet
export template foo
  ...
// bar.civet
import {template foo} from './foo.civet'

edemaine avatar Jul 19 '23 15:07 edemaine

Since template would expand the list of reserved words we may be able to do something like function! id(v) ... This reinforces the concept that it's an inlined function and can be kind of 👻

STRd6 avatar Jul 19 '23 17:07 STRd6