Plain is a minimalist, English-like programming language designed to make programming feel like natural conversation. It combines the power of traditional programming with intuitive, human-readable syntax.
- Overview
- Features
- Installation
- Quick Start
- Language Syntax
- Architecture
- Implementation Details
- CLI Interface
- Examples
- API Reference
- Development
- Testing
- Contributing
- License
Plain reimagines programming by using natural English sentences instead of cryptic symbols and keywords. Instead of writing:
let distance = 5;
distance += 18;
console.log(distance);
You can write:
set the distance to 5.
add 18 to the distance then display it.
The language maintains full programming power while being accessible to beginners and readable like plain English.
- ✅ Natural English Syntax: Write code using everyday language patterns
- ✅ Pronoun Support: Use "it" to refer to previous results
- ✅ Flexible Statements: Support for sequences with "then"
- ✅ Mathematical Operations: Addition, subtraction, and comparisons
- ✅ Conditional Logic: If-then statements with natural syntax
- ✅ Loop Constructs: Count-based iteration
- ✅ Variable Management: Simple variable declaration and manipulation
- ✅ Output Handling: Display results with natural phrasing
- ✅ Interactive REPL: Command-line interface for experimentation
- ✅ Extensible Architecture: Clean separation of concerns for future features
- Rust: Version 1.70 or later
- Cargo: Rust's package manager (included with Rust)
# Clone the repository
git clone https://github.com/yourusername/plain-lang.git
cd plain-lang
# Build the project
cargo build --release
# Run tests (optional)
cargo test
The compiled binary will be available at target/release/plain-lang
.
After building, you can run Plain programs directly:
# Run a Plain source file
./target/release/plain-lang run examples/demo.plain
# Start the interactive REPL
./target/release/plain-lang
Create a file called hello.plain
:
set the greeting to "Hello World".
show on screen the greeting.
Run it:
plain-lang run hello.plain
Expected output:
Hello World
Basic Assignment:
set the variable_name to 42.
set my_name to "Alice".
With Articles:
set the distance to 100.
set the message to "Welcome".
Variables can hold integers or strings and are case-sensitive.
Addition:
set the score to 10.
add 5 to the score.
add 3 to score then display it.
Subtraction:
set the temperature to 72.
subtract 10 from the temperature.
Result Tracking: All arithmetic operations automatically update the internal "last result" for pronoun usage.
Basic Display:
set the message to "Hello".
show on screen the message.
display the message.
Pronoun Usage:
add 5 to the counter then display it.
Flexible Output:
show on screen the result.
display the current_value.
Simple Conditions:
set the age to 25.
if age is greater than 18 then show on screen "Adult".
With Articles:
if the temperature is less than 0 then display "Freezing".
Comparison Operators:
is greater than
is less than
is equal to
Count-based Loops:
count to 5 and when you are done display "Done".
set the counter to 0.
count to 10 and when you are done show on screen the counter.
The loop executes the body statement the specified number of times.
Statement Chaining:
set the value to 10 then add 5 to value then display it.
Complex Sequences:
set the score to 0.
add 10 to the score then display it.
if score is greater than 5 then add 5 to score then display it.
Automatic Context Tracking:
set the distance to 100.
add 50 to the distance.
display it.
In this example, "it" refers to the result of the addition (150).
Pronoun Rules:
- "it" always refers to the most recent computed result
- Pronouns work across all operations that produce values
- Context is maintained throughout program execution
plain-lang/
├── src/
│ ├── main.rs # CLI entry point
│ ├── lexer.rs # Tokenization (logos-based)
│ ├── parser.rs # Recursive descent parsing
│ ├── ast.rs # Abstract Syntax Tree definitions
│ ├── runtime.rs # Execution engine
│ ├── repl.rs # Interactive REPL
│ └── lib.rs # Module declarations
├── examples/ # Sample programs
└── Cargo.toml # Dependencies and metadata
- Lexical Analysis: Source text → tokens
- Parsing: Tokens → AST
- Type Checking: Semantic validation (stub for future)
- Code Generation: AST → executable form (interpreter)
- Execution: Runtime evaluation with state management
- Interpreter Architecture: Tree-walking interpreter for simplicity and debugging
- Context Tracking:
last_value
system for natural pronoun support - Flexible Parsing: Extensive optional tokens for natural language variation
- Error Handling: Descriptive error messages for debugging
- Uses the
logos
crate for efficient tokenization - Case-insensitive keywords with regex patterns
- Comprehensive token set covering English-like constructs
- Recursive descent parser with manual precedence handling
- Extensive support for optional tokens ("the", articles, etc.)
- Context-aware parsing with position tracking
- Robust error reporting with position information
- Variable storage using
HashMap<String, Value>
- Result caching with
last_value
for pronoun support - Tree-walking evaluation of AST nodes
- Type-safe operations with explicit error handling
pub enum Stmt {
Set(String, Expr),
Add(Expr, String),
Sub(Expr, String),
Show(Expr),
If(Expr, Box<Stmt>),
Seq(Box<Stmt>, Box<Stmt>),
Loop(Expr, Box<Stmt>),
}
pub enum Expr {
Int(i64),
Str(String),
Var(String),
LastValue, // For pronouns
Gt(Box<Expr>, Box<Expr>),
Lt(Box<Expr>, Box<Expr>),
Eq(Box<Expr>, Box<Expr>),
}
plain-lang run <file> # Execute a Plain source file
plain-lang # Start interactive REPL
plain-lang --help # Display help information
$ plain-lang
Plain> set the value to 42.
Plain> display it.
42
Plain> add 8 to value then display it.
50
$ plain-lang run examples/demo.plain
23
23
...
set the x to 10.
add 5 to x.
display it.
set the temperature to 75.
if temperature is greater than 70 then display "Warm".
set the counter to 0.
count to 5 and when you are done display the counter.
set the score to 100.
add 25 to score then display it.
if score is greater than 120 then show on screen "High Score".
Parses Plain source code into an AST statement.
Creates a new execution environment.
Executes an AST statement in the current environment.
vars: HashMap<String, Value>
- Variable storagelast_value: Option<Value>
- Most recent result for pronouns
Int(i64)
- Integer valuesStr(String)
- String values
plain-lang/
├── src/
│ ├── main.rs # CLI and main entry point
│ ├── lexer.rs # Tokenization logic
│ ├── parser.rs # Language grammar and parsing
│ ├── ast.rs # Abstract syntax tree definitions
│ ├── runtime.rs # Execution environment
│ ├── repl.rs # Interactive shell
│ ├── codegen.rs # Code generation (future JIT)
│ └── typecheck.rs # Type checking (future features)
├── examples/ # Sample Plain programs
├── tests/ # Unit and integration tests
└── Cargo.toml # Project configuration
- Lexer: Add new tokens to
Token
enum inlexer.rs
- Parser: Extend
parse_stmt
andparse_expr
functions - AST: Add new variants to
Stmt
andExpr
enums - Runtime: Implement execution logic for new features
- Tests: Add test cases for new functionality
# Run all tests
cargo test
# Run specific test
cargo test test_parser
# Run with verbose output
cargo test -- --nocapture
- Unit Tests: Individual component testing
- Integration Tests: End-to-end program execution
- Parser Tests: Grammar and syntax validation
- Runtime Tests: Execution correctness
#[test]
fn test_basic_assignment() {
let input = "set the value to 42.";
let result = parse(input);
assert!(result.is_ok());
let mut runtime = Runtime::new();
let stmt = result.unwrap();
assert!(runtime.exec_stmt(&stmt).is_ok());
assert_eq!(runtime.vars.get("value"), Some(&Value::Int(42)));
}
We welcome contributions! Please follow these guidelines:
- Fork the repository
- Create a feature branch:
git checkout -b feature/new-feature
- Commit your changes:
git commit -am 'Add new feature'
- Push to the branch:
git push origin feature/new-feature
- Submit a pull request
- Rust Style: Follow standard Rust formatting (
cargo fmt
) - Documentation: Add doc comments for public APIs
- Testing: Include tests for new functionality
- Error Handling: Use descriptive error messages
- Performance: Consider efficiency in algorithm design
- New Language Features: Control flow, functions, data structures
- Performance Optimization: JIT compilation, caching
- Error Handling: Better error messages and recovery
- Tooling: IDE support, debuggers, formatters
- Documentation: Tutorials, examples, language specification
This project is licensed under the MIT License - see the LICENSE file for details.
Copyright (c) 2025 Studio Platforms
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
- Rust Community: For the excellent language and ecosystem
- Logos: Efficient tokenization library
- Chumsky: Parser combinator framework (inspiration)
- Open Source Community: For inspiration and tools
- Function Definitions:
define function_name as ...
- Data Structures: Arrays and objects
- File I/O: Reading and writing files
- Modules: Code organization and imports
- JIT Compilation: Performance optimization with Cranelift
- Debugger: Step-through execution and breakpoints
- Package Manager: Dependency management
- Web Integration: Browser-based execution
Plain: Making programming as natural as conversation.