Skip to content

Commit

Permalink
feat(jenny): Added the <<set>> command (#2155)
Browse files Browse the repository at this point in the history
The <<set>> command is used to modify existing variables.
  • Loading branch information
st-pasha authored Nov 9, 2022
1 parent 8d592f1 commit 2b306d9
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 54 deletions.
84 changes: 33 additions & 51 deletions packages/flame_jenny/jenny/lib/src/parse/parse.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import 'package:jenny/src/structure/expressions/literal.dart';
import 'package:jenny/src/structure/expressions/logical.dart';
import 'package:jenny/src/structure/expressions/relational.dart';
import 'package:jenny/src/structure/expressions/string.dart';
import 'package:jenny/src/structure/expressions/variables.dart';
import 'package:jenny/src/structure/node.dart';
import 'package:jenny/src/structure/option.dart';
import 'package:jenny/src/structure/statement.dart';
Expand Down Expand Up @@ -435,50 +434,40 @@ class _Parser {
Command parseCommandSet() {
take(Token.startCommand);
take(Token.commandSet);
take(Token.startExpression);
final variableToken = peekToken();
if (variableToken.isVariable) {
position += 1;
} else {
if (!variableToken.isVariable) {
syntaxError('variable expected');
}
final variableName = variableToken.content;
final variableExists = project.variables.hasVariable(variableName);
if (!project.variables.hasVariable(variableName)) {
nameError('variable $variableName has not been declared');
}
position += 1;
final assignmentToken = peekToken();
if (assignmentTokens.contains(assignmentToken)) {
position += 1;
} else {
if (!assignmentTokens.containsKey(assignmentToken)) {
syntaxError('an assignment operator is expected');
}
position += 1;
final expressionStartPosition = position;
final expression = parseExpression();
if (variableExists) {
final variableType = project.variables.getVariableType(variableName);
if (variableType != expression.type) {
typeError(
'variable $variableName of type ${variableType.name} cannot be '
'assigned a value of type ${expression.type.name}',
);
}
} else {
final variableType = expression.type;
final dynamic initialValue = variableType is String
? ''
: variableType is num
? 0
: variableType is bool
? false
: null;
project.variables.setVariable(variableName, initialValue);
assert(project.variables.getVariableType(variableName) == variableType);
}
if (assignmentToken != Token.operatorAssign) {
if (!variableExists) {
nameError('variable $variableName was not defined');
}
throw UnimplementedError();
final variableType = project.variables.getVariableType(variableName);
if (variableType != expression.type) {
position = expressionStartPosition;
typeError(
'variable $variableName of type ${variableType.name} cannot be '
'assigned a value of type ${expression.type.name}',
);
}
final assignmentExpression = assignmentTokens[assignmentToken]!(
project.variables.getVariableAsExpression(variableName),
expression,
expressionStartPosition,
);
take(Token.endExpression);
take(Token.endCommand);
take(Token.newline);
return SetCommand(variableName, expression);
return SetCommand(variableName, assignmentExpression);
}

Command parseCommandDeclare() {
Expand Down Expand Up @@ -534,14 +523,15 @@ class _Parser {
throw UnimplementedError('user-defined commands are not supported yet');
}

static const List<Token> assignmentTokens = [
Token.operatorAssign,
Token.operatorDivideAssign,
Token.operatorMinusAssign,
Token.operatorModuloAssign,
Token.operatorMultiplyAssign,
Token.operatorPlusAssign,
];
late Map<Token, Expression Function(Expression, Expression, int)>
assignmentTokens = {
Token.operatorAssign: (lhs, rhs, pos) => rhs,
Token.operatorDivideAssign: _divide,
Token.operatorMinusAssign: _subtract,
Token.operatorModuloAssign: _modulo,
Token.operatorMultiplyAssign: _multiply,
Token.operatorPlusAssign: _add,
};

//#endregion

Expand Down Expand Up @@ -627,15 +617,7 @@ class _Parser {
} else if (token.isVariable) {
final name = token.content;
if (project.variables.hasVariable(name)) {
final dynamic variable = project.variables.getVariable(name);
if (variable is num) {
return NumericVariable(name, project.variables);
} else if (variable is String) {
return StringVariable(name, project.variables);
} else {
assert(variable is bool);
return BooleanVariable(name, project.variables);
}
return project.variables.getVariableAsExpression(name);
} else {
position -= 1;
nameError('variable $name is not defined');
Expand Down
14 changes: 14 additions & 0 deletions packages/flame_jenny/jenny/lib/src/variable_storage.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'package:jenny/src/errors.dart';
import 'package:jenny/src/structure/expressions/expression.dart';
import 'package:jenny/src/structure/expressions/expression_type.dart';
import 'package:jenny/src/structure/expressions/variables.dart';
import 'package:meta/meta.dart';

class VariableStorage {
Expand All @@ -17,6 +19,18 @@ class VariableStorage {
bool hasVariable(String name) => variables.containsKey(name);
dynamic getVariable(String name) => variables[name]!;

Expression getVariableAsExpression(String name) {
final dynamic value = variables[name];
if (value is String) {
return StringVariable(name, this);
}
if (value is num) {
return NumericVariable(name, this);
}
assert(value is bool);
return BooleanVariable(name, this);
}

ExpressionType getVariableType(String name) {
final dynamic value = variables[name];
return value is String
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
import 'package:jenny/jenny.dart';
import 'package:jenny/src/parse/token.dart';
import 'package:jenny/src/parse/tokenize.dart';
import 'package:test/test.dart';

import '../../parse/utils.dart';
import '../../test_scenario.dart';

void main() {
group('SetCommand', () {
test('tokenize <<set>>', () {
expect(
tokenize(r'<<set $x = 3>>'),
const [
Token.startCommand,
Token.commandSet,
Token.startExpression,
Token.variable(r'$x'),
Token.operatorAssign,
Token.number('3'),
Token.endExpression,
Token.endCommand,
],
);
});

testScenario(
testName: 'AnalysisTest.plan',
input: r'''
Expand All @@ -20,12 +40,13 @@ void main() {
===
''',
testPlan: 'line: 1 1',
skip: true,
);

testScenario(
testName: 'Basic.plan',
input: r'''
<<declare $foo as Number>>
---
title: Start
---
whoa what here's some text
Expand All @@ -40,7 +61,7 @@ void main() {
<<else>>
oh noooo it didn't work :(
<<endif>>
<<if $foo is 54>>
haha nice now 'set' works even when deeply nested
<<else>>
Expand All @@ -54,7 +75,115 @@ void main() {
line: NESTED IF BLOCK WHAAAT
line: haha nice now 'set' works even when deeply nested
''',
skip: true,
);

testScenario(
testName: 'modifying assignments',
input: r'''
<<declare $x = 0>>
<<declare $s = 'Hello'>>
---
title: Start
---
<<set $x += 12>>
$x = {$x}
<<set $x *= 4>>
$x = {$x}
<<set $x /= 6>>
$x = {$x}
<<set $x %= 5>>
$x = {$x}
<<set $x -= 2>>
$x = {$x}
<<set $s += " world">>
$s = '{$s}'
===
''',
testPlan: r'''
line: $x = 12
line: $x = 48
line: $x = 8.0
line: $x = 3.0
line: $x = 1.0
line: $s = 'Hello world'
''',
);

group('errors', () {
test('no variable', () {
expect(
() => YarnProject()
..parse(
'title: W\n'
'---\n'
'<<set>>\n'
'===\n',
),
hasSyntaxError(
'SyntaxError: variable expected\n'
'> at line 3 column 6:\n'
'> <<set>>\n'
'> ^\n',
),
);
});

test('undeclared variable', () {
expect(
() => YarnProject()
..parse(
'title:E\n'
'---\n'
'<<set \$foo = 123>>\n'
'===\n',
),
hasNameError(
'NameError: variable \$foo has not been declared\n'
'> at line 3 column 7:\n'
'> <<set \$foo = 123>>\n'
'> ^\n',
),
);
});

test('no assignment', () {
expect(
() => YarnProject()
..parse(
'<<declare \$x as Number>>\n'
'title: X\n'
'---\n'
'<<set \$x as String>>\n'
'===\n',
),
hasSyntaxError(
'SyntaxError: an assignment operator is expected\n'
'> at line 4 column 10:\n'
'> <<set \$x as String>>\n'
'> ^\n',
),
);
});

test('wrong type in assignment', () {
expect(
() => YarnProject()
..parse(
'<<declare \$x as String>>\n'
'title: A\n'
'---\n'
'<<set \$x = 12>>\n'
'===\n',
),
hasTypeError(
r'TypeError: variable $x of type string cannot be assigned a '
'value of type numeric\n'
'> at line 4 column 12:\n'
'> <<set \$x = 12>>\n'
'> ^\n',
),
);
});
});
});
}

0 comments on commit 2b306d9

Please sign in to comment.