Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add back declarative macro definition forms #516

Open
disnet opened this issue Mar 28, 2016 · 10 comments
Open

Add back declarative macro definition forms #516

disnet opened this issue Mar 28, 2016 · 10 comments

Comments

@disnet
Copy link
Member

disnet commented Mar 28, 2016

Version 1.0 only comes with primitive syntax transformer forms which is obviously not great for everyday use. Some initial syntax thoughts for a more declarative form:

// pattern matching
syntax new = function (ctx) {
  return match (ctx) {
    case`new $ident $params` => #`$ident.create $params`
  }
}
syntax let = function (ctx) {
  return match (ctx) {
    case`let $ident = $init:expr $rest ...` => #`
      (function ($ident) {
        $rest ...
      })($init)
    `
  }
}

This should happen after #378 and import ... for macros lands so the matching can just be a library.

@gabejohnson
Copy link
Member

gabejohnson commented Jun 30, 2016

How about "destructuring" syntax templates:

syntax new = function (ctx) {
  return match (ctx) {
    case #`new ${ ident } ${ params }` => #`${ ident }.create ${ params }`
  }
};

syntax let = function (ctx) {
  return match (ctx) {
    case #`let ${ ident } = ${ init : AssignmentExpression } ${ ...rest }` => #`
      (function (${ ident }) {
        ${ ...rest }
      })(${ init })
    `
  }
};

It's a little more verbose, but has nice symmetry with the current syntax template literals and es2015+ destructuring assignments.

Perhaps this could be an option:

syntax new = (#`new ${ ident } ${ params }`) => #`${ ident }.create ${ params }`;

@gabejohnson
Copy link
Member

gabejohnson commented Jun 30, 2016

I have a port of Clojure threading macros in mind:

syntaxrec threadFirst = ctx => {
  const inner = ctx.next().value.inner();
  let args, call;
  match (inner) {
    case #`(${ val }) { ${ ident } (${ args }) ${ ...rest } }` => {
      args = #`${ val }, ${ ...args }`;
      call = #`${ ident }(${ args })`;
    }
    case #`(${ val }) { ${ ident } ${ ...rest } }` => {
      call = #`${ ident }(${ val })`;
    }
  }
  return rest.isEmpty() ? call : #`threadFirst (${ call }) { ${ ...rest } }`;
}

const inc = x => x + 1;
const mult = (x, y) => x * y;
const div = (x, y) => x / y;

threadFirst (2) { inc mult(3) inc div(5) } // 2

@gabejohnson
Copy link
Member

I'm currently blocked on #519. In the meantime, I've got a question about semantics:

let #`1 + ${a} - ${b}` = #`1 + 2 - 3`; // let a = #`2`, b = #`3`

let #`1 + ${c} - 3 + ${d}` = #`1 + 2 - 4 + 5`; // let c = #`2`, d = undefined

let #`1 + ${e} - ${f} + 4` = #`1 + 2 - 3 + 5`; // let e = #`2`, f = ?

Should f in the last case be 3 or undefined? A case could be made for either one. My gut tells me to be consistent w/ Object and Array destructuring and let f = 3.

OTOH I was hoping matching could be naively implemented by checking that all of the placeholder variables point to a value.

@disnet
Copy link
Member Author

disnet commented Jul 11, 2016

Humm...why doesn't the second and third cases just throw a "failed to match" error?

@gabejohnson
Copy link
Member

Yeah. I guess for destructuring values shouldn't be allowed on the LHS. It would be an Unexpected token error if I tried that w/ objects or arrays. So destructuring only makes sense if just placeholders are present in the LHS template:

let #`${a} ${b} ${c}` = #`(1 + 2) * 3`; // let a = #`(1 + 2)`, b = #`*`, c = #`3`

And I suppose nested destructuring takes the analogy too far?

let #`${ #`${a} ${b} ${c}` }` = #`(1 + 2)`; // let a = #`1`, b = #`+`, c = #`2`

@gabejohnson
Copy link
Member

gabejohnson commented Jan 18, 2017

I'm thinking about this again and I have some ideas:

const BindingElement = ctx => match(ctx) {
  // isPunctuator(,) - consumes a ',' if it exists and is a Punctuator returning true or false
  // comma? - comma is optional. if it isn't present ctx is reset to right before that point
  // BindingTarget - call functions with those names w/ ctx as the argument
  #`${binding: BindingTarget} = ${init: Expression} ${comma?: isPunctuator(,)}` => new T.BindingWithDefault({ binding, init })

  #`${binding: BindingTarget} ${comma?: isPunctuator(,)}` => binding
};

const FormalParameters = ctx => match(ctx) {
  #`()` => new T.FormalParameters({ items: List() })

  #`(...${rest: BindingIdentifier})` => new T.FormalParameters({ items: List(), rest })

  // ...items calls BindingIdentifier repeatedly and returns a List
  #`(${...items: BindingElement} ...${rest: BindingIdentifier})` => new T.FormalParameters({ items, rest })

  #`(${...items: BindingElement})` => new T.FormalParamters({ items })
};

function BindingTarget(ctx) {
  return match(ctx) {
    #`${name: Identifier}` => new T.BindingIdentifier({ name })

    #`[]` => new T.ArrayBinding({ elements: List() })

    #`[...${rest: BindingTarget}]` => T.ArrayBinding({ elements: List(), rest })

    #`[${...elements: BindingElement} ...${rest: BindingTarget}]` => new T.ArrayBinding({ elements, rest })

    #`[${...elements: BindingElement}]` => new T.ArrayBinding({ elements })

    #`{}` => new T.ObjectBinding({ properties: List() })

    #`{${...properties: BindingProperty}}` => new T.ObjectBinding({ properties })
  };
}

syntax function = ctx => match(ctx) {
  #`${star?: isPunctuator(*)} ${name?: BindingIdentifier} ${params: FormalParameters} ${body: Statement}` =>
    new T.FunctionExpression({ name, isGenerator: star, params, body });
};

I don't intend to implement the enforester as a bunch of macros. This is just to show the features I have in mind. Though we could have theexpandMacro not splice the result back into rest if it's a term (and not a list).

@disnet
Copy link
Member Author

disnet commented Jan 24, 2017

Yeah, I think I really like this.

@jimmyhmiller
Copy link

The declarative syntax was to me the biggest selling point of sweetjs. I understand there is obviously a lot of work that has to be done to the core in order to add this back.

I was just wondering if a declarative syntax still in the works? Is there a list somewhere of what needs to be accomplished before it can be added back in?

@disnet
Copy link
Member Author

disnet commented Jul 31, 2017

@jimmyhmiller I actually don't think there's anything holding this back. In fact I don't think we even need to do this in core since match (or something like it) can just be a macro that expands to the appropriate low-level matching.

@curiousdannii
Copy link

0.7.8 is still available and it works great.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants