We are currently working on new rules for what content should and shouldn't be allowed on this website, and are looking for feedback! See Esolang:2026 topicality proposal to view and give feedback on the current draft.

Don Giovanni

From Esolang
Jump to navigation Jump to search
This entry is not about Mozart's opera *Don Giovanni*.

Don Giovanni is designed by PSTF and his AI assistant. It is a Turing-complete language, that may be helpful to the design of Lingua Indeterminatum.

Syntax Overview

// Factorial (recursion)
fn factorial(n) {
    if n <= 1 {
        return 1;
    } else {
        return n * factorial(n - 1);
    }
}

// Fibonacci (double recursion)
fn fib(n) {
    if n <= 1 {
        return n;
    } else {
        return fib(n - 1) + fib(n - 2);
    }
}

// Higher-order function (closure)
fn make_adder(x) {
    fn adder(y) {
        return x + y;
    }
    return adder;
}

let add5 = make_adder(5);
print(add5(10));  // 15

// Loop (while) – ensures Turing completeness
let i = 0;
while i < 10 {
    print(i);
    i = i + 1;
}

print(factorial(5));  // 120
print(fib(10));       // 55

// Floats
let pi = 3.14159;
let radius = 5.0;
let area = pi * radius * radius;
print(area);   // 78.53975

// Lists
let fruits = ["apple", "banana", "cherry"];
print(fruits[1]);           // banana
fruits[1] = "blueberry";
print(fruits[1]);           // blueberry

// Nested lists & indexing
let matrix = [[1, 2], [3, 4]];
print(matrix[0][1]);        // 2

// Characters (with escapes)
let newline = '\n';
let tab = '\t';
print('A');                 // A
print('Hello'[0]);          // H (strings are indexable)

// Built-in functions: len() and push()
let numbers = [1, 2, 3];
print(len(numbers));        // 3
push(numbers, 4);
print(numbers[3]);          // 4

// All mixed together
let mixed = [1, 2.5, "three", 'x'];
print(mixed[2]);            // three

EBNF Definition

(* ------------------------------------------------------------------
   DonGiovanni Programming Language – Complete EBNF Specification
   ------------------------------------------------------------------ *)

program         = { statement } .

(* ----- Statements ------------------------------------------------- *)

statement       = let_stmt
                | assign_stmt
                | if_stmt
                | while_stmt
                | return_stmt
                | print_stmt
                | function_def
                | block
                | expression ";" .

let_stmt        = "let" identifier "=" expression ";" .

(* The left-hand side of an assignment must be an lvalue:
   a simple variable or an indexed expression. *)
assign_stmt     = lvalue "=" expression ";" .
lvalue          = identifier
                | expression "[" expression "]" .

if_stmt         = "if" expression block [ "else" block ] .
while_stmt      = "while" expression block .
return_stmt     = "return" expression ";" .
print_stmt      = "print" "(" expression ")" ";" .

function_def    = "fn" identifier "(" [ identifier { "," identifier } ] ")" block .
block           = "{" { statement } "}" .

(* ----- Expressions (precedence climbing) -------------------------- *)

expression      = logical_or .

logical_or      = logical_and { "||" logical_and } .
logical_and     = comparison { "&&" comparison } .

comparison      = additive { ( "==" | "!=" | "<" | "<=" | ">" | ">=" ) additive } .

additive        = multiplicative { ( "+" | "-" ) multiplicative } .

multiplicative  = unary { ( "*" | "/" | "%" ) unary } .

unary           = ( "-" | "!" ) unary
                | postfix .

postfix         = primary { "[" expression "]"
                          | "(" [ expression { "," expression } ] ")"
                          } .

(* ----- Primary expressions ---------------------------------------- *)

primary         = integer_literal
                | float_literal
                | string_literal
                | character_literal
                | "true" | "false" | "nil"
                | list_literal
                | identifier
                | "(" expression ")" .

list_literal    = "[" [ expression { "," expression } ] "]" .

(* ----- Lexical tokens --------------------------------------------- *)

integer_literal = digit { digit } .

float_literal   = digit { digit } "." digit { digit }
                | "." digit { digit } .

string_literal  = '"' { string_char | escape_sequence } '"' .

character_literal = "'" ( printable_char | escape_sequence ) "'" .

