Skip to content

Instantly share code, notes, and snippets.

@sma
Created September 18, 2024 17:06
Show Gist options
  • Save sma/8a59ca3db6eaa16b8a41735b2da4768d to your computer and use it in GitHub Desktop.
Save sma/8a59ca3db6eaa16b8a41735b2da4768d to your computer and use it in GitHub Desktop.
A Basic interpreter thrown together with ChatGPT
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(const BasicInterpreterApp());
class BasicInterpreterApp extends StatelessWidget {
const BasicInterpreterApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: BasicInterpreterWidget(),
);
}
}
class BasicInterpreterWidget extends StatefulWidget {
const BasicInterpreterWidget({super.key});
@override
State<BasicInterpreterWidget> createState() => _BasicInterpreterWidgetState();
}
class _BasicInterpreterWidgetState extends State<BasicInterpreterWidget> {
final TextEditingController _controller = TextEditingController();
final ScrollController _scrollController = ScrollController();
String _output = '';
String _inputBuffer = '';
bool _isRunning = false;
bool _isWaitingInput = false;
Completer<void>? _inputCompleter;
final Map<int, String> _program = {};
final Map<String, num> _variables = {};
int _currentLineIndex = 0;
List<int> _sortedLineNumbers = [];
@override
void initState() {
super.initState();
_output = 'Flutter Basic. 38911 bytes free.\nType help for help.\n';
}
@override
void dispose() {
_controller.dispose();
_scrollController.dispose();
super.dispose();
}
void _handleInput() async {
String inputLine = _controller.text.trim();
_controller.clear();
setState(() {
_output += '\n> $inputLine';
});
if (_isWaitingInput) {
_inputBuffer = inputLine;
_inputCompleter?.complete();
_isWaitingInput = false;
return;
}
if (_isRunning) {
setState(() {
_output += '\nProgram is running. Please wait.';
});
return;
}
await _processInput(inputLine);
_scrollToBottom();
}
Future<void> _processInput(String inputLine) async {
String lowerInput = inputLine.toLowerCase();
if (lowerInput == 'new') {
_program.clear();
setState(() {
_output = 'Program cleared.';
});
} else if (lowerInput == 'run') {
_isRunning = true;
_variables.clear();
_sortedLineNumbers = _program.keys.toList()..sort();
_currentLineIndex = 0;
await _runProgram();
_isRunning = false;
} else if (lowerInput == 'list') {
_listProgram();
} else if (lowerInput == 'help') {
_showHelp();
} else if (lowerInput.startsWith('edit ')) {
int lineNumber = int.tryParse(lowerInput.substring(5).trim()) ?? -1;
if (_program.containsKey(lineNumber)) {
String code = _program[lineNumber]!;
_controller.text = '$lineNumber $code';
_controller.selection = TextSelection.fromPosition(
TextPosition(offset: _controller.text.length),
);
} else {
setState(() {
_output += '\nLine $lineNumber does not exist.';
});
}
} else {
RegExp lineRegex = RegExp(r'^(\d+)\s*(.*)');
Match? match = lineRegex.firstMatch(inputLine);
if (match != null) {
int lineNumber = int.parse(match.group(1)!);
String code = match.group(2)?.trim() ?? '';
if (code.isEmpty) {
_program.remove(lineNumber);
setState(() {
_output += '\nDeleted line $lineNumber';
});
} else {
_program[lineNumber] = code;
setState(() {
_output += '\n$lineNumber $code';
});
}
} else {
setState(() {
_output += '\nInvalid input.';
});
}
}
}
void _showHelp() {
setState(() {
_output += '\nAvailable commands:';
_output += '\n- new: Clears the current program.';
_output += '\n- run: Runs the current program.';
_output += '\n- list: Lists the current program.';
_output += '\n- edit <line number>: Edits the specified line.';
_output += '\n- help: Shows this help message.';
_output += '\nTo add or modify a line, type: <line number> <code>';
_output += '\nTo delete a line, type the line number without code.';
_output += '\n\nSupported BASIC commands in programs:';
_output += '\n- let: Assign a value to a variable (e.g., LET x = 5)';
_output += '\n- print: Print text or variables (e.g., PRINT "Hello", PRINT x)';
_output += '\n- input: Read a number from user input (e.g., INPUT x)';
_output += '\n- if ... then ...: Conditional execution (e.g., IF x > 0 THEN PRINT x)';
_output += '\n- goto: Jump to a specified line number (e.g., GOTO 10)';
_output += '\n- for ... to ...: Loop from a start to end value (e.g., FOR i = 1 TO 10)';
_output += '\n- next: Ends a for loop (e.g., NEXT)';
_output += '\n- end: Ends the program execution.';
});
}
void _listProgram() {
setState(() {
List<int> lines = _program.keys.toList()..sort();
for (var line in lines) {
_output += '\n$line ${_program[line]}';
}
});
}
Future<void> _runProgram() async {
while (_currentLineIndex < _sortedLineNumbers.length) {
int lineNumber = _sortedLineNumbers[_currentLineIndex];
String? codeLine = _program[lineNumber];
if (codeLine != null) {
bool shouldContinue = await _executeLine(codeLine);
if (!shouldContinue) break;
}
_currentLineIndex++;
}
}
Future<bool> _executeLine(String codeLine) async {
codeLine = codeLine.trim();
if (codeLine.toLowerCase() == 'end') {
return false;
} else if (codeLine.toLowerCase().startsWith('print')) {
await _handlePrint(codeLine.substring(5).trim());
} else if (codeLine.toLowerCase().startsWith('input')) {
await _handleInputCommand(codeLine.substring(5).trim());
} else if (codeLine.toLowerCase().startsWith('let')) {
_handleLet(codeLine.substring(3).trim());
} else if (codeLine.toLowerCase().startsWith('if')) {
await _handleIfThen(codeLine.substring(2).trim());
} else if (codeLine.toLowerCase().startsWith('for')) {
await _handleForLoop(codeLine.substring(3).trim());
} else if (codeLine.toLowerCase().startsWith('next')) {
// 'next' is handled in _handleForLoop
} else if (codeLine.toLowerCase().startsWith('goto')) {
await _handleGoto(codeLine.substring(4).trim());
} else {
setState(() {
_output += '\nSyntax error at line ${_sortedLineNumbers[_currentLineIndex]}';
});
}
return true;
}
Future<void> _handlePrint(String expr) async {
expr = expr.trim();
if (expr.startsWith('"') && expr.endsWith('"')) {
String text = expr.substring(1, expr.length - 1);
setState(() {
_output += '\n$text';
});
} else {
num value = _evaluateExpression(expr);
setState(() {
_output += '\n$value';
});
}
}
Future<void> _handleInputCommand(String varName) async {
setState(() {
_output += '\nEnter value for $varName: ';
});
_isWaitingInput = true;
_inputCompleter = Completer<void>();
await _inputCompleter!.future;
num? value = num.tryParse(_inputBuffer);
if (value == null) {
setState(() {
_output += '\nInvalid number. Using 0.';
});
value = 0;
}
_variables[varName] = value;
}
void _handleLet(String expr) {
List<String> parts = expr.split('=');
if (parts.length != 2) {
setState(() {
_output += '\nSyntax error in LET statement.';
});
return;
}
String varName = parts[0].trim();
num value = _evaluateExpression(parts[1].trim());
_variables[varName] = value;
}
Future<void> _handleIfThen(String expr) async {
RegExp thenRegex = RegExp(r'\bthen\b', caseSensitive: false);
List<String> parts = expr.split(thenRegex);
if (parts.length != 2) {
setState(() {
_output += '\nSyntax error in IF statement.';
});
return;
}
bool condition = _evaluateCondition(parts[0].trim());
if (condition) {
await _executeLine(parts[1].trim());
}
}
Future<void> _handleForLoop(String expr) async {
RegExp forRegex = RegExp(r'^(\w+)\s*=\s*(.+)\s+to\s+(.+)$', caseSensitive: false);
Match? match = forRegex.firstMatch(expr);
if (match == null) {
setState(() {
_output += '\nSyntax error in FOR statement.';
});
return;
}
String varName = match.group(1)!;
num startValue = _evaluateExpression(match.group(2)!);
num endValue = _evaluateExpression(match.group(3)!);
_variables[varName] = startValue;
int loopStartLineIndex = _currentLineIndex;
while (_variables[varName]! <= endValue) {
_currentLineIndex++;
while (_currentLineIndex < _sortedLineNumbers.length &&
!_program[_sortedLineNumbers[_currentLineIndex]]!.toLowerCase().startsWith('next')) {
String? codeLine = _program[_sortedLineNumbers[_currentLineIndex]];
if (codeLine != null) {
bool shouldContinue = await _executeLine(codeLine);
if (!shouldContinue) return;
}
_currentLineIndex++;
}
_variables[varName] = _variables[varName]! + 1;
if (_variables[varName]! <= endValue) {
_currentLineIndex = loopStartLineIndex;
}
}
// Skip the 'next' line
}
Future<void> _handleGoto(String expr) async {
int targetLine = int.tryParse(expr) ?? -1;
if (_program.containsKey(targetLine)) {
_currentLineIndex = _sortedLineNumbers.indexOf(targetLine) - 1;
} else {
setState(() {
_output += '\nLine $targetLine does not exist.';
});
}
}
// Declare these variables at the class level within your state class
late String _expr;
late int _pos;
num _evaluateExpression(String expr) {
expr = expr.trim();
_expr = expr;
_pos = 0;
try {
num value = _parseExpression();
return value;
} catch (e) {
setState(() {
_output += '\nError evaluating expression: $expr';
});
return 0;
}
}
void _skipWhitespace() {
while (_pos < _expr.length && _expr[_pos] == ' ') {
_pos++;
}
}
num _parseExpression() {
num value = _parseTerm();
while (true) {
_skipWhitespace();
if (_pos >= _expr.length) break;
String op = _expr[_pos];
if (op == '+') {
_pos++;
num term = _parseTerm();
value += term;
} else if (op == '-') {
_pos++;
num term = _parseTerm();
value -= term;
} else {
break;
}
}
return value;
}
num _parseTerm() {
num value = _parseFactor();
while (true) {
_skipWhitespace();
if (_pos >= _expr.length) break;
String op = _expr[_pos];
if (op == '*') {
_pos++;
num factor = _parseFactor();
value *= factor;
} else if (op == '/') {
_pos++;
num factor = _parseFactor();
value /= factor;
} else {
break;
}
}
return value;
}
num _parseFactor() {
_skipWhitespace();
if (_pos >= _expr.length) {
throw Exception('Unexpected end of expression');
}
String currentChar = _expr[_pos];
if (currentChar == '(') {
_pos++;
num value = _parseExpression();
_skipWhitespace();
if (_pos >= _expr.length || _expr[_pos] != ')') {
throw Exception('Expected closing parenthesis');
}
_pos++;
return value;
} else if (currentChar == '+' || currentChar == '-') {
// Handle unary plus and minus
String op = currentChar;
_pos++;
num value = _parseFactor();
return op == '-' ? -value : value;
} else if (isDigit(currentChar) || currentChar == '.') {
return _parseNumber();
} else if (isLetter(currentChar)) {
return _parseVariable();
} else {
throw Exception('Unexpected character: $currentChar');
}
}
num _parseNumber() {
int start = _pos;
while (_pos < _expr.length && (isDigit(_expr[_pos]) || _expr[_pos] == '.')) {
_pos++;
}
String numberString = _expr.substring(start, _pos);
num? value = num.tryParse(numberString);
if (value == null) {
throw Exception('Invalid number: $numberString');
}
return value;
}
num _parseVariable() {
int start = _pos;
while (_pos < _expr.length && isLetterOrDigit(_expr[_pos])) {
_pos++;
}
String varName = _expr.substring(start, _pos);
if (_variables.containsKey(varName)) {
return _variables[varName]!;
} else {
throw Exception('Undefined variable: $varName');
}
}
bool isDigit(String s) {
return s.codeUnitAt(0) >= '0'.codeUnitAt(0) && s.codeUnitAt(0) <= '9'.codeUnitAt(0);
}
bool isLetter(String s) {
int codeUnit = s.codeUnitAt(0);
return (codeUnit >= 'a'.codeUnitAt(0) && codeUnit <= 'z'.codeUnitAt(0)) ||
(codeUnit >= 'A'.codeUnitAt(0) && codeUnit <= 'Z'.codeUnitAt(0));
}
bool isLetterOrDigit(String s) {
return isLetter(s) || isDigit(s);
}
bool _evaluateCondition(String condition) {
RegExp condRegex = RegExp(r'^(.+)\s*(=|<>|<|>|<=|>=)\s*(.+)$');
Match? match = condRegex.firstMatch(condition);
if (match == null) {
setState(() {
_output += '\nInvalid condition: $condition';
});
return false;
}
num left = _evaluateExpression(match.group(1)!.trim());
num right = _evaluateExpression(match.group(3)!.trim());
String op = match.group(2)!;
switch (op) {
case '=':
return left == right;
case '<>':
return left != right;
case '<':
return left < right;
case '>':
return left > right;
case '<=':
return left <= right;
case '>=':
return left >= right;
default:
return false;
}
}
void _scrollToBottom() {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_scrollController.hasClients) {
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: SafeArea(
child: Container(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: SingleChildScrollView(
controller: _scrollController,
child: SelectableText(
_output,
style: const TextStyle(
color: Colors.green,
fontFamily: 'Courier',
fontSize: 16,
),
textAlign: TextAlign.left,
),
),
),
TextField(
controller: _controller,
style: const TextStyle(
color: Colors.green,
fontFamily: 'Courier',
fontSize: 16,
),
cursorColor: Colors.green,
autofocus: true,
decoration: const InputDecoration(
border: InputBorder.none,
fillColor: Colors.black,
filled: true,
),
onEditingComplete: _handleInput,
),
],
),
),
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment