microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.28k stars 12.39k forks source link

Proposal: embedded syntax block #3022

Closed tinganho closed 9 years ago

tinganho commented 9 years ago

I have watched this PR #2673 and this #296 discussion for a while. But I somehow don't think it is the right approach to accept JSX directly into TS. There seem to be a demand of embedding HTML and even CSS in JS. Facebook might be the pioneer in embedding HTML in JS and therefore gotten a lot of attention recently with JSX/React. If JSX got accepted into TS, it would probably send a clear message to the JS and TS community that the TS team thinks JSX is the right way(and the only way) to go in terms of rendering views. I think it is more a correct way to accept an "embedded syntax block", so different frameworks have a chance to compete with JSX. It is a more generalized approach as oppose to accepting JSX directly into the TS.

Also remember that @ahejlsberg said "A framework is created nearly every day". So who knows if React will survive the next 2 years? Just remember how fast Angular 1 was popular and very quickly perceived as outdated(roughly a year?). That's how fast the web moves today.

So in rough terms here is my thoughts on this "embedded syntax block". I will take JSX as an example:

First, we refer to a JSX to TS transpiler. So we can transpile JSX to TS in our source code:

///<transpiler name="jsx" path="../node_modules/jsx-ts-transpiler/transpiler.ts"/>

Now, we define a JSX block we want transpiled into TS using the syntax syntax 'jsx' {...}:

let HelloMessage = React.createClass({
  render: function() {
    return syntax 'jsx' { <div>Hello {this.props.name}</div> }
  }
});

The TS's scanner stops at syntax 'jsx' { and feeds the string that reaches the closing brace } of the syntax block to the JSX-TS transpiler in this case: <div>Hello {this.props.name}</div>. The JSX-TS transpiler emits the correct TS code back along with a source map so we can map any lines and columns in the emitted TS back to our JSX. Here is how the transpiler looks like:

export transpile(source: string): Output {
  // Do something with the source
  return { output, sourceMap, diagnostics };
}

Now the TS scanner goes ahead an parse it as if it was a normal TS file.

let HelloMessage = React.createClass({
  render: function() {
    return React.createElement("div", null, "Hello", this.props.name);
  }
});

Now, we want all AST nodes outside of any "embedded syntax block" to have the same positions(pos and end) as if they where without any line and column shifts. So that we can have the correct positions on errors and warnings autocomplete etc. This is easily done by just offsetting a node's pos and end by keeping track of how much the emitted JSX-TS output shifts in character positions. For the emitted AST nodes we would marked them coming from a particular embedded syntax block(the embedded syntax block itself is an AST node with pos and end). The emitted TS nodes will be assigned pos and end beginning from 0 and not the start of its' syntax block.

Now, we have all the information we need to output the correct positions in source map and diagnostics. The diagnostics and the source map needs to be rewritten to take into consideration the new embedded syntax blocks along with its' emitted TS code.

I think this is a more open approach. If you accepts JSX directly into TS, then you are practically closing the door for any future syntax embeds and letting Facebook decide the future of syntax embeds.


Updates 1:

Since the TS scanner can't decide where a syntax embed block ends we would need the transpiler to help us so here is the updated transpiler:

let output = "";
let sourceMap;
let diagnostics;
let statements;

export function pushChar(ch: number): boolean {
  // Do something that produces an AST

  // Return false if it is the end of a syntax embed block else return true.
  return true;
}

export function transpile(): Output {
  // Take the AST and produce the output, source map and diagnostics.
  return { output, sourceMap, diagnostics};
}

The TS scanner pushes characters to our transpiler with pushChar() and receives true or false, whether to proceed with pushing characters to our transpiler or get the transpiled output with transpile().

The syntax for defining syntax embed could drop the syntax keyword:

'jsx' {  <div>Hello {{this.props.name}}</div> }
Arnavion commented 9 years ago
tinganho commented 9 years ago

How would the transformer do more complicated transformations that just string replacements on the source string? For example, how would it do a transformation that's dependent on the types of the things in the string that it's transforming, if all it has access to is the string itself?

It's meant to transforms one string to an another. So a perfect use case is embedding HTML templates or creating your own CSS language that can live inline with your TS code. But instead of writing it in strings("..."), which React team and others like me thinks it creates "syntax pollution". Or writing it on a separate file you would need to create a separate build and tape things together which is quite a hell.

