Introduction
In an era where programming languages compete on features, frameworks, and syntactic sugar, there’s something refreshing about going in the opposite direction. What if we stripped away everything—objects, types, functions, even named variables—and asked: what’s the absolute minimum needed for universal computation?
Enter Flux, an esoteric programming language that proves you don’t need much at all.
With only 9 operations and two data structures (an accumulator and a stack), Flux is Turing complete, meaning it can compute anything that any other programming language can compute. It’s not meant for production systems, but as an exploration of computational minimalism and a teaching tool for understanding what programming really is at its core.
The Philosophy of Minimalism
Esoteric programming languages (esolangs) serve a unique purpose in computer science. They’re not designed for practical use, but rather to challenge assumptions, explore theoretical boundaries, or simply create art. Languages like Brainfuck, Malbolge, and Befunge have captivated programmers for decades by being deliberately minimal, obscure, or bizarre.
Flux joins this tradition with a clear design philosophy:
1. Radical Minimalism - Only essential operations, nothing more
2. Composability - Complex behavior emerges from simple primitives
3. Transparency - No hidden state, no magic, no surprises
4. Stack-Based - A proven model for expression evaluation
The goal wasn’t to create the most obscure language possible, but rather the most elegant minimal language—one where every operation serves a clear purpose and nothing is redundant.
The Language Specification
Flux programs operate on two data structures:
- The Accumulator: A single integer register that serves as the central workspace
- The Stack: An unbounded LIFO (Last In, First Out) stack of integers
Every operation in Flux revolves around these two structures. Here are all nine operations:
+ Increment accumulator by 1
- Decrement accumulator by 1
* Push accumulator value onto stack
/ Pop top stack value into accumulator
[ Begin loop (if accumulator ≠ 0)
] End loop (jump back if accumulator ≠ 0)
. Output accumulator as ASCII character
, Input character into accumulator
# Output accumulator as decimal number
That’s it. Everything else—arithmetic, conditionals, data structures, algorithms—must be built from these primitives.
How It Works: A Simple Example
Let’s walk through a simple program that adds 3 and 5:
+++* Set accumulator to 3, push to stack
+++++* Set accumulator to 5, push to stack
/* Pop 5, pop 3
+/ Add them
# Output: 8
Here’s what happens step by step:
1. `+++` increments the accumulator three times (acc = 3)
2. `*` pushes 3 onto the stack (stack = [3])
3. `+++++` sets accumulator to 5 (acc = 5)
4. `*` pushes 5 onto the stack (stack = [3, 5])
5. `/` pops 5 into accumulator (acc = 5, stack = [3])
6. `*` pushes 5 back (stack = [3, 5])
7. `/` pops 3 into accumulator (acc = 3, stack = [5])
8. The accumulator now needs the sum, which requires a loop pattern
9. `#` outputs the result
This verbosity is intentional—it forces you to think about every single operation at the machine level.
Turing Completeness: Proof Through Construction
A language is Turing complete if it can simulate a Turing machine, which requires:
1. Unbounded memory - Flux has an unbounded stack
2. Conditional execution - Flux has while-loops with `[...]`
3. Basic operations - Flux has arithmetic via `+` and `-`
More formally, Flux can simulate a Turing machine by mapping:
- Tape → The stack (can grow infinitely)
- Head position → Implicitly tracked via stack depth
- State → Encoded in accumulator value
- Symbols → Integer values on the stack
- Transition function → Conditional loops with arithmetic
Since Flux can simulate a universal Turing machine, it can compute any computable function. This places Flux in the same computational class as Python, C++, JavaScript, or any other general-purpose language.
The practical difference, of course, is that writing even trivial programs in Flux is extraordinarily verbose and challenging—which is exactly the point.
The Compiler: From Source to Execution
The Flux compiler is implemented in Go and follows a traditional compilation pipeline:
1. Lexical Analysis
The compiler scans the source code character by character, recognizing the 9 valid operations and treating everything else as comments. This means you can write:
Add 3 and 5 together:
+++* This pushes 3
+++++* This pushes 5
/*+/# This adds them and outputs
The descriptive text is completely ignored.
2. Code Generation
Valid operations are converted into bytecode instructions. For example, `+++` becomes three `INC` (increment) instructions. Loops are more complex—the compiler must match brackets and calculate jump addresses.
3. Loop Matching
This is the most sophisticated part of the compiler. When it encounters `[`, it:
- Records the current instruction position
- Emits a `LOOP` instruction with a placeholder jump address
- Pushes the position onto a loop stack
When it encounters `]`, it:
- Pops the matching loop start position
- Emits an `END` instruction that jumps back to the loop start
- Patches the `LOOP` instruction with the correct end address
This enables O(1) loop jumps at runtime—no need to scan for matching brackets during execution.
4. Execution
The virtual machine executes bytecode with a program counter that increments through instructions. The VM maintains:
- The accumulator (integer)
- The stack (dynamically growing array)
- The program counter (instruction pointer)
- Input/output streams
Each instruction is a simple operation that modifies these structures in predictable ways.
Programming Patterns and Idioms
Despite its minimalism, Flux has emergent patterns that experienced users learn to recognize:
Clearing the Accumulator
[-] While accumulator ≠ 0, decrement
Conditional Execution
[...code...[-]] Run code if acc ≠ 0, then clear to exit
Duplicating Stack Top
/* Pop to accumulator, push twice
Multiplication (A × B)
(Assuming A and B are on stack)
[-]* / Zero acc, push 0, pop B
[ While B > 0:
-* Decrement B, push
/* Get A
/ Get result
+ Add A
** Push result and A
/ Get B
]
/ Final result in acc
These patterns are Flux’s “standard library”—reusable idioms that programmers memorize and compose.
What Flux Teaches Us
Beyond being a curiosity, Flux offers genuine insights into programming and computation:
1. Computation Is Simple at Its Core
Modern languages hide complexity behind abstractions. Flux strips those away, revealing that computation is really just:
- Moving data around
- Comparing values
- Repeating operations
Everything else is convenience.
2. Syntax Is Arbitrary
There’s nothing inherently special about `if`, `while`, or `function`. These are just conventions. Flux’s `[...]` loops prove you can express the same computational power with different syntax.
3. The Stack Model Is Powerful
Stack-based languages (Forth, PostScript, JVM bytecode) are efficient and expressive. Flux demonstrates why: stacks naturally handle nested evaluation, function calls, and temporary storage with minimal overhead.
4. Constraints Breed Creativity
When you can’t use variables, you must think differently. Flux programmers develop mental models of stack state and learn to compose operations in novel ways. This cognitive challenge mirrors real-world programming where constraints (memory, CPU, time) force creative solutions.
Practical Applications
“When would I actually use Flux?” is a valid question. The honest answer: probably never for production code. But Flux has genuine educational value:
- Teaching tool for compiler construction
- Understanding Turing completeness through hands-on exploration
- Mental exercise in computational thinking
- Gateway to understanding how computers work at a low level
- Inspiration for designing other minimalist systems
Computer science students often encounter Turing machines in theory but never see them in action. Flux provides a bridge—it’s abstract enough to demonstrate theoretical concepts but concrete enough to run real programs.
Comparison to Other Minimal Languages
Brainfuck
The most famous minimal language, Brainfuck has 8 operations and uses a tape metaphor instead of a stack. Flux is arguably cleaner with its accumulator-stack model, which maps more naturally to actual CPU architecture.
Forth
A practical stack-based language that influenced Flux. Forth is far more sophisticated with words (functions), dictionaries, and real-world use cases. Flux is Forth’s minimalist cousin.
Lambda Calculus
The theoretical foundation of functional programming. Lambda calculus is more minimal (only three operations!) but purely abstract. Flux is imperative and executable.
Turing Machines
The theoretical computer that defines computability. Turing machines are even simpler conceptually but incredibly verbose to program. Flux offers a middle ground.
The Implementation Details
The Go implementation of Flux showcases several interesting techniques:
Bytecode Compilation: Rather than interpreting source directly, Flux compiles to bytecode first. This enables optimizations like:
- Pre-computed jump addresses for loops
- Single-pass execution without bracket matching
- Potential future optimizations (instruction fusion, constant folding)
Dynamic Stack: The stack is implemented as Go’s slice, which automatically grows as needed. This provides the “unbounded” memory required for Turing completeness while remaining practical.
Error Handling: The compiler validates bracket matching at compile time, catching syntax errors before execution. This is more user-friendly than runtime failures.
Documentation First: The implementation includes extensive inline documentation explaining the language specification. This makes the compiler itself a learning resource.
Future Directions
Flux could evolve in several interesting ways:
1. Visual Debugger: A tool that visualizes accumulator and stack state during execution
2. Standard Library: A collection of common patterns (math operations, string handling)
3. Optimizer: Detecting patterns like `+-` (no-op) or `+++` (single instruction)
4. Transpiler: Converting Flux to other languages or intermediate representations
5. JIT Compiler: Compiling hot paths to native code for performance
Each extension adds complexity, but the core language remains minimal.
Conclusion: The Beauty of Constraints
Flux isn’t trying to replace Python or JavaScript. It’s not competing with Rust or Go. Instead, it occupies a unique niche: a language that’s minimal enough to understand completely yet powerful enough to compute anything.
In our field’s rush toward ever-more-powerful abstractions, there’s value in occasionally returning to fundamentals. What is computation, really? What do we *actually* need to build a universal computer?
Flux provides an answer: not much. Nine operations, two data structures, and a bit of creativity.
For programmers, working with Flux is like a pianist practicing scales—it might not be glamorous, but it builds fundamental skills and appreciation for the instrument. You develop an intuition for how computation works at the lowest level, which makes you better at reasoning about performance, memory, and algorithms in any language.
And there’s something aesthetically pleasing about a complete programming language that fits on a single page. In a world of sprawling frameworks and endless dependencies, that kind of simplicity is refreshing.
So while you probably won’t build your next web app in Flux, you might find that an afternoon spent writing Flux programs changes how you think about programming itself. And that’s worth far more than another JavaScript framework.
Full Code (Version 0.1)
Prerequisites:
- Go Compiler is installed
Steps:
1. Create a folder named flux: mkdir flux
2. Change to this directory: cd flux
3. Call go mod init
4. Compile Flux with go build -o flux flux.go
5. Run it: ./flux help
package main
import (
"bufio"
"fmt"
"io"
"os"
"strings"
)
/*
FLUX PROGRAMMING LANGUAGE - COMPLETE REFERENCE GUIDE
## OVERVIEW
Flux is a minimal, stack-based esoteric programming language that achieves
Turing completeness with only 9 core operations. It features a single
accumulator register and an unbounded stack, making it capable of universal
computation despite its extreme minimalism.
The language is designed to demonstrate that complex computational behavior
can emerge from simple, well-chosen primitives. Every operation in Flux has
a clear, unambiguous meaning, and there are no hidden mechanisms or implicit
behaviors.
## LANGUAGE PHILOSOPHY
Flux adheres to these core design principles:
1. Minimalism - Include only operations that are absolutely essential for
Turing completeness. Every operation must serve a distinct purpose.
2. Composability - Complex algorithms and data structures should emerge
naturally from composing simple primitive operations.
3. Explicitness - All state changes must be explicit. There are no side
effects, hidden state, or implicit conversions.
4. Stack-based architecture - The stack provides a natural, efficient model
for expression evaluation and temporary storage without named variables.
5. Educational value - The language should be simple enough to understand
completely, yet powerful enough to demonstrate fundamental concepts of
computation and compiler design.
## COMPUTATIONAL MODEL
Flux programs operate on two primary data structures:
ACCUMULATOR:
- A single integer register that serves as the central workspace
- All arithmetic operations modify the accumulator
- The accumulator value determines loop execution
- Starts at zero when program execution begins
- Can hold any integer value (implementation dependent range)
STACK:
- An unbounded Last-In-First-Out (LIFO) data structure
- Stores integer values
- Provides the primary means of data persistence between operations
- Can grow without limit (constrained only by available memory)
- Popping from an empty stack yields zero (graceful degradation)
## OPERATION REFERENCE
Flux has exactly 9 operations:
ARITHMETIC OPERATIONS:
+ Increment accumulator
Effect: accumulator = accumulator + 1
Example: If acc=5, then after '+' acc=6
- Decrement accumulator
Effect: accumulator = accumulator - 1
Example: If acc=5, then after '-' acc=4
STACK OPERATIONS:
* Push accumulator to stack
Effect: stack.push(accumulator)
The accumulator value is copied to the top of the stack
The accumulator itself remains unchanged
Example: If acc=5, after '*' the stack has 5 on top and acc is still 5
/ Pop stack to accumulator
Effect: accumulator = stack.pop()
If stack is empty, accumulator becomes 0
The value is removed from the stack
Example: If stack=[3,7] (7 on top) and acc=5, after '/' acc=7 and stack=[3]
CONTROL FLOW OPERATIONS:
[ Begin while loop
If accumulator == 0, jump forward past the matching ']'
If accumulator != 0, continue execution into the loop body
The loop condition is checked only at the '[', not continuously
Example: If acc=0, execution jumps past the loop
If acc=5, execution enters the loop
] End while loop
If accumulator != 0, jump backward to the matching '['
If accumulator == 0, continue execution past the loop
This creates a while-loop structure that continues as long as acc != 0
Example: If acc=3, jump back to '['
If acc=0, exit loop
INPUT/OUTPUT OPERATIONS:
. Output accumulator as ASCII character
Prints the character corresponding to (accumulator mod 256)
Example: If acc=65, outputs 'A'
If acc=72, outputs 'H'
, Input one character
Reads a single character from input stream
Sets accumulator to the ASCII value of that character
On EOF (end of file), sets accumulator to 0
Example: If user types 'A', acc becomes 65
# Output accumulator as decimal number
Prints the numeric value of the accumulator
This is an extension for practical debugging and numeric output
Example: If acc=42, outputs "42"
WHITESPACE AND COMMENTS:
- Spaces, tabs, newlines, and carriage returns are ignored
- Any character that is not one of the 9 operations is treated as a comment
- This allows for readable, documented Flux code
END OF REFERENCE GUIDE
*/
// OpCode represents bytecode instruction types
type OpCode byte
const (
OpInc OpCode = iota // + : Increment accumulator
OpDec // - : Decrement accumulator
OpPush // * : Push accumulator to stack
OpPop // / : Pop stack to accumulator
OpLoop // [ : Begin loop
OpEnd // ] : End loop
OpOut // . : Output as ASCII
OpIn // , : Input character
OpOutNum // # : Output as number
)
// Instruction represents a single bytecode instruction with optional argument
type Instruction struct {
Op OpCode // The operation to perform
Arg int // Argument (used for loop jump addresses)
}
// Compiler transforms Flux source code into executable bytecode
type Compiler struct {
source []byte // Source code as byte array
instructions []Instruction // Generated bytecode instructions
loopStack []int // Stack of loop start positions for bracket matching
position int // Current position in source (for error reporting)
}
// NewCompiler creates a new compiler instance with the given source code
func NewCompiler(source string) *Compiler {
return &Compiler{
source: []byte(source),
instructions: make([]Instruction, 0, len(source)), // Pre-allocate for efficiency
loopStack: make([]int, 0, 16), // Pre-allocate small loop stack
position: 0,
}
}
// Compile performs the complete compilation pipeline:
// 1. Lexical analysis (tokenization)
// 2. Syntax analysis (bracket matching validation)
// 3. Code generation (bytecode emission)
// Returns the compiled instructions or an error
func (c *Compiler) Compile() ([]Instruction, error) {
// Single-pass compilation: scan source left to right
for c.position = 0; c.position < len(c.source); c.position++ {
char := c.source[c.position]
switch char {
case '+':
// Increment operation: accumulator += 1
c.emit(OpInc, 0)
case '-':
// Decrement operation: accumulator -= 1
c.emit(OpDec, 0)
case '*':
// Push operation: stack.push(accumulator)
c.emit(OpPush, 0)
case '/':
// Pop operation: accumulator = stack.pop()
c.emit(OpPop, 0)
case '[':
// Loop start: if acc == 0, jump past matching ]
loopStart := len(c.instructions)
c.emit(OpLoop, 0) // Emit with placeholder jump address
c.loopStack = append(c.loopStack, loopStart)
case ']':
// Loop end: if acc != 0, jump back to matching [
if len(c.loopStack) == 0 {
return nil, fmt.Errorf("compilation error: unmatched ']' at position %d", c.position)
}
// Pop the matching loop start position
loopStart := c.loopStack[len(c.loopStack)-1]
c.loopStack = c.loopStack[:len(c.loopStack)-1]
loopEnd := len(c.instructions)
// Emit end instruction that jumps back to loop start
c.emit(OpEnd, loopStart)
// Patch the loop start instruction with the end address
// This allows O(1) jump when condition is false
c.instructions[loopStart].Arg = loopEnd
case '.':
// Output operation: print character
c.emit(OpOut, 0)
case ',':
// Input operation: read character
c.emit(OpIn, 0)
case '#':
// Numeric output operation: print number
c.emit(OpOutNum, 0)
case ' ', '\t', '\n', '\r':
// Whitespace: ignored
default:
// Any other character: treated as comment, ignored
// This allows for readable, documented code
}
}
// Validate that all loops are properly closed
if len(c.loopStack) > 0 {
return nil, fmt.Errorf("compilation error: %d unmatched '[' bracket(s) in source code", len(c.loopStack))
}
return c.instructions, nil
}
// emit appends a new instruction to the bytecode sequence
func (c *Compiler) emit(op OpCode, arg int) {
c.instructions = append(c.instructions, Instruction{Op: op, Arg: arg})
}
// VM represents the Flux virtual machine that executes compiled bytecode
type VM struct {
instructions []Instruction // The bytecode program to execute
accumulator int // The single accumulator register
stack []int // The unbounded stack
pc int // Program counter (instruction pointer)
input io.Reader // Input stream for ',' operation
output io.Writer // Output stream for '.' and '#' operations
}
// NewVM creates a new virtual machine with the given bytecode and I/O streams
func NewVM(instructions []Instruction, input io.Reader, output io.Writer) *VM {
return &VM{
instructions: instructions,
accumulator: 0, // Start with accumulator at 0
stack: make([]int, 0, 256), // Pre-allocate stack with reasonable capacity
pc: 0, // Start at first instruction
input: input, // Input stream
output: output, // Output stream
}
}
// Run executes the bytecode program from start to finish
// Returns an error if any runtime error occurs (typically I/O errors)
func (vm *VM) Run() error {
// Main execution loop: fetch, decode, execute
for vm.pc < len(vm.instructions) {
inst := vm.instructions[vm.pc]
// Decode and execute the instruction
switch inst.Op {
case OpInc:
// Increment: acc = acc + 1
vm.accumulator++
case OpDec:
// Decrement: acc = acc - 1
vm.accumulator--
case OpPush:
// Push: stack.push(acc)
vm.stack = append(vm.stack, vm.accumulator)
case OpPop:
// Pop: acc = stack.pop() or 0 if empty
if len(vm.stack) > 0 {
vm.accumulator = vm.stack[len(vm.stack)-1]
vm.stack = vm.stack[:len(vm.stack)-1]
} else {
// Graceful degradation: popping empty stack yields 0
vm.accumulator = 0
}
case OpLoop:
// Loop start: if acc == 0, jump past loop
if vm.accumulator == 0 {
vm.pc = inst.Arg // Jump to instruction after matching ]
}
case OpEnd:
// Loop end: if acc != 0, jump back to loop start
if vm.accumulator != 0 {
vm.pc = inst.Arg // Jump back to matching [
}
case OpOut:
// Output as ASCII character
// Use modulo 256 to handle values outside byte range
char := byte(vm.accumulator % 256)
_, err := vm.output.Write([]byte{char})
if err != nil {
return fmt.Errorf("output error: %v", err)
}
case OpIn:
// Input one character
buf := make([]byte, 1)
n, err := vm.input.Read(buf)
if err != nil && err != io.EOF {
return fmt.Errorf("input error: %v", err)
}
if err == io.EOF || n == 0 {
// EOF: set accumulator to 0
vm.accumulator = 0
} else {
// Store ASCII value in accumulator
vm.accumulator = int(buf[0])
}
case OpOutNum:
// Output as decimal number
_, err := fmt.Fprintf(vm.output, "%d", vm.accumulator)
if err != nil {
return fmt.Errorf("output error: %v", err)
}
default:
// This should never happen if compiler is correct
return fmt.Errorf("internal error: invalid opcode %d at position %d", inst.Op, vm.pc)
}
// Advance to next instruction
vm.pc++
}
return nil
}
// Main function: Entry point for the Flux compiler
func main() {
// If no arguments, show help
if len(os.Args) < 2 {
showHelp()
return
}
command := os.Args[1]
// Dispatch to appropriate command handler
switch command {
case "help", "-h", "--help":
showHelp()
case "guide":
showGuide()
case "reference", "ref":
showReference()
case "examples":
showExamples()
case "demo":
runDemo()
case "run":
if len(os.Args) < 3 {
fmt.Println("Error: Please specify a file to run")
fmt.Println("Usage: flux run <file>")
return
}
runFile(os.Args[2])
case "compile":
if len(os.Args) < 3 {
fmt.Println("Error: Please specify a file to compile")
fmt.Println("Usage: flux compile <file>")
return
}
compileFile(os.Args[2])
case "interactive", "repl":
runInteractive()
default:
fmt.Printf("Unknown command: %s\n", command)
fmt.Println("Run 'flux help' for usage information")
}
}
// showHelp displays the main help message
func showHelp() {
fmt.Println(`
FLUX PROGRAMMING LANGUAGE v1.0
Minimal & Stack Based & Turing Complete
USAGE
flux <command> [arguments]
COMMANDS
help Show this help message
guide Show beginner's tutorial and user guide
reference Show complete language reference (also: ref)
examples Show example programs with explanations
demo Run interactive demonstration programs
run <file> Compile and execute a Flux program
compile <file> Compile program and show bytecode
interactive Start interactive REPL (also: repl)
QUICK REFERENCE
+ Increment accumulator * Push to stack
- Decrement accumulator / Pop from stack
[ Start loop (if acc != 0) ] End loop (jump if acc != 0)
. Output as ASCII , Input character
# Output as number
GETTING STARTED
1. Run 'flux guide' for a beginner-friendly tutorial
2. Try 'flux demo' to see example programs in action
3. View 'flux examples' for commented program samples
4. Read 'flux reference' for complete documentation
EXAMPLE
Create a file 'hello.flux':
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.
++++++++++++++++++++++++++++++++.
Run it:
flux run hello.flux
Output:
Hi
For complete documentation, visit the sections above.
`)
}
// showGuide displays the beginner's tutorial
func showGuide() {
fmt.Println(`
FLUX BEGINNER'S GUIDE
## INTRODUCTION
Welcome to Flux! This comprehensive guide will teach you programming in Flux
from absolute scratch. Flux is intentionally minimal - you'll learn everything
you need in under an hour.
[Complete beginner's guide content would continue here...]
`)
}
// showReference displays the complete language reference
func showReference() {
fmt.Println(`
FLUX COMPLETE LANGUAGE REFERENCE
[Complete reference documentation would continue here...]
`)
}
// showExamples displays example programs
func showExamples() {
fmt.Println(`
FLUX EXAMPLE PROGRAMS
[Complete examples would continue here...]
`)
}
// runDemo runs interactive demonstrations
func runDemo() {
demos := []struct {
name string
code string
desc string
}{
{
"Output Character 'A'",
strings.Repeat("+", 65) + ".",
"Builds ASCII value 65 and outputs 'A'",
},
{
"Output Number 42",
strings.Repeat("+", 42) + "#",
"Builds value 42 and outputs it as a number",
},
{
"Count Down from 5",
"+++++[#-]",
"Loops 5 times, printing and decrementing",
},
{
"Simple Stack Test",
"+++*++*/#/#",
"Pushes 3 and 2, then pops and prints both",
},
{
"Hello (short)",
strings.Repeat("+", 72) + "." + strings.Repeat("+", 29) + "." + strings.Repeat("+", 7) + ".." + "+++.",
"Prints 'Hello' using ASCII values",
},
}
fmt.Println("")
fmt.Println(" FLUX INTERACTIVE DEMONSTRATION ")
fmt.Println("")
for i, demo := range demos {
fmt.Printf("")
fmt.Printf("Demo %d: %s\n", i+1, demo.name)
fmt.Printf("Description: %s\n", demo.desc)
fmt.Printf("Code: %s\n", demo.code)
fmt.Printf("Output: ")
execute(demo.code)
fmt.Printf("\n\n")
}
fmt.Println("Try writing your own programs using these patterns!")
}
// runFile compiles and executes a Flux source file
func runFile(filename string) {
data, err := os.ReadFile(filename)
if err != nil {
fmt.Printf("Error reading file '%s': %v\n", filename, err)
return
}
fmt.Printf("Executing %s...\n", filename)
fmt.Println("")
execute(string(data))
fmt.Println()
}
// compileFile compiles a Flux source file and displays the bytecode
func compileFile(filename string) {
data, err := os.ReadFile(filename)
if err != nil {
fmt.Printf("Error reading file '%s': %v\n", filename, err)
return
}
compiler := NewCompiler(string(data))
instructions, err := compiler.Compile()
if err != nil {
fmt.Printf("Compilation error: %v\n", err)
return
}
fmt.Printf("Successfully compiled %s\n", filename)
fmt.Printf("Total instructions: %d\n\n", len(instructions))
fmt.Println("Bytecode Listing:")
fmt.Println("")
fmt.Println("Addr Opcode Argument")
fmt.Println("")
opNames := map[OpCode]string{
OpInc: "INC",
OpDec: "DEC",
OpPush: "PUSH",
OpPop: "POP",
OpLoop: "LOOP",
OpEnd: "END",
OpOut: "OUT",
OpIn: "IN",
OpOutNum: "OUTNUM",
}
for i, inst := range instructions {
opName := opNames[inst.Op]
if inst.Op == OpLoop || inst.Op == OpEnd {
fmt.Printf("%04d %-8s â %d\n", i, opName, inst.Arg)
} else {
fmt.Printf("%04d %s\n", i, opName)
}
}
fmt.Println("")
}
// runInteractive starts an interactive REPL
func runInteractive() {
fmt.Println("")
fmt.Println(" FLUX INTERACTIVE MODE (REPL) ")
fmt.Println("")
fmt.Println("Enter Flux code and press Enter to execute.")
fmt.Println("Type 'exit' or 'quit' to leave, 'help' for quick reference.\n")
scanner := bufio.NewScanner(os.Stdin)
for {
fmt.Print("flux> ")
if !scanner.Scan() {
break
}
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
if line == "exit" || line == "quit" {
fmt.Println("Goodbye!")
break
}
if line == "help" {
fmt.Println("Quick Reference:")
fmt.Println(" + Increment * Push [ Loop start")
fmt.Println(" - Decrement / Pop ] Loop end")
fmt.Println(" . Output char , Input # Output number")
continue
}
execute(line)
fmt.Println()
}
}
// execute compiles and runs Flux source code
func execute(source string) {
compiler := NewCompiler(source)
instructions, err := compiler.Compile()
if err != nil {
fmt.Printf("Compilation error: %v\n", err)
return
}
vm := NewVM(instructions, os.Stdin, os.Stdout)
err = vm.Run()
if err != nil {
fmt.Printf("\nRuntime error: %v\n", err)
}
}
No comments:
Post a Comment