Technical Note: Transforming Our MUD Language into a LALR and Turing Complete Language - wwestlake/Labyrinth GitHub Wiki
Currently, our language is designed with a focus on handling simple, intuitive MUD commands, such as go west
and say hello
. However, these commands are parsed with an ad-hoc parser, which lacks the rigor of a formal grammar. To achieve the goal of having a LALR (Look-Ahead Left-to-Right, Rightmost Derivation) parser and making our language Turing complete, we must rethink the language's syntax and grammar.
A LALR parser, typically generated using parser generators like Yacc/Bison or tools like ANTLR, is a powerful method to handle deterministic context-free grammars. It enables the language to be parsed efficiently and ensures that our game logic can scale up with more complex interactions and commands. By making the language Turing complete, we ensure it has the computational power to perform any computation that a general-purpose language can, which will enable complex scripting for game logic, AI behaviors, and more.
- LALR Parsing: Redesign the syntax and grammar of the language to be parsable by a LALR parser.
- Turing Completeness: Introduce constructs like loops, conditional statements, and recursion to ensure the language is Turing complete.
-
Maintain Simplicity for MUD Commands: While making the language more powerful, we must maintain an easy-to-use syntax for in-game commands like
go west
orsay hello
.
To achieve Turing completeness, the language must support:
-
Conditional Statements: The ability to make decisions based on conditions (e.g.,
if-else
). -
Loops: Constructs that allow repetition of actions (e.g.,
while
,for
). - Recursion: The ability to call functions recursively or allow tail-recursive behavior.
- Memory and State Management: Variables must be mutable, and there should be a mechanism for assigning values.
- Function Definitions: The language should allow the definition of reusable functions.
These features give the language the full computational power needed to perform any algorithm, thus making it Turing complete.
// Variables
let health = 100
let inventory = ["sword", "potion"]
// Function with recursion
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)
// Loops
while health > 0:
print("Keep playing!")
// Conditional
if inventory.contains("sword"):
print("You can attack!")
To create a LALR-parsable language, we need to formally define its grammar using BNF (Backus-Naur Form). The LALR parser will work with a deterministic context-free grammar, meaning that for any given input, the parser can decide which production rule to apply by looking ahead a fixed number of tokens.
- Context-free: The language's grammar must be context-free, meaning that the parsing of a statement should depend only on the structure, not the specific values of variables.
-
Unambiguous: The grammar must avoid ambiguity, which is a common issue with natural-language-like commands. For example, ensuring that
go west
cannot be confused with a function callgo('west')
.
Here’s an initial look at the grammar in BNF format:
<program> ::= <statement_list>
<statement_list> ::= <statement> | <statement> <statement_list>
<statement> ::= <variable_decl>
| <function_decl>
| <assignment>
| <command>
| <if_statement>
| <while_statement>
| <expression>
<variable_decl> ::= "let" <identifier> "=" <expression>
<function_decl> ::= "def" <identifier> "(" <parameter_list> ")" ":" <block>
<parameter_list> ::= <identifier> | <identifier> "," <parameter_list>
<assignment> ::= <identifier> "=" <expression>
<if_statement> ::= "if" <expression> "then" <block> ["else" <block>]
<while_statement> ::= "while" <expression> "do" <block>
<block> ::= <statement> | "{" <statement_list> "}"
<expression> ::= <term> | <term> <operator> <term>
<term> ::= <identifier> | <literal> | <function_call>
<function_call> ::= <identifier> "(" <argument_list> ")"
<argument_list> ::= <expression> | <expression> "," <argument_list>
<literal> ::= <number> | <string> | <boolean>
<operator> ::= "+" | "-" | "*" | "/" | "==" | "!=" | ">" | "<" | "and" | "or"
We also need to formally define MUD commands like go west
and say hello
. These commands can be represented as special statements in the grammar that are parsed differently from standard expressions or function calls.
<command> ::= "go" <direction> | "say" <message> | "look" <target> | "pick up" <item>
<direction> ::= "north" | "south" | "east" | "west"
<message> ::= <string>
<target> ::= <identifier> | <literal>
<item> ::= <identifier>
# MUD Commands
go west
say "Hello, world!"
# Variables and Function
let health = 100
let name = "Hero"
def attack(enemy):
if health > 0:
print(name + " attacks " + enemy)
else:
print(name + " is too weak to attack")
# Game loop
while health > 0:
attack("goblin")
health = health - 10
To implement the parser, we need to use a tool that supports LALR(1) parsing, such as ANTLR, Yacc/Bison, or FParsec. These tools allow us to define the grammar and generate a parser that can efficiently parse and process player inputs and game scripts.
We’ll define the language grammar using one of the parser generators, such as ANTLR. This will allow us to create a parser that can handle both commands and the programming constructs needed for Turing completeness.
grammar MUDLang;
program: statement_list EOF;
statement_list: statement*;
statement: variable_decl
| function_decl
| assignment
| command
| if_statement
| while_statement
| expression
;
variable_decl: 'let' IDENTIFIER '=' expression;
function_decl: 'def' IDENTIFIER '(' parameter_list ')' ':' block;
parameter_list: IDENTIFIER (',' IDENTIFIER)*;
assignment: IDENTIFIER '=' expression;
if_statement: 'if' expression 'then' block ('else' block)?;
while_statement: 'while' expression 'do' block;
block: statement | '{' statement_list '}';
expression: term (operator term)?;
term: IDENTIFIER | literal | function_call;
function_call: IDENTIFIER '(' argument_list ')';
argument_list: expression (',' expression)*;
literal: NUMBER | STRING | BOOLEAN;
operator: '+' | '-' | '*' | '/' | '==' | '!=' | '>' | '<' | 'and' | 'or';
command: 'go' direction
| 'say' message
| 'look' target
| 'pick up' item
;
direction: 'north' | 'south' | 'east' | 'west';
message: STRING;
target: IDENTIFIER | literal;
item: IDENTIFIER;
Using ANTLR, Yacc/Bison, or another parser generator, we can generate the LALR parser from the grammar defined above.
After making the language LALR and Turing complete, there are further enhancements we could explore:
- Standard Library: Adding a set of pre-defined functions for game logic, math operations, string manipulation, etc.
- Modules: Allowing the language to support namespaces or modules for organizing large scripts.
- Error Handling: Improving error reporting and recovery during parsing to make the language more user-friendly.
To make our MUD language LALR and Turing complete, we must introduce formal grammar and core programming constructs, such as variables, loops, conditionals, and recursion. This ensures that our language can be efficiently parsed and capable of complex computation. By designing the grammar in BNF and using tools like ANTLR or Yacc/Bison, we can build a powerful, extensible language that allows both simple player commands and advanced game scripting.
The resulting language will combine the simplicity of text-based MUD commands like go west
with the full computational power needed to handle complex game logic, AI behavior, and dynamic event handling.