It isn't meant to be a "full" programming language to "full" programming language embed. So it isn't meant that you can embed PHP code. There is no benefit of embedding a full programming language than to write TS code alone. JSX described itself as "XML-like syntax extension to EcmaScript" — and that is what this embed functionality is meant for extending the TS language with additional syntaxes.

Even parsing the string becomes non-trivial when you consider a transform that supports embedded JS / TS (such as React). Then the transform function needs to invoke a full parser, perhaps invoke the TS parser in some sort of nested context.

The proposed embedded syntax block is only functioning as sugar. If the embed already contains TS code that are marked(in JSX it is marked inside { ... }). The one who writes the transpiler need to make sure that code isn't transformed. The transpiler doesn't need to understand TS code — just where it is. Even a trivial example let element = <MyComponent/> we can figure out what is TS code and what is JSX.

I guess with your proposal that type-checking, completion information, etc. will be provided by TS on the transformed code, and not by the transformer itself.

yes.

Do braces inside the string need to be balanced? That is, can the DSL inside the syntax block have a closing brace without an open brace and not confuse the TS parser into thinking the syntax block ends earlier than it actually does?

I missed this in my proposal. If balancing works then yes.

Arnavion commented 9 years ago

The transpiler doesn't need to understand TS code — just where it is.

My point is that it does. To take your example:

syntax 'jsx' { <div>Hello {this.props.name}</div> }

I assume that this will be passed as the string "<div>Hello {this.props.name}</div>" to transpile(). Then transpile() will need to interpret this string to realize that it consists of a tag "div", that then contains the string "Hello " and a TS expression "this.props.name", and then generate the appropriate codegen in your OP.

Now consider if this TS expression contains braces, double quotes, escapes, etc. Now transpile() has to implement some of the TS parser to be able to make sense of this embedded DSL. In fact, there can be more than one such {...} expression and you need to know where one ends and the next begins.

syntax 'jsx' { <div>Hello { `this.props.name${ "I'm a template string expression which has its own braces
and contains newlines. I could even contain entire JS statements inside IIFEs." } { This is an unterminated open brace` } }</div> }

transpile() will have a hard time parsing this and converting it into real TS without being able to invoke the TS parser on it.

What you're essentially describing is a very unrestricted pluggable parser-emitter, which I'm not sure is doable with a grammar such as TS. Compare this to, say, sweet.js, where the unit of transformation for the macro engine is pre-parsed tokens that vaguely resemble JS, not arbitrary text.

tinganho commented 9 years ago

Now consider if this TS expression contains braces, double quotes, escapes, etc. Now transpile() has to implement some of the TS parser to be able to make sense of this embedded DSL. In fact, there can be more than one such {...} expression and you need to know where one ends and the next begins.

It was a rough proposal :smile: . But one can probably make the TS evaluated only inside {{...}} or any syntax that is very distinguished from TS, in worst case even add escape characters and let the transpiler unescape them.

Instead of having balanced braces for calculating the end of the embed. All characters are feed one-by-one to a scanner/parser of the transpiler including the open brace { and ending brace } of the embed and the scanner/parser returns if is the end of the embed or not for every character.

basarat commented 9 years ago

///<transpiler name="jsx" path="../node_modules/jsx-ts-transpiler/transpiler.ts"/>

Perhaps better registered as transpilers using tsconfig.json.

Arnavion commented 9 years ago

But one can probably make the TS evaluated only inside {{...}} or any syntax that is very distinguished with TS

Yep, exactly my point. That is, your transpile() isn't a function from source string to TS. Rather, the TS parser needs to first split the text inside the syntax block into strings and expressions, and transpile() would get such a sequence of strings and expressions that it can then compose into the final TS codegen.