identifier      = ( letter | "_" ) { letter | digit | "_" } .

(* Escapes are valid inside both strings and characters *)
escape_sequence = "\" ( "n" | "t" | "\\" | "\"" | "'" ) .

(* Comments are // to end of line; they are ignored by the lexer *)
comment         = "//" { any_char - newline } newline .

(* Helper definitions (not part of the grammar proper) *)
digit           = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" .
letter          = "A" | "B" | … | "Z" | "a" | "b" | … | "z" .
string_char     = ? any character except backslash or double-quote ? .
printable_char  = ? any character except backslash or single-quote ? .
any_char        = ? any character ? .
newline         = ? ASCII LF or CR/LF ? .

Interpreter in Python

import sys

# ----------------------------------------------------------------------
# 1. Lexer / Tokenizer (unchanged)
# ----------------------------------------------------------------------

class Token:
    def __init__(self, type, value, line):
        self.type = type
        self.value = value
        self.line = line

    def __repr__(self):
        return f"Token({self.type}, {repr(self.value)})"

class Lexer:
    def __init__(self, source):
        self.source = source
        self.pos = 0
        self.line = 1
        self.tokens = []
        self.scan()

    def scan(self):
        while self.pos < len(self.source):
            c = self.source[self.pos]

            if c in ' \t':
                self.pos += 1
                continue
            if c == '\n':
                self.line += 1
                self.pos += 1
                continue

            if c == '/' and self.pos + 1 < len(self.source) and self.source[self.pos+1] == '/':
                self.pos += 2
                while self.pos < len(self.source) and self.source[self.pos] != '\n':
                    self.pos += 1
                continue

            if c.isdigit() or (c == '.' and self.pos + 1 < len(self.source) and self.source[self.pos+1].isdigit()):
                num = ''
                has_dot = False
                while self.pos < len(self.source):
                    ch = self.source[self.pos]
                    if ch.isdigit():
                        num += ch
                        self.pos += 1
                    elif ch == '.' and not has_dot:
                        has_dot = True
                        num += ch
                        self.pos += 1
                    else:
                        break
                if has_dot:
                    self.tokens.append(Token('FLOAT', float(num), self.line))
                else:
                    self.tokens.append(Token('INT', int(num), self.line))
                continue

            if c.isalpha() or c == '_':
                ident = ''
                while self.pos < len(self.source) and (self.source[self.pos].isalnum() or self.source[self.pos] == '_'):
                    ident += self.source[self.pos]
                    self.pos += 1
                keywords = {
                    'let': 'LET', 'fn': 'FN', 'if': 'IF', 'else': 'ELSE',
                    'while': 'WHILE', 'return': 'RETURN', 'print': 'PRINT',
                    'true': 'TRUE', 'false': 'FALSE', 'nil': 'NIL',
                    'len': 'LEN', 'push': 'PUSH'
                }
                tok_type = keywords.get(ident, 'IDENT')
                value = ident
                if tok_type == 'TRUE':
                    value = True
                elif tok_type == 'FALSE':
                    value = False
                elif tok_type == 'NIL':
                    value = None
                self.tokens.append(Token(tok_type, value, self.line))
                continue

            if c == '"':
                self.pos += 1
                s = ''
                while self.pos < len(self.source) and self.source[self.pos] != '"':
                    if self.source[self.pos] == '\\':
                        self.pos += 1
                        if self.pos < len(self.source):
                            esc = self.source[self.pos]
                            if esc == 'n': s += '\n'
                            elif esc == 't': s += '\t'
                            elif esc == '\\': s += '\\'
                            elif esc == '"': s += '"'
                            else: s += esc
                            self.pos += 1
                    else:
                        s += self.source[self.pos]
                        self.pos += 1
                if self.pos < len(self.source):
                    self.pos += 1
                self.tokens.append(Token('STRING', s, self.line))
                continue

            if c == "'":
                self.pos += 1
                ch = ''
                if self.pos < len(self.source):
                    if self.source[self.pos] == '\\':
                        self.pos += 1
                        if self.pos < len(self.source):
                            esc = self.source[self.pos]
                            if esc == 'n': ch = '\n'
                            elif esc == 't': ch = '\t'
                            elif esc == '\\': ch = '\\'
                            elif esc == "'": ch = "'"
                            else: ch = esc
                            self.pos += 1
                    else:
                        ch = self.source[self.pos]
                        self.pos += 1
                if self.pos < len(self.source) and self.source[self.pos] == "'":
                    self.pos += 1
                else:
                    raise SyntaxError(f"Unterminated character at line {self.line}")
                self.tokens.append(Token('CHAR', ch, self.line))
                continue

            if c == '=' and self.pos + 1 < len(self.source) and self.source[self.pos+1] == '=':
                self.tokens.append(Token('EQEQ', '==', self.line))
                self.pos += 2
                continue
            if c == '!' and self.pos + 1 < len(self.source) and self.source[self.pos+1] == '=':
                self.tokens.append(Token('NEQ', '!=', self.line))
                self.pos += 2
                continue
            if c == '<' and self.pos + 1 < len(self.source) and self.source[self.pos+1] == '=':
                self.tokens.append(Token('LTE', '<=', self.line))
                self.pos += 2
                continue
            if c == '>' and self.pos + 1 < len(self.source) and self.source[self.pos+1] == '=':
                self.tokens.append(Token('GTE', '>=', self.line))
                self.pos += 2
                continue
            if c == '&' and self.pos + 1 < len(self.source) and self.source[self.pos+1] == '&':
                self.tokens.append(Token('AND', '&&', self.line))
                self.pos += 2
                continue
            if c == '|' and self.pos + 1 < len(self.source) and self.source[self.pos+1] == '|':
                self.tokens.append(Token('OR', '||', self.line))
                self.pos += 2
                continue

            single = {
                '=': 'EQ', '+': 'PLUS', '-': 'MINUS', '*': 'STAR',
                '/': 'SLASH', '%': 'PERCENT', '(': 'LPAREN', ')': 'RPAREN',
                '{': 'LBRACE', '}': 'RBRACE', ';': 'SEMICOLON',
                '<': 'LT', '>': 'GT', '!': 'NOT',
                ',': 'COMMA', '[': 'LBRACKET', ']': 'RBRACKET'
            }
            if c in single:
                self.tokens.append(Token(single[c], c, self.line))
                self.pos += 1
                continue

            raise SyntaxError(f"Unexpected character '{c}' at line {self.line}")

        self.tokens.append(Token('EOF', None, self.line))

# ----------------------------------------------------------------------
# 2. Parser (unchanged)
# ----------------------------------------------------------------------

class ASTNode:
    pass

class Program(ASTNode):
    def __init__(self, statements):
        self.statements = statements

class LetStmt(ASTNode):
    def __init__(self, name, expr):
        self.name = name
        self.expr = expr

class AssignStmt(ASTNode):
    def __init__(self, target, expr):
        self.target = target
        self.expr = expr

class IfStmt(ASTNode):
    def __init__(self, cond, then_block, else_block):
        self.cond = cond
        self.then_block = then_block
        self.else_block = else_block

class WhileStmt(ASTNode):
    def __init__(self, cond, body):
        self.cond = cond
        self.body = body

class ReturnStmt(ASTNode):
    def __init__(self, expr):
        self.expr = expr

class PrintStmt(ASTNode):
    def __init__(self, expr):
        self.expr = expr

class Block(ASTNode):
    def __init__(self, statements):
        self.statements = statements

class FunctionDef(ASTNode):
    def __init__(self, name, params, body):
        self.name = name
        self.params = params
        self.body = body

class BinaryExpr(ASTNode):
    def __init__(self, left, op, right):
        self.left = left
        self.op = op
        self.right = right

class UnaryExpr(ASTNode):
    def __init__(self, op, expr):
        self.op = op
        self.expr = expr

class CallExpr(ASTNode):
    def __init__(self, callee, args):
        self.callee = callee
        self.args = args

class IndexExpr(ASTNode):
    def __init__(self, obj, index):
        self.obj = obj
        self.index = index

class VarExpr(ASTNode):
    def __init__(self, name):
        self.name = name

class IntLiteral(ASTNode):
    def __init__(self, value):
        self.value = value

class FloatLiteral(ASTNode):
    def __init__(self, value):
        self.value = value

class StringLiteral(ASTNode):
    def __init__(self, value):
        self.value = value

class CharLiteral(ASTNode):
    def __init__(self, value):
        self.value = value

class BoolLiteral(ASTNode):
    def __init__(self, value):
        self.value = value

class NilLiteral(ASTNode):
    pass

class ListLiteral(ASTNode):
    def __init__(self, elements):
        self.elements = elements

class Parser:
    def __init__(self, tokens):
        self.tokens = tokens
        self.pos = 0

    def peek(self):
        return self.tokens[self.pos]

    def next_token(self):
        tok = self.peek()
        self.pos += 1
        return tok

    def expect(self, type, err=None):
        if self.peek().type == type:
            return self.next_token()
        raise SyntaxError(f"Expected {type}, got {self.peek().type} at line {self.peek().line}")

    def parse_program(self):
        stmts = []
        while self.peek().type != 'EOF':
            stmts.append(self.parse_statement())
        return Program(stmts)

    def parse_statement(self):
        tok = self.peek()
        if tok.type == 'LET':
            return self.parse_let()
        if tok.type == 'FN':
            return self.parse_function()
        if tok.type == 'IF':
            return self.parse_if()
        if tok.type == 'WHILE':
            return self.parse_while()
        if tok.type == 'RETURN':
            return self.parse_return()
        if tok.type == 'PRINT':
            return self.parse_print()
        if tok.type == 'LBRACE':
            return self.parse_block()

        saved_pos = self.pos
        try:
            target = self.parse_expression()
            if self.peek().type == 'EQ':
                self.next_token()
                expr = self.parse_expression()
                self.expect('SEMICOLON')
                if not isinstance(target, (VarExpr, IndexExpr)):
                    raise SyntaxError(f"Invalid assignment target at line {tok.line}")
                return AssignStmt(target, expr)
        except:
            self.pos = saved_pos
        expr = self.parse_expression()
        self.expect('SEMICOLON')
        return expr

    def parse_let(self):
        self.next_token()
        name = self.expect('IDENT').value
        self.expect('EQ')
        expr = self.parse_expression()
        self.expect('SEMICOLON')
        return LetStmt(name, expr)

    def parse_function(self):
        self.next_token()
        name = self.expect('IDENT').value
        self.expect('LPAREN')
        params = []
        if self.peek().type != 'RPAREN':
            params.append(self.expect('IDENT').value)
            while self.peek().type == ',':
                self.next_token()
                params.append(self.expect('IDENT').value)
        self.expect('RPAREN')
        body = self.parse_block()
        return FunctionDef(name, params, body)

    def parse_block(self):
        self.expect('LBRACE')
        stmts = []
        while self.peek().type != 'RBRACE' and self.peek().type != 'EOF':
            stmts.append(self.parse_statement())
        self.expect('RBRACE')
        return Block(stmts)

    def parse_if(self):
        self.next_token()
        cond = self.parse_expression()
        then_block = self.parse_block()
        else_block = None
        if self.peek().type == 'ELSE':
            self.next_token()
            else_block = self.parse_block()
        return IfStmt(cond, then_block, else_block)

    def parse_while(self):
        self.next_token()
        cond = self.parse_expression()
        body = self.parse_block()
        return WhileStmt(cond, body)

    def parse_return(self):
        self.next_token()
        expr = self.parse_expression()
        self.expect('SEMICOLON')
        return ReturnStmt(expr)

    def parse_print(self):
        self.next_token()
        self.expect('LPAREN')
        expr = self.parse_expression()
        self.expect('RPAREN')
        self.expect('SEMICOLON')
        return PrintStmt(expr)

    def parse_expression(self, min_prec=0):
        left = self.parse_primary()
        return self.parse_binary_op(left, min_prec)

    def parse_binary_op(self, left, min_prec):
        prec = self.get_precedence()
        while prec >= min_prec:
            op = self.next_token()
            right = self.parse_expression(prec + 1)
            left = BinaryExpr(left, op.value, right)
            prec = self.get_precedence()
        return left

    def get_precedence(self):
        tok = self.peek()
        if tok.type in ('OR',): return 1
        if tok.type in ('AND',): return 2
        if tok.type in ('EQEQ', 'NEQ', 'LT', 'LTE', 'GT', 'GTE'): return 3
        if tok.type in ('PLUS', 'MINUS'): return 4
        if tok.type in ('STAR', 'SLASH', 'PERCENT'): return 5
        return -1

    def parse_primary(self):
        tok = self.peek()

        if tok.type == 'INT':
            self.next_token()
            return IntLiteral(tok.value)
        if tok.type == 'FLOAT':
            self.next_token()
            return FloatLiteral(tok.value)
        if tok.type == 'STRING':
            self.next_token()
            return StringLiteral(tok.value)
        if tok.type == 'CHAR':
            self.next_token()
            return CharLiteral(tok.value)
        if tok.type == 'TRUE':
            self.next_token()
            return BoolLiteral(True)
        if tok.type == 'FALSE':
            self.next_token()
            return BoolLiteral(False)
        if tok.type == 'NIL':
            self.next_token()
            return NilLiteral()

        if tok.type == 'LBRACKET':
            self.next_token()
            elems = []
            if self.peek().type != 'RBRACKET':
                elems.append(self.parse_expression())
                while self.peek().type == ',':
                    self.next_token()
                    elems.append(self.parse_expression())
            self.expect('RBRACKET')
            return ListLiteral(elems)

        if tok.type == 'LPAREN':
            self.next_token()
            expr = self.parse_expression()
            self.expect('RPAREN')
            return self.parse_postfix(expr)

        if tok.type == 'IDENT' or tok.type in ('LEN', 'PUSH'):
            self.next_token()
            ident = tok.value
            expr = VarExpr(ident)
            return self.parse_postfix(expr)

        if tok.type in ('NOT', 'MINUS'):
            op = self.next_token()
            expr = self.parse_primary()
            return UnaryExpr(op.value, expr)

        raise SyntaxError(f"Unexpected token {tok.type} at line {tok.line}")

    def parse_postfix(self, left):
        while True:
            tok = self.peek()
            if tok.type == 'LBRACKET':
                self.next_token()
                idx = self.parse_expression()
                self.expect('RBRACKET')
                left = IndexExpr(left, idx)
            elif tok.type == 'LPAREN':
                self.next_token()
                args = []
                if self.peek().type != 'RPAREN':
                    args.append(self.parse_expression())
                    while self.peek().type == ',':
                        self.next_token()
                        args.append(self.parse_expression())
                self.expect('RPAREN')
                left = CallExpr(left, args)
            else:
                break
        return left

# ----------------------------------------------------------------------
# 3. Interpreter (UPDATED with read built-ins)
# ----------------------------------------------------------------------

class ReturnException(Exception):
    def __init__(self, value):
        self.value = value

class Function:
    def __init__(self, name, params, body, env):
        self.name = name
        self.params = params
        self.body = body
        self.env = env

    def call(self, args, interpreter):
        if len(args) != len(self.params):
            raise RuntimeError(f"Function {self.name} expects {len(self.params)} args, got {len(args)}")
        new_env = Environment(outer=self.env)
        for i, param in enumerate(self.params):
            new_env.set(param, args[i])
        try:
            interpreter.execute_block(self.body.statements, new_env)
        except ReturnException as ret:
            return ret.value
        return None

class Environment:
    def __init__(self, outer=None):
        self.store = {}
        self.outer = outer

    def get(self, name):
        if name in self.store:
            return self.store[name]
        if self.outer:
            return self.outer.get(name)
        raise RuntimeError(f"Undefined variable '{name}'")

    def set(self, name, value):
        self.store[name] = value

# ---- NEW BUILT-INS FOR INPUT ----
class BuiltinRead:
    def call(self, args):
        if len(args) != 0:
            raise RuntimeError("read() expects 0 arguments")
        try:
            return sys.stdin.readline().rstrip('\n')
        except:
            return None

class BuiltinReadInt:
    def call(self, args):
        if len(args) != 0:
            raise RuntimeError("read_int() expects 0 arguments")
        try:
            line = sys.stdin.readline().rstrip('\n')
            return int(line)
        except ValueError:
            raise RuntimeError("read_int(): expected an integer")
        except:
            return None

class BuiltinReadFloat:
    def call(self, args):
        if len(args) != 0:
            raise RuntimeError("read_float() expects 0 arguments")
        try:
            line = sys.stdin.readline().rstrip('\n')
            return float(line)
        except ValueError:
            raise RuntimeError("read_float(): expected a number")
        except:
            return None

class BuiltinLen:
    def call(self, args):
        if len(args) != 1:
            raise RuntimeError("len() expects exactly 1 argument")
        obj = args[0]
        if isinstance(obj, (str, list)):
            return len(obj)
        raise RuntimeError("len() only works on strings and lists")

class BuiltinPush:
    def call(self, args):
        if len(args) != 2:
            raise RuntimeError("push() expects exactly 2 arguments")
        lst = args[0]
        val = args[1]
        if not isinstance(lst, list):
            raise RuntimeError("push() first argument must be a list")
        lst.append(val)
        return None

class Interpreter:
    def __init__(self):
        self.global_env = Environment()
        self.global_env.set('len', BuiltinLen())
        self.global_env.set('push', BuiltinPush())
        # Register input built-ins
        self.global_env.set('read', BuiltinRead())
        self.global_env.set('read_int', BuiltinReadInt())
        self.global_env.set('read_float', BuiltinReadFloat())

    def interpret(self, program):
        try:
            self.execute_program(program, self.global_env)
        except RuntimeError as e:
            print(f"Runtime Error: {e}")

    def execute_program(self, program, env):
        for stmt in program.statements:
            self.execute(stmt, env)

    def execute(self, stmt, env):
        if isinstance(stmt, LetStmt):
            val = self.evaluate(stmt.expr, env)
            env.set(stmt.name, val)
        elif isinstance(stmt, AssignStmt):
            target = stmt.target
            val = self.evaluate(stmt.expr, env)
            if isinstance(target, VarExpr):
                self.set_var(target.name, val, env)
            elif isinstance(target, IndexExpr):
                obj = self.evaluate(target.obj, env)
                idx = self.evaluate(target.index, env)
                if not isinstance(obj, list):
                    raise RuntimeError("Can only index into lists")
                if not isinstance(idx, int):
                    raise RuntimeError("List index must be integer")
                if idx < 0 or idx >= len(obj):
                    raise RuntimeError(f"Index {idx} out of range")
                obj[idx] = val
            else:
                raise RuntimeError("Invalid assignment target")
        elif isinstance(stmt, FunctionDef):
            func = Function(stmt.name, stmt.params, stmt.body, env)
            env.set(stmt.name, func)
        elif isinstance(stmt, IfStmt):
            cond = self.evaluate(stmt.cond, env)
            if self.is_truthy(cond):
                self.execute_block(stmt.then_block.statements, env)
            elif stmt.else_block:
                self.execute_block(stmt.else_block.statements, env)
        elif isinstance(stmt, WhileStmt):
            while self.is_truthy(self.evaluate(stmt.cond, env)):
                self.execute_block(stmt.body.statements, env)
        elif isinstance(stmt, ReturnStmt):
            val = self.evaluate(stmt.expr, env)
            raise ReturnException(val)
        elif isinstance(stmt, PrintStmt):
            val = self.evaluate(stmt.expr, env)
            print(self.stringify(val), end='')
            print()
        elif isinstance(stmt, Block):
            self.execute_block(stmt.statements, env)
        else:
            self.evaluate(stmt, env)

    def set_var(self, name, value, env):
        if name in env.store:
            env.set(name, value)
            return
        if env.outer:
            self.set_var(name, value, env.outer)
            return
        raise RuntimeError(f"Undefined variable '{name}'")

    def execute_block(self, statements, env):
        for stmt in statements:
            self.execute(stmt, env)

    def evaluate(self, expr, env):
        if isinstance(expr, IntLiteral):
            return expr.value
        if isinstance(expr, FloatLiteral):
            return expr.value
        if isinstance(expr, StringLiteral):
            return expr.value
        if isinstance(expr, CharLiteral):
            return expr.value
        if isinstance(expr, BoolLiteral):
            return expr.value
        if isinstance(expr, NilLiteral):
            return None
        if isinstance(expr, ListLiteral):
            return [self.evaluate(e, env) for e in expr.elements]
        if isinstance(expr, VarExpr):
            return env.get(expr.name)
        if isinstance(expr, UnaryExpr):
            right = self.evaluate(expr.expr, env)
            if expr.op == '-':
                return -right
            if expr.op == '!':
                return not self.is_truthy(right)
        if isinstance(expr, BinaryExpr):
            left = self.evaluate(expr.left, env)
            right = self.evaluate(expr.right, env)
            op = expr.op
            if op == '+': return left + right
            if op == '-': return left - right
            if op == '*': return left * right
            if op == '/':
                if right == 0: raise RuntimeError("Division by zero")
                return left / right
            if op == '%':
                if right == 0: raise RuntimeError("Modulo by zero")
                return left % right
            if op == '==': return left == right
            if op == '!=': return left != right
            if op == '<': return left < right
            if op == '<=': return left <= right
            if op == '>': return left > right
            if op == '>=': return left >= right
            if op == '&&': return self.is_truthy(left) and self.is_truthy(right)
            if op == '||': return self.is_truthy(left) or self.is_truthy(right)
        if isinstance(expr, IndexExpr):
            obj = self.evaluate(expr.obj, env)
            idx = self.evaluate(expr.index, env)
            if isinstance(obj, str):
                if not isinstance(idx, int):
                    raise RuntimeError("String index must be integer")
                if idx < 0 or idx >= len(obj):
                    raise RuntimeError(f"Index {idx} out of range")
                return obj[idx]
            if not isinstance(obj, list):
                raise RuntimeError("Can only index into lists or strings")
            if not isinstance(idx, int):
                raise RuntimeError("Index must be integer")
            if idx < 0 or idx >= len(obj):
                raise RuntimeError(f"Index {idx} out of range")
            return obj[idx]
        if isinstance(expr, CallExpr):
            callee = self.evaluate(expr.callee, env)
            args = [self.evaluate(arg, env) for arg in expr.args]
            # Check built-ins first (including new input ones)
            if isinstance(callee, (BuiltinLen, BuiltinPush, BuiltinRead, BuiltinReadInt, BuiltinReadFloat)):
                return callee.call(args)
            if isinstance(callee, Function):
                return callee.call(args, self)
            raise RuntimeError(f"Can only call functions or built-ins")
        raise RuntimeError(f"Unknown expression type {type(expr)}")

    def is_truthy(self, val):
        if val is None:
            return False
        if isinstance(val, bool):
            return val
        if isinstance(val, (int, float)):
            return val != 0
        if isinstance(val, str):
            return val != ""
        return True

    def stringify(self, val):
        if val is None:
            return "nil"
        if isinstance(val, bool):
            return "true" if val else "false"
        if isinstance(val, str):
            return val
        if isinstance(val, list):
            return "[" + ", ".join(self.stringify(v) for v in val) + "]"
        return str(val)

# ----------------------------------------------------------------------
# 4. Main
# ----------------------------------------------------------------------

def run_source(source):
    lexer = Lexer(source)
    parser = Parser(lexer.tokens)
    ast = parser.parse_program()
    interpreter = Interpreter()
    interpreter.interpret(ast)

if __name__ == "__main__":
    if len(sys.argv) > 1:
        with open(sys.argv[1], 'r') as f:
            run_source(f.read())
    else:
        print("DonGiovanni REPL (type 'exit()' to quit)")
        interpreter = Interpreter()
        while True:
            try:
                line = input(">>> ")
                if line.strip() == "exit()":
                    break
                lexer = Lexer(line + "\n")
                parser = Parser(lexer.tokens)
                ast = parser.parse_program()
                interpreter.interpret(ast)
            except Exception as e:
                print(f"Error: {e}")

Example

Factorial

Shown above.

Hello, World!

print("Hello, World!")

A+B Problem

// A+B: function that adds two numbers and prints the result
fn add(a, b) {
    return a + b;
}

let result = add(5, 7);
print(result);   // 12

// Or directly:
print(add(3.5, 2.7));  // 6.2 (floats work too)

Prime Number Detector

fn is_prime(n) {
    if n < 2 {
        return false;
    }
    let i = 2;
    while i * i <= n {
        if n % i == 0 {
            return false;
        }
        i = i + 1;
    }
    return true;
}

print(is_prime(17));  // true
print(is_prime(18));  // false

// Print all primes up to 30
let x = 2;
while x <= 30 {
    if is_prime(x) {
        print(x);
    }
    x = x + 1;
}
// Output: 2 3 5 7 11 13 17 19 23 29

Greeting

print("What is your name?");
let name = read();
print("Hello, " + name + "!");

See Also

Categories