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

Comments

4 participants
@disnet
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

This comment has been minimized.

Show comment
Hide comment
@gabejohnson

gabejohnson Jun 30, 2016

Member

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 }`;
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

This comment has been minimized.

Show comment
Hide comment
@gabejohnson

gabejohnson Jun 30, 2016

Member

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
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

This comment has been minimized.

Show comment
Hide comment
@gabejohnson

gabejohnson Jul 9, 2016

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.

Member

gabejohnson commented Jul 9, 2016

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

This comment has been minimized.

Show comment
Hide comment
@disnet

disnet Jul 11, 2016

Member

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

Member

disnet commented Jul 11, 2016

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

@gabejohnson

This comment has been minimized.

Show comment
Hide comment
@gabejohnson

gabejohnson Jul 11, 2016

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`
Member

gabejohnson commented Jul 11, 2016

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

This comment has been minimized.

Show comment
Hide comment
@gabejohnson

gabejohnson Jan 18, 2017

Member

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).

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

This comment has been minimized.

Show comment
Hide comment
@disnet

disnet Jan 24, 2017

Member

Yeah, I think I really like this.

Member

disnet commented Jan 24, 2017

Yeah, I think I really like this.

@jimmyhmiller

This comment has been minimized.

Show comment
Hide comment
@jimmyhmiller

jimmyhmiller Jul 28, 2017

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?

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

This comment has been minimized.

Show comment
Hide comment
@disnet

disnet Jul 31, 2017

Member

@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.

Member

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

This comment has been minimized.

Show comment
Hide comment
@curiousdannii

curiousdannii Jul 31, 2017

0.7.8 is still available and it works great.

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