(And that should sound a lot like tagged template strings, because it is. Although template string tag functions deal with the results of embedded expressions, not the expressions themselves; but it's the same in that the parser needs to be aware of how to parse them and the tagger needs to work on the separate components. Of course, the value here is to retain the existing DSL rather than have to convert it to template strings.)

tinganho commented 9 years ago

Yep, exactly my point. That is, your transpile() isn't a function from source string to TS. Rather, the TS parser needs to first split the text inside the syntax block into strings and expressions, and transpile() would get such a sequence of strings and expressions that it can then compose into the final TS codegen.

Just for clarifications, there is no need for splits? I meant {{...}} is for TS code inside JSX and then feed every character to the JSX-TS scanner/parser and the scanner/parser returns if it is in the end of the syntax embed or not.

So an end can be calculated even if we have(no need to involve the TS parser):

syntax 'jsx' { <div>Hello {{`hello world :{  ${message`}}</div> }

The above example will generate this invalid TS code(from valid JSX code):

React.createElement('div', null, 'Hello' , `hello world :{ ${message`);
Arnavion commented 9 years ago

No matter how complicated you make your delimiter, it'll always be possible to use it in a way where it doesn't function as a delimiter.

syntax 'jsx' { <div>Hello {{ "hello world }} {{" }}</div> }

consists of a string "Hello" and a single expression - the string "hello world }} {{". A naive algorithm that doesn't understand TS syntax and only looks for the delimiter would instead infer two expressions.

tinganho commented 9 years ago

As mentioned earlier, you would need to escape }} {{, though having {{ and }} makes escaping happening rarely as oppose to just having { and }.

syntax 'jsx' { <div>Hello {{ "hello world \}\} \{\{" }}</div> }

The transpiler will then unescape those:

React.createElement('div', null, 'Hello' , "hello world }} {{");
tinganho commented 9 years ago

Perhaps better registered as transpilers using tsconfig.json.

True, I leave it up the TS team to decide if they want to go along this road. Then I guess, it also need to have a command line option.

tinganho commented 9 years ago

Side note: if possible perhaps having the shorter syntax 'transpiler' {...} by dropping the syntax keyword. So for JSX:

'jsx' {  <div>Hello {{this.props.name}}</div> }
icetraxx commented 9 years ago

I think a solution that didn't add any extra syntax to the language, except for the DSL that is embedded, would be much cleaner (something like Babel's transformers).

tinganho commented 9 years ago

@icetraxx isn't 'jsx' {...} small enough? You would need at least a symbol for the start {, and one for the end } and if you want to support more than one type of embed you need to specify the type 'jsx' of the embed?

For the Babel transforms: I'm not sure if this is correct but it seems like Babel are visiting/looping through all the nodes one per custom transform and is only editable within their range of supported AST nodes? http://babeljs.io/docs/usage/plugins/

This solutions doesn't create an additional loop and doesn't rely on TS team to support any new AST nodes.

NoelAbrahams commented 9 years ago

Definitely plus one this. :+1:

But I somehow don't think it is the right approach to accept JSX directly into TS. There seem to be a demand of embedding HTML and even CSS in JS.

I agree. TypeScript should look at solving this problem in the abstract rather than implement a solution that favours one specific framework.

There are lots of frameworks out there that would benefit from this approach, e.g.

// myModel.ts

var viewModel = {
    firstName: ko.observable('Joe'),
    lastName: ko.observable('Bloggs')
};

ko.applyBindings(viewModel);
// myView.ts

'knockout' {
    <p>First name: <input data-bind="value: firstName" /></p>
    <p>Last name: <input data-bind="value: lastName" /></p>
}

I would want to see the embedded syntax type-checked and produce HTML as output.

tinganho commented 9 years ago

@jbondc I believe any kind of DSL that relies on a general purpose language e.g. HTML, CSS, SQL etc are better written as a syntax embed then on a separate file or a multi-line string.

Here is an example with Facebook's GraphQL(don't know the whole spec so bare with me):

getUser() {
  return 'gql' {
     @user(1000) {
        name,
        age
     }
  }
}

Just looking at the their React conference. GraphQL will be based on tagged template strings. It could be much better be based on a syntax embed instead.

tinganho commented 9 years ago

Any feedback from TS team? @ahejlsberg @CyrusNajmabadi @mhegazy etc.

sophiajt commented 9 years ago

In thinking about this, we already have a precedent in TS for how domain specific languages (DSLs) like the above are handled: we use the file extension. We support .ts for normal source and .d.ts for declare files.

I get there is some ergonomic disadvantage to not being able to embed syntax blocks, but it also comes with some complications. Instead, one thought may be to support addition ..ts. Like .jsx.ts for example. That way it's on a file-by-file with natural delineation.

tinganho commented 9 years ago

One problem with having the file extension is that only one type of DSL can be accepted per file. Just looking at React they are heading on adopting 2 types of DSL:s JSX and GraphQL(using tagged template strings). When you write your React component both the DSL:s needs to be in the same file. And I can almost smell that React is heading on adopting another DSL for defining styles.

