LPC90

DRAFT Language specification for the LPC90 programming language

View on GitHub

DRAFT LPC90 Language Specification

Table of Contents

  1. Introduction
    1.1 Purpose of this Specification
    1.2 History of LPC
    1.3 Design Philosophy

  2. Lexical Structure
    2.1 Characters
    2.2 Tokens
    2.3 Keywords
    2.4 Identifiers
    2.5 Literals
    2.6 Comments

  3. Types
    3.1 Primitive Types
    3.2 Object Types
    3.3 Special Types (nil, mixed)
    3.4 Type Conversion

  4. Variables
    4.1 Variable Declarations
    4.2 Scope and Lifetime of Variables
    4.3 Default Values

  5. Operators
    5.1 Operator Precedence and Associativity
    5.2 Arithmetic Operators
    5.3 Relational and Logical Operators
    5.4 Assignment Operators

  6. Expressions
    6.1 Evaluation Order
    6.2 Method Calls
    6.3 Field Access
    6.4 Conditional Expressions

  7. Statements
    7.1 Block Statements
    7.2 Control Flow Statements (if, else, while, for)
    7.3 Return Statements

  8. Objects
    8.1 Object Definition and Structure
    8.2 Fields
    8.3 Methods
    8.4 Inheritance

  9. Execution
    9.1 Program Entry Point
    9.2 Object Instantiation and Initialization

  10. Preprocessing Directives
    10.1 #include Directives
    10.2 #define Macros

  11. Error Handling
    11.1 Syntax Errors
    11.2 Runtime Errors

  12. Appendices
    A. Backus-Naur Form (BNF) Grammar
    B. Reserved Words

1. Introduction

1.1 Purpose of this Specification [toc]

The purpose of this specification is to define the syntax, semantics, and structure of LPC, a programming language designed for building and enhancing LPMuds (online, multi-user, text-only role-playing games) — specifically, as the language existed very soon after its inception. By offering a formal and detailed description of LPC circa 1990, the LPC90 language specification aims to:

  1. Establish a Foundation: Establish a consistent and widely accepted foundation that describes an original (or near-original) dialect of LPC, providing a common starting point from which future LPC specifications can evolve;

  2. Facilitate Compatibility: Provide a clear and consistent reference for developers working with or adapting legacy LPC-based systems;

  3. Enable Collaboration: Provide a foundation for community-driven enhancements or adaptations of LPC, while maintaining fidelity to LPC’s original design;

  4. Preserve Historical Context: Document the features and design principles of LPC to ensure that the language’s original intent and functionality are not lost over time; and,

  5. Promote Education and Exploration: Serve as a resource for programmers, hobbyists, and gamers interested in the role of LPC in the development of interactive gaming.

This specification is modeled after the scope and structure of modern programming language specifications but reflects LPC90’s simpler, more focused feature set. It aims to strike a balance between technical rigor and accessibility, making it useful for both technical audiences building LPC90-compliant compilers and systems and non-technical audiences interested in LPC’s legacy.

1.2 History of LPC [toc]

Lars Pensjö created LPC in 1989 as a streamlined programming language for MUD (“Multi-User Dungeon”) game engine developers and world architects. MUDs written in LPC came to be known, accordingly, as LPMuds.

1.3 Design Philosophy [toc]

The original design of LPC (i.e., LPC90) reflected a pragmatic approach to programming for text-based, multi-user games, such as MUDs. Its philosophy was guided by simplicity, efficiency, and flexibility, aiming to empower developers to create and maintain complex, interactive systems with minimal overhead.

Key Principles

  1. C-like Simplicity
    LPC90 drew heavily from the syntax and semantics of the C programming language, providing developers with a familiar and straightforward programming style. This similarity reduced the learning curve for those already acquainted with C, while maintaining the readability and ease of use that procedural programming offered.

  2. Lightweight Object Orientation
    While not fully object-oriented, LPC90 introduced lightweight object support to facilitate modular design and code reuse. Objects encapsulated state and behavior, enabling developers to define interactive entities &emdash; such as players, items, and rooms &emdash; in a manner that was both intuitive and efficient.

  3. Performance for Real-Time Applications
    Designed for real-time, interactive environments, LPC90 prioritized performance. Its features were streamlined to minimize unnecessary complexity, ensuring that MUD servers could handle a high volume (by 1990 standards) of concurrent users and events without significant resource constraints.

  4. Flexibility and Extensibility
    LPC90 balanced simplicity with extensibility, providing mechanisms such as inheritance and preprocessor directives (‘#include’, ‘#define’) to allow developers to structure and extend their code effectively. This flexibility supported a wide variety of use cases, from simple systems to complex game mechanics.

  5. Minimalism
    The language avoided overloading developers with unnecessary features, instead focusing on core functionality that aligned with its intended purpose. This minimalist design philosophy ensured that developers could focus on problem-solving rather than grappling with excessive language constructs.

  6. Preservation of Developer Creativity
    LPC90 provided developers with tools to implement custom behavior and mechanics without imposing rigid design patterns. This freedom fostered creativity and adaptability, enabling the development of highly customized and innovative MUDs.

Historical Context

LPC90’s design reflected the state of computer hardware, networking, programming, and MUD development in the early 1990s. By blending the procedural style of C with lightweight object-oriented concepts, it provided a bridge between traditional structured programming and emerging object-oriented methodologies, making it uniquely suited to its era.

2. Lexical Structure

2.1 Characters [toc]

The characters used in LPC90 source code are defined by the standard ASCII character set. These characters are divided into the following categories based on their role in the language:

1. Letters

2. Digits

3. Whitespace

4. Special Characters

! " # $ % & ' ( ) * + , - . / : ; < = > ? [ \ ] ^ _ ` { | } ~

5. Escape Sequences

6. Comments

7. Character Encoding

8. Case Sensitivity


Example

Here is an example showcasing valid use of characters in LPC90:

int count = 10;   // Variable declaration
string name = "LPC90\n";  /* String with escape sequence */
if (count > 0) {          // Special characters used in syntax
  count--;              // Decrement operator
}

2.2 Tokens [toc]

In LPC90, a token is the smallest unit of meaningful text in the source code. Tokens are recognized during lexical analysis and are categorized into several types:

1. Identifiers

Identifiers are names used for variables, fields, methods, and other user-defined elements.

Example:

int my_varariable = 10;
string _name = "LPC90";

2. Keywords

Keywords are reserved words that have predefined meanings in LPC90. They cannot be used as identifiers.
Examples of keywords include:
if, else, for, while, return, true, false, nil, inherit

Example:

if (x > 0) {
    return true;
}

3. Literals

Literals represent fixed values in the source code. LPC90 supports the following types of literals:

Example:

int num = 42;
float pi = 3.14;
string message = "Hello";
status flag = true;

4. Operators

Operators are symbols that perform computations or comparisons. Examples include:

Example:

int result = 10 + 5;
if (x >= 0 && x < 100) {
    x += 1;
}

5. Delimiters

Delimiters are symbols used to separate or group code elements. They include:

Example:

if (x > 0) {
    my_array[0] = 10;
}

6. Comments

Comments are ignored by the compiler and are used for documentation:

Example:

// This is a single-line comment
/* This is a
   multi-line comment */

Summary

Tokens are categorized as:

  1. Identifiers: Names of variables, fields, and methods.
  2. Keywords: Reserved words with predefined meanings.
  3. Literals: Fixed values such as numbers, strings, or logical constants.
  4. Operators: Symbols for computation, comparison, and logic.
  5. Delimiters: Symbols that structure the code.
  6. Comments: Non-executable documentation in the code.

The proper use of tokens ensures that LPC90 source code is well-formed and understandable by the compiler.

2.3 Keywords [toc]

Keywords in LPC90 are reserved words with predefined meanings. They are an essential part of the language syntax and cannot be used as identifiers for variables, methods, or other user-defined elements.

List of Keywords

The following words are reserved in LPC90:

Usage of Keywords

Keywords must be written in lowercase. LPC90 is case-sensitive, so writing If instead of if will result in a syntax error.

Example:

if (health > 0) {
    return true;
} else {
    return false;
}

In this example:

Restrictions

Attempting to use a keyword as an identifier will result in a compilation error. For example:

Invalid Code:

int if = 10;  // Error: 'if' is a reserved keyword

Summary

Keywords are reserved words that define the structure and behavior of LPC90 programs. They include control structures, logical constants, and object-oriented terms. Since LPC90 is case-sensitive, keywords must be written in lowercase exactly as specified.

2.4 Identifiers [toc]

Identifiers in LPC90 are names used to represent variables, methods, fields, parameters, and other user-defined elements. They allow developers to reference and manipulate data and behaviors in a program.

Rules for Identifiers

  1. Starting Character:
    An identifier must begin with:
    • A letter (A-Z, a-z)
    • An underscore (_)
  2. Subsequent Characters:
    After the first character, identifiers can include:
    • Letters (A-Z, a-z)
    • Digits (0-9)
    • Underscores (_)
  3. Case Sensitivity:
    LPC90 is case-sensitive, meaning identifiers such as Variable, variable, and VARIABLE are treated as distinct.

  4. Reserved Words Restriction:
    Identifiers cannot be the same as keywords or reserved words (e.g., if, else, return). Using reserved words as identifiers will result in a compilation error.

Valid and Invalid Identifiers

Valid Identifiers:

int health;
string _name;
float player1_score;

Invalid Identifiers:

int 1stPlayer;   // Cannot start with a digit
float if;        // 'if' is a reserved keyword
string player-name; // Cannot contain a hyphen

Naming Conventions

While LPC90 imposes no strict naming conventions, the following practices are encouraged to improve code readability:

  1. Use Descriptive Names:
    Choose names that clearly convey their purpose.
    Example:
    int health_points;
    string player_name;
    
  2. Underscores for Separation:
    Use underscores to separate words in multi-word identifiers.
    Example:
    float player_score;
    
  3. Avoid Reserved Words:
    Always avoid names that conflict with reserved keywords.

Summary

Identifiers in LPC90:

Adhering to these rules ensures that LPC90 programs are syntactically correct and readable.

2.5 Literals [toc]

Literals in LPC90 are fixed, constant values that appear directly in the source code. They represent specific data and can be categorized into the following types:


1. Integer Literals

Integer literals represent whole numbers, both positive and negative.

Examples:

int health = 100;
int damage = -25;

2. Float Literals

Float literals represent real numbers with a fractional part.

Examples:

float pi = 3.14;
float temperature = -12.5;

3. String Literals

String literals represent sequences of characters enclosed in double quotes (").

Escape Sequences:

Examples:

string name = "LPC90";
string greeting = "Hello, World!\nWelcome to LPC.";
string escaped = "Path: C:\\Users\\Player";

4. Logical Literals

LPC90 includes three logical constants:

Examples:

status is_alive = true;
status has_loot = false;
object weapon = nil;

Summary

LPC90 supports the following types of literals:

  1. Integer Literals: Whole numbers like 42 or -100.
  2. Float Literals: Real numbers like 3.14 or -0.01.
  3. String Literals: Text values enclosed in double quotes, supporting escape sequences.
  4. Logical Literals: The constants true, false, and nil.

Literals provide the foundation for constant values within LPC90 programs and are essential for defining variables, initializing fields, and representing fixed data.

2.6 Comments [toc]

Comments in LPC90 are used to add explanatory notes or documentation to the source code. Comments are ignored by the compiler during lexical analysis and do not affect program behavior. LPC90 supports two types of comments:


1. Single-Line Comments

Example:

// This is a single-line comment
int health = 100; // Initialize health to 100

2. Multi-Line Comments

Example:

/* 
   This is a multi-line comment.
   It spans multiple lines.
*/
int health = 100;

/* Multi-line comments can also
   be used to temporarily disable code.
int damage = 25;
*/

Comment Nesting

Invalid Example:

/* Outer comment
   /* Inner comment */  // Error: nested comments are not allowed
*/

Best Practices for Comments

  1. Clarity: Use comments to explain complex logic or document code intentions.
  2. Avoid Redundancy: Do not comment obvious code.
  3. Consistent Style: Follow a consistent style for comments throughout your code.

Good Example:

// Check if the player has enough health
if (health > 0) {
    reset();
}

Summary

LPC90 supports two types of comments:

Comments are essential for improving code readability, documentation, and maintenance but are ignored by the compiler during execution.

3. Types

3.1 Primitive Types [toc]

Types

Primitive Types

LPC90 supports the following primitive types:

  1. int
    • Represents integer values as a 32-bit signed integer with a range of -2,147,483,648 to 2,147,483,647.
    • Example: int count = 42;
  2. float
    • Represents floating-point numbers with single-precision, following the IEEE 754 standard, with approximately 7 decimal digits of precision and a range of about ±1.4 × 10⁻⁴⁵ to ±3.4 × 10³⁸.
    • Example: float pi = 3.14;
  3. status
    • Represents a boolean value, where:
      • true indicates a positive condition.
      • false indicates a negative condition.
    • Example: status is_ready = true;
  4. string
    • Represents sequences of characters.
    • Strings are immutable.
    • Example: string name = "player_one";
  5. void
    • Used to indicate that a method does not return a value.
    • Cannot be used as a variable type.
    • Example:
      void reset() {
          // Method implementation
      }
      

Primitive types provide the foundational building blocks for variables, method return types, and method parameters in LPC90. Additional types such as mapping and mixed are defined later in this specification.

3.2 Object Types [toc]

3.3 Special Types (`nil`, `mixed`) [toc]

3.4 Type Conversion [toc]

4. Variables

4.1 Variable Declarations [toc]

4.2 Scope and Lifetime of Variables [toc]

4.3 Default Values [toc]

5. Operators

5.1 Operator Precedence and Associativity [toc]

5.2 Arithmetic Operators [toc]

5.3 Relational and Logical Operators [toc]

5.4 Assignment Operators [toc]

6. Expressions

6.1 Evaluation Order [toc]

6.2 Method Calls [toc]

6.3 Field Access [toc]

6.4 Conditional Expressions [toc]

7. Statements

7.1 Block Statements [toc]

7.2 Control Flow Statements (`if`, `else`, `while`, `for`) [toc]

7.3 Return Statements [toc]

8. Objects

8.1 Object Definition and Structure [toc]

8.2 Fields [toc]

8.3 Methods [toc]

8.4 Inheritance [toc]

9. Execution

9.1 Program Entry Point [toc]

9.2 Object Instantiation and Initialization [toc]

10. Preprocessing Directives

10.1 #include Directives[toc]

10.2 #define Macros [toc]

11. Error Handling

11.1 Syntax Errors [toc]

11.2 Runtime Errors [toc]

12. Appendices

A. Backus-Naur Form (BNF) Grammar [toc]

This appendix defines the grammar of LPC90 using Backus-Naur Form (BNF). The grammar specifies the structure of an LPC source file, which consists of the following elements in order:

  1. Optional inherit declarations
  2. Optional preprocessor directives, including #include and #define
  3. Field declarations and definitions
  4. Method declarations and definitions

A.1 BNF Grammar

<source-file> ::= <inherit-section>? <preprocessor-section>? <field-section> <method-section>

<inherit-section> ::= "inherit" <string-literal> ";"

<preprocessor-section> ::= (<include-directive> | <define-directive>)*

<include-directive> ::= "#include" <file-path>
<file-path> ::= <quoted-file> | <bracketed-file>
<quoted-file> ::= "\"" <file-name> "\""
<bracketed-file> ::= "<" <file-name> ">"
<file-name> ::= <identifier> ("/" <identifier>)*

<define-directive> ::= "#define" <identifier> <replacement-text>

<field-section> ::= <field-declaration>*
<field-declaration> ::= <type> <identifier> ("=" <expression>)? ";"

<method-section> ::= <method-declaration>*
<method-declaration> ::= <type> <identifier> "(" <parameter-list>? ")" <block>
<parameter-list> ::= <parameter> ("," <parameter>)*
<parameter> ::= <type> <identifier>
<block> ::= "{" <statement>* "}"

<type> ::= "int" | "float" | "mapping" | "mixed" | "object" | "status" | "string" | "void"
<expression> ::= <literal> | <identifier> | <binary-expression> | <unary-expression>
<literal> ::= <integer-literal> | <float-literal> | <string-literal> | "true" | "false" | "nil"

<statement> ::= <expression> ";" 
              | "if" "(" <expression> ")" <block> ("else" <block>)?
              | "while" "(" <expression> ")" <block>
              | "for" "(" <expression>? ";" <expression>? ";" <expression>? ")" <block>
              | <block>

A.2 Example LPC Source File

inherit "base_object";

#define MAX_HEALTH 100

#include "local_file.h"    // File in the same directory
#include <standard_lib.h>  // File in the standard library

int health = 100;
string name;

void reset() {
    health = MAX_HEALTH;
}

A.3 Parsing of the Example

The example LPC file in A.2 (above) parses into the following sections:

Inherit Section:

inherit "base_object";

Preprocessor Section:

#define MAX_HEALTH 100

#include "local_file.h" // File in the same directory
#include <standard_lib.h> // File in the standard library

Field Section:

int health = 100;
string name;

Method Section:

void reset() { health = MAX_HEALTH; }

B. Reserved Words [toc]

This appendix lists the reserved words in LPC90. Reserved words are predefined keywords in the language that cannot be used as identifiers (such as variable names, function names, or object names). They are an integral part of the language syntax and semantics.

List of Reserved Words

The following words are reserved in LPC90:

Notes

  1. Case Sensitivity: LPC90 is a case-sensitive language. Reserved words must be written in lowercase as listed above.

  2. Future Extensions: Additional reserved words may be introduced in future versions of the LPC specification. Programs written in LPC90 should avoid using words that might reasonably become reserved in future specifications, such as switch or class.

  3. Impact on Identifiers: Because reserved words cannot be redefined, attempting to use them as identifiers will result in a compilation error.

Editor’s note: The draft LPC90 specification was generated with the assistance of ChatGPT.