Skip to content

Commit

Permalink
Merge pull request #1240 from glimmerjs/make-inline-if-and-unless-key…
Browse files Browse the repository at this point in the history
…words

[FEATURE] Adds inline if and unless keywords
  • Loading branch information
rwjblue authored Dec 17, 2020
2 parents c355419 + f26034b commit 1693af6
Show file tree
Hide file tree
Showing 17 changed files with 591 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { VISIT_EXPRS } from '../visitors/expressions';
import { keywords } from './impl';
import { assertValidCurryUsage } from './utils/curry';
import { assertValidHasBlockUsage } from './utils/has-block';
import { assertValidIfUnlessInlineUsage } from './utils/if-unless';

export const APPEND_KEYWORDS = keywords('Append')
.kw('yield', {
Expand Down Expand Up @@ -194,6 +195,72 @@ export const APPEND_KEYWORDS = keywords('Append')
return Ok(new mir.AppendTextNode({ loc: node.loc, text }));
},
})
.kw('if', {
assert: assertValidIfUnlessInlineUsage('{{if}}', false),

translate(
{ node, state }: { node: ASTv2.AppendContent; state: NormalizationState },
{
condition,
truthy,
falsy,
}: {
condition: ASTv2.ExpressionNode;
truthy: ASTv2.ExpressionNode;
falsy: ASTv2.ExpressionNode | null;
}
): Result<mir.AppendTextNode> {
let conditionResult = VISIT_EXPRS.visit(condition, state);
let truthyResult = VISIT_EXPRS.visit(truthy, state);
let falsyResult = falsy ? VISIT_EXPRS.visit(falsy, state) : Ok(null);

return Result.all(conditionResult, truthyResult, falsyResult).mapOk(
([condition, truthy, falsy]) => {
let text = new mir.IfInline({
loc: node.loc,
condition,
truthy,
falsy,
});

return new mir.AppendTextNode({ loc: node.loc, text });
}
);
},
})
.kw('unless', {
assert: assertValidIfUnlessInlineUsage('{{unless}}', true),

translate(
{ node, state }: { node: ASTv2.AppendContent; state: NormalizationState },
{
condition,
truthy,
falsy,
}: {
condition: ASTv2.ExpressionNode;
truthy: ASTv2.ExpressionNode;
falsy: ASTv2.ExpressionNode | null;
}
): Result<mir.AppendTextNode> {
let conditionResult = VISIT_EXPRS.visit(condition, state);
let truthyResult = VISIT_EXPRS.visit(truthy, state);
let falsyResult = falsy ? VISIT_EXPRS.visit(falsy, state) : Ok(null);

return Result.all(conditionResult, truthyResult, falsyResult).mapOk(
([condition, truthy, falsy]) => {
let text = new mir.IfInline({
loc: node.loc,
condition: new mir.Not({ value: condition, loc: node.loc }),
truthy,
falsy,
});

return new mir.AppendTextNode({ loc: node.loc, text });
}
);
},
})
.kw('component', {
assert: assertValidCurryUsage('{{component}}', 'component', true),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,17 +101,17 @@ export const BLOCK_KEYWORDS = keywords('Block')
generateSyntaxError(
`{{#if}} cannot receive named parameters, received ${args.named.entries
.map((e) => e.name.chars)
.join(', ')}.`,
args.named.loc
.join(', ')}`,
node.loc
)
);
}

if (args.positional.size > 1) {
return Err(
generateSyntaxError(
`{{#if}} can only receive one positional parameter, the conditional value. Received ${args.positional.size} parameters.`,
args.positional.loc
`{{#if}} can only receive one positional parameter in block form, the conditional value. Received ${args.positional.size} parameters`,
node.loc
)
);
}
Expand All @@ -121,8 +121,8 @@ export const BLOCK_KEYWORDS = keywords('Block')
if (condition === null) {
return Err(
generateSyntaxError(
`{{#if}} requires a condition as its first positional parameter, did not receive any parameters.`,
args.loc
`{{#if}} requires a condition as its first positional parameter, did not receive any parameters`,
node.loc
)
);
}
Expand Down Expand Up @@ -165,17 +165,17 @@ export const BLOCK_KEYWORDS = keywords('Block')
generateSyntaxError(
`{{#unless}} cannot receive named parameters, received ${args.named.entries
.map((e) => e.name.chars)
.join(', ')}.`,
args.named.loc
.join(', ')}`,
node.loc
)
);
}

if (args.positional.size > 1) {
return Err(
generateSyntaxError(
`{{#unless}} can only receive one positional parameter, the conditional value. Received ${args.positional.size} parameters.`,
args.positional.loc
`{{#unless}} can only receive one positional parameter in block form, the conditional value. Received ${args.positional.size} parameters`,
node.loc
)
);
}
Expand All @@ -185,8 +185,8 @@ export const BLOCK_KEYWORDS = keywords('Block')
if (condition === null) {
return Err(
generateSyntaxError(
`{{#unless}} requires a condition as its first positional parameter, did not receive any parameters.`,
args.loc
`{{#unless}} requires a condition as its first positional parameter, did not receive any parameters`,
node.loc
)
);
}
Expand All @@ -197,7 +197,7 @@ export const BLOCK_KEYWORDS = keywords('Block')
translate(
{ node, state }: { node: ASTv2.InvokeBlock; state: NormalizationState },
{ condition }: { condition: ASTv2.ExpressionNode }
): Result<mir.Unless> {
): Result<mir.If> {
let block = node.blocks.get('default');
let inverse = node.blocks.get('else');

Expand All @@ -207,9 +207,9 @@ export const BLOCK_KEYWORDS = keywords('Block')

return Result.all(conditionResult, blockResult, inverseResult).mapOk(
([condition, block, inverse]) =>
new mir.Unless({
new mir.If({
loc: node.loc,
condition,
condition: new mir.Not({ value: condition, loc: node.loc }),
block,
inverse,
})
Expand All @@ -231,7 +231,7 @@ export const BLOCK_KEYWORDS = keywords('Block')
`{{#each}} can only receive the 'key' named parameter, received ${args.named.entries
.filter((e) => e.name.chars !== 'key')
.map((e) => e.name.chars)
.join(', ')}.`,
.join(', ')}`,
args.named.loc
)
);
Expand All @@ -240,7 +240,7 @@ export const BLOCK_KEYWORDS = keywords('Block')
if (args.positional.size > 1) {
return Err(
generateSyntaxError(
`{{#each}} can only receive one positional parameter, the collection being iterated. Received ${args.positional.size} parameters.`,
`{{#each}} can only receive one positional parameter, the collection being iterated. Received ${args.positional.size} parameters`,
args.positional.loc
)
);
Expand All @@ -252,7 +252,7 @@ export const BLOCK_KEYWORDS = keywords('Block')
if (value === null) {
return Err(
generateSyntaxError(
`{{#each}} requires an iterable value to be passed as its first positional parameter, did not receive any parameters.`,
`{{#each}} requires an iterable value to be passed as its first positional parameter, did not receive any parameters`,
args.loc
)
);
Expand Down Expand Up @@ -299,7 +299,7 @@ export const BLOCK_KEYWORDS = keywords('Block')
generateSyntaxError(
`{{#with}} cannot receive named parameters, received ${args.named.entries
.map((e) => e.name.chars)
.join(', ')}.`,
.join(', ')}`,
args.named.loc
)
);
Expand All @@ -308,7 +308,7 @@ export const BLOCK_KEYWORDS = keywords('Block')
if (args.positional.size > 1) {
return Err(
generateSyntaxError(
`{{#with}} can only receive one positional parameter. Received ${args.positional.size} parameters.`,
`{{#with}} can only receive one positional parameter. Received ${args.positional.size} parameters`,
args.positional.loc
)
);
Expand All @@ -319,7 +319,7 @@ export const BLOCK_KEYWORDS = keywords('Block')
if (value === null) {
return Err(
generateSyntaxError(
`{{#with}} requires a value as its first positional parameter, did not receive any parameters.`,
`{{#with}} requires a value as its first positional parameter, did not receive any parameters`,
args.loc
)
);
Expand Down Expand Up @@ -363,7 +363,7 @@ export const BLOCK_KEYWORDS = keywords('Block')
generateSyntaxError(
`{{#let}} cannot receive named parameters, received ${args.named.entries
.map((e) => e.name.chars)
.join(', ')}.`,
.join(', ')}`,
args.named.loc
)
);
Expand All @@ -372,7 +372,7 @@ export const BLOCK_KEYWORDS = keywords('Block')
if (args.positional.size === 0) {
return Err(
generateSyntaxError(
`{{#let}} requires at least one value as its first positional parameter, did not receive any parameters.`,
`{{#let}} requires at least one value as its first positional parameter, did not receive any parameters`,
args.positional.loc
)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { VISIT_EXPRS } from '../visitors/expressions';
import { keywords } from './impl';
import { assertValidCurryUsage } from './utils/curry';
import { assertValidHasBlockUsage } from './utils/has-block';
import { assertValidIfUnlessInlineUsage } from './utils/if-unless';

export const CALL_KEYWORDS = keywords('Call')
.kw('has-block', {
Expand Down Expand Up @@ -36,6 +37,68 @@ export const CALL_KEYWORDS = keywords('Call')
);
},
})
.kw('if', {
assert: assertValidIfUnlessInlineUsage('(if)', false),

translate(
{ node, state }: { node: ASTv2.CallExpression; state: NormalizationState },
{
condition,
truthy,
falsy,
}: {
condition: ASTv2.ExpressionNode;
truthy: ASTv2.ExpressionNode;
falsy: ASTv2.ExpressionNode | null;
}
): Result<mir.IfInline> {
let conditionResult = VISIT_EXPRS.visit(condition, state);
let truthyResult = VISIT_EXPRS.visit(truthy, state);
let falsyResult = falsy ? VISIT_EXPRS.visit(falsy, state) : Ok(null);

return Result.all(conditionResult, truthyResult, falsyResult).mapOk(
([condition, truthy, falsy]) =>
new mir.IfInline({
loc: node.loc,
condition,
truthy,
falsy,
})
);
},
})
.kw('unless', {
assert: assertValidIfUnlessInlineUsage('(unless)', true),

translate(
{ node, state }: { node: ASTv2.CallExpression; state: NormalizationState },
{
condition,
falsy,
truthy,
}: {
condition: ASTv2.ExpressionNode;
truthy: ASTv2.ExpressionNode;
falsy: ASTv2.ExpressionNode | null;
}
): Result<mir.IfInline> {
let conditionResult = VISIT_EXPRS.visit(condition, state);
let truthyResult = VISIT_EXPRS.visit(truthy, state);
let falsyResult = falsy ? VISIT_EXPRS.visit(falsy, state) : Ok(null);

return Result.all(conditionResult, truthyResult, falsyResult).mapOk(
([condition, truthy, falsy]) =>
new mir.IfInline({
loc: node.loc,

// We reverse the condition by inserting a Not
condition: new mir.Not({ value: condition, loc: node.loc }),
truthy,
falsy,
})
);
},
})
.kw('component', {
assert: assertValidCurryUsage('(component)', 'component', true),

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { ASTv2, generateSyntaxError } from '@glimmer/syntax';

import { Err, Ok, Result } from '../../../../shared/result';

export function assertValidIfUnlessInlineUsage(type: string, inverted: boolean) {
return (
originalNode: ASTv2.AppendContent | ASTv2.ExpressionNode
): Result<{
condition: ASTv2.ExpressionNode;
truthy: ASTv2.ExpressionNode;
falsy: ASTv2.ExpressionNode | null;
}> => {
let node = originalNode.type === 'AppendContent' ? originalNode.value : originalNode;
let named = node.type === 'Call' ? node.args.named : null;
let positional = node.type === 'Call' ? node.args.positional : null;

if (named && !named.isEmpty()) {
return Err(
generateSyntaxError(
`${type} cannot receive named parameters, received ${named.entries
.map((e) => e.name.chars)
.join(', ')}`,
originalNode.loc
)
);
}

let condition = positional?.nth(0);

if (!positional || !condition) {
return Err(
generateSyntaxError(
`When used inline, ${type} requires at least two parameters 1. the condition that determines the state of the ${type}, and 2. the value to return if the condition is ${
inverted ? 'false' : 'true'
}. Did not receive any parameters`,
originalNode.loc
)
);
}

let truthy = positional.nth(1);
let falsy = positional.nth(2);

if (truthy === null) {
return Err(
generateSyntaxError(
`When used inline, ${type} requires at least two parameters 1. the condition that determines the state of the ${type}, and 2. the value to return if the condition is ${
inverted ? 'false' : 'true'
}. Received only one parameter, the condition`,
originalNode.loc
)
);
}

if (positional.size > 3) {
return Err(
generateSyntaxError(
`When used inline, ${type} can receive a maximum of three positional parameters 1. the condition that determines the state of the ${type}, 2. the value to return if the condition is ${
inverted ? 'false' : 'true'
}, and 3. the value to return if the condition is ${
inverted ? 'true' : 'false'
}. Received ${positional?.size ?? 0} parameters`,
originalNode.loc
)
);
}

return Ok({ condition, truthy, falsy });
};
}
Loading

0 comments on commit 1693af6

Please sign in to comment.