With this solution you don't need to write a compiler from scratch if you want to invent your own language extension. Just write your extension and not your language.

Source: ReactConf(GraphQL): https://youtu.be/9sc8Pyc51uU?t=20m33s

icetraxx commented 9 years ago

@tinganho You could potentially compose multiple DSLs with file extensions, i.e. .jsx.gql.ts.

tinganho commented 9 years ago

@icetraxx true, but then you have the problem that some let say a React components have jsx.ts and jsx.gql.ts in the same folder. Though you could put every file with jsx.gql.ts. (I personally don't like the idea of having multiple extension in the file name, I can cope with two d.ts but not more than that).

Having file extensions also implies no embedded solution. So you still have the problem with: how do you implement a language extension without writing a compiler from scratch? Only very big companies like Facebook can afford to do that.

How do you deal with multiple language extension embedded in each other? One language extension have a defined behavior with it's parent language, not another language extensions. With embeds you won't have the problem because you can only have one per embed and they are enclosed in {...}.

tinganho commented 9 years ago

@jbondc that is embedding gql and ts in XML/HTML and then transpiled to ts. I don't know if that is what people wants. It is not so "UX friendly".

NoelAbrahams commented 9 years ago

JSON is another possible candidate that will benefit from this feature:

interface User {
   firstName: string;
   lastName: string;
   age: number;
}

'json' {
    User: {
        "firstName": "Joe",
        "lastName" : "Bloggs",
        "age": "54" // Error: number expected
    }
}

Expected output

{
   "firstName": "Joe",
   "lastName" : "Bloggs",
   "age": "54"
}

Related issue #2064

tinganho commented 9 years ago

@NoelAbrahams that would be definitely helpful!

Arnavion commented 9 years ago

An alternative that does not require a new syntax with its own ways of embedding expressions, escapes, etc:

jsx`<div>Hello ${ this.props.name }</div>`;

gql`
@user(1000) {
    name,
    age
}
`

@compile
function jsx(strings: string[], expressions: string[]): string {
    ...
}

Syntax looks identical to template strings. The parser and the user do not need to learn a new syntax, such as syntax for escaping, embedding TS expressions, balancing braces/backticks, etc. The only difference from actual tagged template strings is that the expressions parameter is an array of strings instead of the values of the expressions. (However this dissonance might be sufficient reason to stick with a syntax that looks different, like the OP.)

The presence of the @compile decorator implies that this is a compile-time transformer and will be invoked by the parser upon finding it in the AST. The signature of the transformer must be (string[], string[]): string However this will probably need to be given inside its own file with a compiler flag / tsconfig.json property as mentioned above to handle issues of declaration ordering (it needs to be defined before it is encountered in the code, and it needs to be defined only once), so the decorator isn't required.

Note that both the original suggestion as well as with this one don't give syntax highlighting, completion and the other LS goodies for the DSL, even for syntax that's obviously JS/TS like in @NoelAbrahams 's comment. They only give this for the embedded TS expressions, if any ({{ ... }} in the OP, ${ ... } here).

Also @NoelAbrahams, note that the original proposal (and mine) do not handle type-dependent transformations (I asked this in my first comment). The transformer only has access to the string, not the types of the expressions it encounters or have been encountered before, so it does not have access to the interface definition to be able to transform "54" to 54, or complain about "54" vs 54. (This can be handled by having the transformer take a ts.TypeChecker parameter, but then it has to run in a separate step after the parser and checker have already run once. This might be a cleaner approach in general at the cost of increasing compile time.)

tinganho commented 9 years ago

There is a problem with having tagged templates. It becomes apparent when you want to nest things. http://facebook.github.io/jsx/#why-not-template-literals. You would need to type jsx'' inside jsx''

I'm not sure if decorators should be used as meta data for compile time, they are right now just there for providing runtime meta data. And the jsx function could be huge for supporting jsx and other DSL:s.

Syntax highlighting can be supported by the IDE developers. I think code completion and other related tooling can be doable by just providing another hook for it.

Arnavion commented 9 years ago

There is a problem with having tagged templates. It becomes apparent when you want to nest things. http://facebook.github.io/jsx/#why-not-template-literals. You would need to type jsx'' inside jsx''

The concerns on that page are for JSX via template strings at runtime, not at compile time. For example, it says <${Box}> is noise compared to <Box>, but this is not a problem for compile-time template strings (as in my example) - you can continue to write <Box>.

And no, you would not need to nest jsx'' inside jsx''.

I'm not sure if decorators should be used as meta data for compile time

As I said, the decorator is unnecessary since the transformer needs to be explicitly known to the compiler anyway.

I think code completion and other related tooling can be doable by just providing another hook for it.

It is doable, and as I said, it will require running the transformer after running the type checker, which itself runs after the parser. Thus this will require a separate transformer step instead of being in line with the parser step.

AlicanC commented 9 years ago

+1

I thought some people might consider this as something new (or even weird), but it's actually not.

Remember the asm block?

__asm {
   mov al, 2
   mov dx, 0xD007
   out dx, al
}

Yeah.


As stated before, what we are going to put in these blocks will probably care about more stuff like types. So, why not pass data between target transpiler?

Let's say that changes in TS2 broke some of our code and we want to use that code until we find the time to port it:

/// <transpiler name="ts1" path="path/to/ts1" />
var numA: number;

var add = function add(a: number, b: number): number {
  return a + b;
};

__ts1 {
  import foo = require('../foo.ts');

  numA = foo.getNumA();
  var numB = foo.getNumB();
}

var total = add(numA, numB);

TS can send information about the current scope (like the vars and funcs in it and their types) to the transpiler and if the transpiler cares, it can use that information and send the new information back after it's done.

For transpilers which do not support this, we could use declare:

...

__purejs {
  var numB = 13;
}
declare var numB: number;

var total = add(numA, numB); // No errors because we let TS know that there would be a numB with type 'number'

And to compile my thoughs on how the syntax should be:

// These can be both here and in `tsconfig.json`:
/// <transpiler name="jsx" path="path/to/jsx" />
/// <transpiler name="babel" path="path/to/babel" />

// Using the same transpiler with different configuration:
/// <transpiler name="babel2" path="path/to/babel" configuration="../babel2.json" />

// Transpile JSX in a block and provide missing type information:
__jsx {
  var myDiv =
    <div>
      <div>
        <span>
        </span>
      </div>
    </div>;
} // JSX is told to read until "}"
declare var myDiv: ReactElement;

// Or do the exact same thing with the "__transpiler var" sugar:
__jsx var myDiv: ReactElement =
  <div>
    <div>
      <span>
      </span>
    </div>
  </div>; // JSX is told to read until ";"

// Or:
var myDiv: ReactElement;
__jsx {
  myDiv =
    <div>
      <div>
        <span>
        </span>
      </div>
    </div>;
} // JSX is told to read until "}"

// Or with sugar:
var myDiv: ReactElement;
__jsx myDiv =
  <div>
    <div>
      <span>
      </span>
    </div>
  </div>; // JSX is told to read until ";"

// Import a file with "__transpiler import" after transpiling it with Babel:
__babel import {Foo} from 'foo.js'; // Babel is told to read file "foo.js"

// Note that the previous line doesn't do this:
__babel {
  import {Foo} from 'foo.js';
} // Babel is told to read until "}"
// Babel does not get that "import" line, it gets the content of the file.
RyanCavanaugh commented 9 years ago

Arbitrary embedded DSLs are completely out of scope for TypeScript.

As for JSX, the proposal here doesn't even solve the problem of using JSX and TypeScript. Nobody is going to consider code like {{ "hello world \}\} \{\{" }} as "support" -- it's virtually unreadable, doesn't port over cleanly, and wouldn't have tooling support.

I get that we don't want to "pick winners" in terms of frameworks, but JSX really is a special case. It has wide support and strong adoption, solves a readily-identified problem in the JavaScript world, occupies a small and reasonable part of the grammar, and is one of our biggest adoption blockers ("I want to use TS, but we use JSX").

tinganho commented 9 years ago

For starter, I'm not sure if judging a super rare case like the escaping example is fair. But anyway:

As for JSX, the proposal here doesn't even solve the problem of using JSX and TypeScript. Nobody is going to consider code like {{ "hello world }} {{" }} as "support"

I just discovered recently that it's actually possible to have a clean support for JSX. { "hello world }} {{"}. If the transpiler balance on the quote". We could even drop the double braces {{ by letting the transpiler matching the TS embeds on <...{ and }...>.

wouldn't have tooling support.

This proposal was having tooling support?

JSX really is a special case

The thing is only a few companies and people have the resources to create something like JSX. I just think that this proposal will create an innovation platform for those who want to create the next web framework and many of them will be inspired by JSX. Though writing a compiler from scratch plus the extended syntax might prove to be too daunting for people. With the proposed embed, people don't need to write a compiler from scratch, just the extension.

I could create a POC of this but only if it got a chance(even the slightest) to be landed.

NoelAbrahams commented 9 years ago

I get that we don't want to "pick winners" in terms of frameworks, but JSX really is a special case. It has wide support and strong adoption, solves a readily-identified problem in the JavaScript world, occupies a small and reasonable part of the grammar, and [flow] is one of our biggest adoption blockers [competitors in the compiler space].

:smile:

I am with you in wanting to put flow out of business (it is costly to hire, train, and maintain staff requiring different technical skills). But let's not get carried away and claim that there is something special about JSX/React: the competing frameworks have even wider support and stronger adoption.

basarat commented 9 years ago

Having native React support will immediately move it to the top of my UI tech stack preference :smile:

RyanCavanaugh commented 9 years ago

the competing frameworks have even wider support and stronger adoption.

Can you be more specific? Which of these extend JS syntax?

NoelAbrahams commented 9 years ago

Knockout for example defines its logic in the data-bind attribute. This is currently embedded in the HTML, but the proposal above would have brought it into the JavaScript domain.

tinganho commented 9 years ago

You also forgot to mention all template languages Mustasch, Handlebars even maybe Angular? could have an interest on this.

JSX is just a new kid on the block fed with a silver spoon.

basarat commented 9 years ago

I am okay with the TS team adding native JSX support specific to React :rose:. Still curious about non react implementations though e.g. https://github.com/vjeux/jsxdom this is a fundamental motivation behind the jsx spec : https://facebook.github.io/jsx/#transpilers < is this out of scope as well or needs a different proposal?

tinganho commented 9 years ago

Also notice that Handlebars has 10x more downloads than React on NPM:

Charts for Handlebars: http://npm-stat.com/charts.html?package=handlebars&author=&from=2013-05-25&to=2015-05-25 Charts for React: http://npm-stat.com/charts.html?package=react&author=&from=&to=

kitsonk commented 9 years ago

Also notice that Handlebars has 10x more downloads than React on NPM:

Not that I am concerned about this specific conversations, but making architectural decisions based on the number of downloads somethings has on NPM is quite ill advised logic.

tinganho commented 9 years ago

I definitely agree with you, but wasn't it the numbers that made the TS team include JSX in the first place? And just if I can speculate(which is bad) added the postfix type assertion operator as just to support JSX?

basarat commented 9 years ago

@tinganho worth your review : https://github.com/TypeStrong/atom-typescript/issues/362

tinganho commented 9 years ago

@basarat Atom-typescript is a great plugin. But I think this feature should be implemented in TS for various reason, one of them being that not everyone is using the same IDE. Another reason is that this problem belongs to the TS compiler, so it would be best if they could fix it.

@RyanCavanaugh how about instead of an embed having pluggable syntax support? Acorn have already re-written its parser to support syntax plugins. The current TS JSX implementation could then be rewritten to use it? Though it would require some hooks to be implemented in the scanner, parser emitter checker etc.

It looks very nice in their example code: https://github.com/marijnh/acorn/blob/jsx/plugin/jsx.js#L608-L662

Relevant info: https://github.com/marijnh/acorn#plugins

Relevant discussion: https://github.com/marijnh/acorn/issues/168

basarat commented 9 years ago

I'm thinking:

syntax:jsx{ 
      /* code here */ 
}syntax:jsx 

Making the need for any escaping very unlikely. This is very much like <span> </span> => syntax{ }syntax and easy to parse and partition out.

NoelAbrahams commented 9 years ago

@basarat, I like what you are trying to do.

basarat commented 9 years ago

Here is another syntax that turned up in my research, Rust uses syntaxName! { } e.g. Maud which is a syntax extension for html : html! { } : http://lfairy.gitbooks.io/maud/content/basic_syntax.html

I still prefer my stronger delimiters

tinganho commented 9 years ago

@basarat turns out that Rust already has an syntax extension API https://doc.rust-lang.org/book/compiler-plugins.html.

basarat commented 9 years ago

turns out that Rust already has an syntax extension API

@tinganho Yup. That's what I meant with "Rust uses syntaxName! {" Thanks for the link though, I should have added that :+1: