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

feat: Added the <<set>> command #2155

Merged
merged 4 commits into from
Nov 9, 2022
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
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',
),
);
});
});
});
}