🍑PEACH🍑 programming language
emd22 (14)

PEACH - Prototypical language with knack for extensibility and meta programming

Team @mynameisjonas:
@emd22
@ajmd17

PEACH is a modern programming language, handwritten in both Python and itself, completely written from scratch over the past 20 days. Included is the core language, a standard library, and some examples(including TicTacToe!)

In PEACH, all types are objects, and vice versa. Everything is an object, and that includes functions. Objects descend from a parent object and inherit functions and variables from the parent. For example, to create a type, we extend from a parent Object (in this case Type), and define any methods needed within the type.

The core library in PEACH is written in itself, even allowing for methods to be attached with the Object.patch() method at runtime. Types in PEACH are built of Objects and have overloadable functions for operations such as addition, subtraction, multiplication, division, modulus, and compare.

All builtin types such as String, Int, Float, Array, Bool, etc. are all defined completely in PEACH, including all operations that can be done on them. An example of this supercharged extensibility being the call operator can be overloaded, with an example being that when a Number is called, like 5(10), the values are multiplied together, allowing for a more classical math-like syntax in programming.

Example:

let Person: type = Type.extend({
  instance = {
    name: str

    # overload the | operator
    func __bitor__(self, other) {
      return Person.new(self.name + ' ' + other.name);
    }

    # example of lambdas
    to_str: Func = self -> "Person [name: " + self.name + "]"
  }

  # constructor
  func __construct__(self, name) {
    self.name = name;
  }
});

let person_a: Person = Person.new('Bruce');
let person_b: Person = Person.new('Wayne');
let combined: Person = person_a | person_b;
io.write_color(Console.RED, combined); # prints `Person [name: 'Bruce Wayne']` in red

Types are objects and all objects are patchable, meaning you can swap out or add methods dynamically.

Int.patch({
  # overload object being called as a function.
  # arguments are passed in as an array, so
  # you need to splat (*) the arguments, expanding
  # from the first (the first argument would be the `Int` type
  # itself)
  func __call__(self, args) {
    return self.__mul__(*(args.from(1)));
  }
});

let result: int = 10(20);
print(result); # prints 200

Later we plan to rewrite PEACH in C/C++ for better speed, efficiency. Both of us have more experience in C/C++. We plan to keep PEACH code and the standard library similar to how it is today.

Links:
Github Repository
Repl.it link(click here to run)

Documentation:

vars - How to declare and use variables

functions - Writing and calling functions

strings - Strings operations and interpolation

operators - Available operators + Operator overloading

types - Custom types

embedding - Embedding in Python

proto - Extending types using prototypical inheritance

macros - Macros

arrays - Array operations

iterators - Building custom iterator objects

random - Random number generation

modules - Modules

console - Console input and output

files - File reading and writing

Standard Library

Standard Library

Examples

embedding

numbers

strings

mandlebrot

TicTacToe Example

Be sure play the TicTacToe example for yourself!
Start the REPL by running main.py and call tictactoe(); to try it out!

Code:

let ttt: type = Type.extend({
    name = "TicTacToe"
    
    EMPTY = 0
    O_SPOT = 1
    X_SPOT = 2
    
    instance = {
        moves = [0, 0, 0, 0, 0, 0, 0, 0, 0]

        func to_str(self) {
            return "TicToeToe moves: " + self.moves.to_str();
        }
    }
    func __construct__(self){
    }
    
    play = func (self){}
    board_gen = func (self){}
    
    func board_draw_top(self){
    }
    
    func get(self, loc) {
        let strn = loc+1;
        let ch:str = strn.to_str();
        let move = self.moves[loc].to_int();
        if  move == self.O_SPOT {
            ch = 'O';
        }
        elif move == self.X_SPOT {
            ch = 'X';
        }
        return ch;
    }
    
    func clear_board(self) {
        self.moves = [0, 0, 0, 0, 0, 0, 0, 0, 0];
    }
    
    func ai_find_next_spot(self) {
        import "std/time.peach";
        import "std/math/random.peach";
        
        let available_spaces = [];
        let index = 0;
        for move in self.moves {
            if move == 0 {
                available_spaces += index;
            }
            index += 1;
        }
        if available_spaces.len() == 0 {
            self.board_draw();
            print("\n=== TIED! ===\n");
            self.clear_board();
            self.ai_find_next_spot();
            return 0;
        }
        
        let rand = random.range(Time.now().to_int(), available_spaces.len()-1, 0);
        self.moves[available_spaces[rand]] = self.O_SPOT;

        available_spaces = [];
        for move in self.moves {
            if move == 0 {
                available_spaces += index;
            }
            index += 1;
        }
        if available_spaces.len() == 0 {
            self.board_draw();
            print("\n=== TIED! ===\n");
            self.clear_board();
            self.ai_find_next_spot();
            return 0;
        }
    }
    
    func ai_move(self) {
        return self.ai_find_next_spot();
    }
    
    func check_index(self, player, center_index, offset, check_offset) {
        let index = 0;
        
        while index != 3 {
            if self.moves[center_index-check_offset] == player {
                if self.moves[center_index+check_offset] == player {
                    if self.moves[center_index] == player {
                        return player;                    
                    }
                }
            }
            center_index += offset;

            index += 1;
        }
        return self.EMPTY;
    }
    
    func check_diag_corner(self, player, top, bottom) {
        if self.moves[top] == player {
            if self.moves[bottom] == player {
                if self.moves[4] == player {
                    return player;
                }
            }
        }
        return self.EMPTY;
    }
    
    func check_diagonal(self, player) {

        let tl = self.check_diag_corner(player, 0, 8);
        let bl = self.check_diag_corner(player, 6, 2);
        if tl {
            return tl;
        }
        if bl {
            return bl;
        }
        return self.EMPTY;
    }
    
    func print_winner(self, player) {
        if player == self.O_SPOT {
            self.board_draw();
            print("\n=== YOU LOSE! ===\n"); 
        }
        elif player == self.X_SPOT {
            self.board_draw();
            print("\n=== YOU WIN! ===\n");
        }
    }
    
    func check_winner(self, player) {
        # start at left side in middle of board, checking above and below, and move by 1 spot.
        let colcheck = self.check_index(player, 3, 1, 3);
        # start at top in middle of board, checking left and right and moving down by board width
        let rowcheck = self.check_index(player, 1, 3, 1);

        let diagcheck = self.check_diagonal(player);
        
        # check if win in columns
        if colcheck {
            self.print_winner(player);
            self.clear_board();
            return true;
        }
        # check if win in rows
        elif rowcheck {
            self.print_winner(player);
            self.clear_board();
            return true;
        }
        # check if win in diagonal spaces
        elif diagcheck {
            self.print_winner(player);
            self.clear_board();
            return true;
        }

        return false;
    }
    func player_write(self, player_char) {
        if player_char == 'O' {
            io.write_color(Console.RED, player_char);
        }
        elif player_char == 'X' {
            io.write_color(Console.YELLOW, player_char);
        }
        else {
            io.write(player_char);
        }
    }

    func board_draw_row(self, lc) {
        let v0 = self.get(lc);
        let v1 = self.get(lc + 1);
        let v2 = self.get(lc + 2);

        # print column 1
        io.write("| "); self.player_write(v0);
        # column 2
        io.write(" | "); self.player_write(v1);
        # column 3
        io.write(" | "); self.player_write(v2);
        # print end
        io.write(" |\n");

        print("+---+---+---+");
    }
    
    func board_draw(self) {
        let index = 0;
        print("+---+---+---+");
        while index != 9 {
            self.board_draw_row(index);
            index += 3;
        }
    }
});

ttt.play = func (self) {
    let command = "";
    
    print("=== TICTACTOE ===");
    print("Input 'q' to exit");
    print("Input a number play a spot");
    self.board_draw();
    
    let int_cmd = 0;
    
    while command != "q" {
        command = Console.read();
        if command == 'q' {
            return 0;
        }
        int_cmd = command.to_int()-1;
        if self.moves[int_cmd] == self.EMPTY {
            self.moves[int_cmd] = self.X_SPOT;            
            self.check_winner(self.X_SPOT);
            self.ai_move();
            self.check_winner(self.O_SPOT);
            self.board_draw();
        }
        else {
            print("Space already taken!");
            self.board_draw();
        }
    }
};

Future

using our macro syntax (check the doc for more info), we have been able to prototype more of the extensibility features we’d envisioned originally. This includes building custom control structures such as a custom match or switch-like block.

With the macro feature:

macro match(expr, block) {
  let cases = [];

  let _ = Type.extend({
    func __lt__(self, other) {
      return ['<', other];
    }

    func __lte__(self, other) {
      return ['<=', other];
    }

    func __gt__(self, other) {
      return ['>', other];
    }

    func __gte__(self, other) {
      return ['>=', other];
    }

    func __eql__(self, other) {
      return ['==', other];
    }

    func __noteql__(self, other) {
      return ['!=', other];
    }

  });

  macro when(ary: Array, block) {
    let tok: str = ary[0];
    let val = ary[1];
    cases.append([tok, val, block]);
  }

  # yield execution to the given block
  block();

  mixin {
    for ary in cases {
      let tok = ary[0][0];
      let val = ary[0][1];
      let blk = ary[1];

      let cond: bool = false;

      if tok == '<' {
        cond = expr < val;
      } elif tok == '<=' {
        cond = expr <= val;
      } elif tok == '>' {
        cond = expr > val;
      } elif tok == '>=' {
        cond = expr >= val;
      } elif tok == '==' {
        cond = expr == val;
      } elif tok == '!=' {
        cond = expr != val;
      }

      if cond {
        # condition satisfied, pass execution to block
        blk();
      }
    }
  }
}
# future block syntax, passed as last param like ruby

let favorite_number = 5;
match(favorite_number) {
  when(_ < 5) {
    print("less than 5");
  }

  when(_ == 5) {
    print("equal to 5");
  }
}

To start the PEACH repl:

Click HERE

You are viewing a single comment. View All
emd22 (14)

@SeamusDonahue to create a modular/customizable language, having types internally defined, etc.
We did this all during the 10 days of the jam so it's not as polished as say a year old language, but it works fairly well.