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

[FEATURE] Adds inline if and unless keywords #1240

Merged
merged 1 commit into from
Dec 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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