mirror of
https://github.com/maride/mexico.git
synced 2026-04-24 07:15:46 +00:00
Init commit
This commit is contained in:
6
mexigo/interpreter/codeline.go
Normal file
6
mexigo/interpreter/codeline.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package interpreter
|
||||
|
||||
type Codeline struct {
|
||||
Linenumber int
|
||||
Code string
|
||||
}
|
||||
20
mexigo/interpreter/debug.go
Normal file
20
mexigo/interpreter/debug.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package interpreter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
func (t *Tape) DebugPrintTape() {
|
||||
log.Printf("Tape is currently %d cells big", len(t.cells))
|
||||
for i, v := range t.cells {
|
||||
fmt.Printf("Cell %d: %d (%c)\n", i, v, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (s * Stack) DebugPrintStack() {
|
||||
log.Printf("Stack is currently %d entries big", len(s.values))
|
||||
for i, v := range s.values {
|
||||
fmt.Printf("Stack row %d: %d (%c)\n", i, v, v)
|
||||
}
|
||||
}
|
||||
89
mexigo/interpreter/interpreter.go
Normal file
89
mexigo/interpreter/interpreter.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package interpreter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Interpreter struct {
|
||||
machine Machine
|
||||
program []Codeline
|
||||
programCounter int
|
||||
programPointer int
|
||||
}
|
||||
|
||||
// Feeds a new mexico machine with given code.
|
||||
func Run(commands []Codeline) error {
|
||||
var i Interpreter
|
||||
|
||||
// Set the given code as commands for the interpreter
|
||||
setCmdErr := i.SetCommands(commands)
|
||||
if setCmdErr != nil {
|
||||
return setCmdErr
|
||||
}
|
||||
|
||||
// Let's run this program :)
|
||||
return i.Run()
|
||||
}
|
||||
|
||||
// Sets the given array as new program for the interpreter
|
||||
func (i *Interpreter) SetCommands(commands []Codeline) error {
|
||||
i.program = commands
|
||||
i.programCounter = 0
|
||||
return i.GoToNextCommand()
|
||||
}
|
||||
|
||||
// Searches for the next command, starting from the current value of the programCounter.
|
||||
// This may sound odd, because in most other architectures, this is just programCounter++, and there would be no need
|
||||
// for a function like this. However, mexico has a BASIC-style program line numbering, means we need to search for the
|
||||
// next line number containing code, because there may be one or more empty lines between the current and the next line.
|
||||
// This is exactly what GoToNextCommand() does.
|
||||
// If there is no next command, most likely because we reached the end of the program, an error is thrown.
|
||||
func (i *Interpreter) GoToNextCommand() error {
|
||||
// Iterate over all lines to find the first which has a greater line number than the current one
|
||||
for index, line := range i.program {
|
||||
if line.Linenumber >= i.programCounter {
|
||||
// Found, set and return
|
||||
i.programCounter = line.Linenumber
|
||||
i.programPointer = index
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// No next command found. Throw error.
|
||||
return errors.New(fmt.Sprintf("Found no commands after line %d. Stopping.", i.programCounter))
|
||||
}
|
||||
|
||||
// Runs the commands, unless an error is encountered, then it doesn't run the commands.
|
||||
func (i *Interpreter) Run() error {
|
||||
defer i.machine.Tape.DebugPrintTape()
|
||||
defer i.machine.Stack.DebugPrintStack()
|
||||
|
||||
for {
|
||||
// Get current command
|
||||
cmd := i.program[i.programPointer]
|
||||
|
||||
// Run command in the machine
|
||||
jumpLine, doJump, runErr := i.machine.RunCommand(cmd.Code)
|
||||
if runErr != nil {
|
||||
// Encountered an error during runtime, stop execution
|
||||
return runErr
|
||||
}
|
||||
|
||||
// Check if we should jump anywhere else than to the next code line
|
||||
if doJump {
|
||||
// Yes, do it then.
|
||||
i.programCounter = jumpLine
|
||||
} else {
|
||||
// We are not asked to jump anywhere, move on to the next code line then.
|
||||
i.programCounter++
|
||||
}
|
||||
|
||||
// Skip empty lines if there are any.
|
||||
nextCmdErr := i.GoToNextCommand()
|
||||
if nextCmdErr != nil {
|
||||
// Encountered error finding the next command, throw it
|
||||
return nextCmdErr
|
||||
}
|
||||
}
|
||||
}
|
||||
160
mexigo/interpreter/machine.go
Normal file
160
mexigo/interpreter/machine.go
Normal file
@@ -0,0 +1,160 @@
|
||||
package interpreter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Machine struct {
|
||||
Stack Stack
|
||||
Tape Tape
|
||||
}
|
||||
|
||||
// Runs the given command.
|
||||
// Returns the next line (comparable to the 'Program Counter') to be executed, but just if doJump is true.
|
||||
// May also return an error. It's advised to stop the execution of further commands if this command throws an error.
|
||||
func (m *Machine) RunCommand(cmd string) (jumpLine int, doJump bool, execErr error) {
|
||||
// Let's check which command we are told to run.
|
||||
if cmd == "left" {
|
||||
// Moves the tape head one cell to the left
|
||||
m.Tape.MoveLeft()
|
||||
} else if cmd == "right" {
|
||||
// Moves the tape head one cell to the right
|
||||
m.Tape.MoveRight()
|
||||
} else if cmd == "pusht" {
|
||||
// Reads the current cell value and pushes it on top of the stack
|
||||
m.Stack.Push(m.Tape.Get())
|
||||
} else if strings.HasPrefix(cmd, "push ") {
|
||||
// Pushes the value n to the stack
|
||||
|
||||
// Cut "push " away and trim
|
||||
strVal := strings.Trim(cmd[5:], " ")
|
||||
|
||||
// Convert to integer
|
||||
intVal, atoiErr := strconv.Atoi(strVal)
|
||||
if atoiErr != nil {
|
||||
// Conversion failed.
|
||||
execErr = errors.New(fmt.Sprintf("Tried to push non-integer value '%s' to the stack. %s", strVal, atoiErr.Error()))
|
||||
return
|
||||
} else {
|
||||
// Conversion successful, push constant
|
||||
m.Stack.Push(intVal)
|
||||
}
|
||||
} else if cmd == "pop" {
|
||||
// Pops top stack value to the current cell
|
||||
m.Tape.Set(m.Stack.Pop())
|
||||
} else if cmd == "dup" {
|
||||
// Duplicates the topmost stack value
|
||||
val := m.Stack.Pop()
|
||||
m.Stack.Push(val)
|
||||
m.Stack.Push(val)
|
||||
} else if cmd == "del" {
|
||||
// Deletes the topmost stack value, ignoring its value
|
||||
m.Stack.Pop()
|
||||
} else if cmd == "eq" {
|
||||
// Checks if stack[0] == stack[1]. Pushes 1 to the stack if equal, 0 otherwise
|
||||
stack0 := m.Stack.Pop()
|
||||
stack1 := m.Stack.Pop()
|
||||
|
||||
if stack0 == stack1 {
|
||||
m.Stack.Push(1)
|
||||
} else {
|
||||
m.Stack.Push(0)
|
||||
}
|
||||
} else if cmd == "not" {
|
||||
// Inverses stack[0]
|
||||
stack0 := m.Stack.Pop()
|
||||
|
||||
if stack0 == 0 {
|
||||
m.Stack.Push(1)
|
||||
} else if stack0 == 1 {
|
||||
m.Stack.Push(0)
|
||||
} else {
|
||||
// Not a binary number, not going to inverse it.
|
||||
execErr = errors.New(fmt.Sprintf("Tried to inverse non-binary integer value '%d'", stack0))
|
||||
return
|
||||
}
|
||||
} else if cmd == "gt" {
|
||||
// Checks if stack[0] > stack[1]. Pushes 1 to the stack if greater, 0 otherwise
|
||||
stack0 := m.Stack.Pop()
|
||||
stack1 := m.Stack.Pop()
|
||||
|
||||
if stack0 > stack1 {
|
||||
m.Stack.Push(1)
|
||||
} else {
|
||||
m.Stack.Push(0)
|
||||
}
|
||||
} else if cmd == "lt" {
|
||||
// Checks if stack[0] < stack[1]. Pushes 1 to the stack if greater, 0 otherwise
|
||||
stack0 := m.Stack.Pop()
|
||||
stack1 := m.Stack.Pop()
|
||||
|
||||
if stack0 < stack1 {
|
||||
m.Stack.Push(1)
|
||||
} else {
|
||||
m.Stack.Push(0)
|
||||
}
|
||||
} else if cmd == "add" {
|
||||
// Calculates stack[0] + stack[1], and pushes the result to the stack
|
||||
m.Stack.Push(m.Stack.Pop() + m.Stack.Pop())
|
||||
} else if cmd == "sub" {
|
||||
// Calculates stack[0] - stack[1], and pushes the result to the stack
|
||||
m.Stack.Push(m.Stack.Pop() - m.Stack.Pop())
|
||||
} else if cmd == "mult" {
|
||||
// Calculates stack[0] * stack[1], and pushes the result to the stack
|
||||
m.Stack.Push(m.Stack.Pop() * m.Stack.Pop())
|
||||
} else if cmd == "div" {
|
||||
// Calculates stack[0] / stack[1], and pushes the result to the stack
|
||||
m.Stack.Push(m.Stack.Pop() / m.Stack.Pop())
|
||||
} else if cmd == "mod" {
|
||||
// Calculates stack[0] % stack[1], and pushes the result to the stack
|
||||
m.Stack.Push(m.Stack.Pop() % m.Stack.Pop())
|
||||
} else if cmd == "read" {
|
||||
// Reads a character from the user, and pushes its char value to the stack
|
||||
var readChar []byte
|
||||
|
||||
// Read character(s)
|
||||
numChars, readErr := os.Stdin.Read(readChar)
|
||||
if readErr != nil {
|
||||
// Failed to read from stdin.
|
||||
execErr = errors.New(fmt.Sprintf("Failed to read character from stdin: %s", readErr.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if numChars < 1 || readChar == nil {
|
||||
// Didn't even read a single character.
|
||||
execErr = errors.New("Failed to read character from stdin.")
|
||||
return
|
||||
}
|
||||
|
||||
// Push character to stack
|
||||
m.Stack.Push(int(readChar[0]))
|
||||
|
||||
// Check if user typed more than asked for
|
||||
if numChars > 1 {
|
||||
// Ugh, what a spammer
|
||||
execErr = errors.New("Read more than one character - ignoring all but the first character.")
|
||||
return
|
||||
}
|
||||
} else if cmd == "print" {
|
||||
// Prints stack[0] as a character
|
||||
val := m.Stack.Pop()
|
||||
fmt.Printf("%q (%d)\n", rune(val), val)
|
||||
} else if cmd == "jmp" {
|
||||
// Jumps to the line number specified by stack[0]
|
||||
jumpLine = m.Stack.Pop()
|
||||
doJump = true
|
||||
} else if cmd == "jmpc" {
|
||||
// Jumps to the line number specified by stack[0], if stack[1] is not 0.
|
||||
jumpLine = m.Stack.Pop()
|
||||
doJump = m.Stack.Pop() != 0
|
||||
} else {
|
||||
// ... no such command.
|
||||
execErr = errors.New(fmt.Sprintf("Command not found: %s", cmd))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
27
mexigo/interpreter/stack.go
Normal file
27
mexigo/interpreter/stack.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package interpreter
|
||||
|
||||
import "log"
|
||||
|
||||
type Stack struct {
|
||||
values []int
|
||||
}
|
||||
|
||||
// Pushes the given value to the stack
|
||||
func (s *Stack) Push(val int) {
|
||||
s.values = append(s.values, val)
|
||||
}
|
||||
|
||||
// Pops the top element from the stack and return its value
|
||||
func (s *Stack) Pop() int {
|
||||
// Check if the stack contains at least one element
|
||||
if len(s.values) > 0 {
|
||||
// There's at least one element, pop it: get value and delete element
|
||||
val := s.values[len(s.values)-1]
|
||||
s.values = s.values[:len(s.values)-1]
|
||||
return val
|
||||
}
|
||||
|
||||
// Stack is empty, but we should pop... Damn.
|
||||
log.Panic("Tried to pop value from empty stack.")
|
||||
return 0
|
||||
}
|
||||
51
mexigo/interpreter/tape.go
Normal file
51
mexigo/interpreter/tape.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package interpreter
|
||||
|
||||
type Tape struct {
|
||||
head uint
|
||||
cells []int
|
||||
}
|
||||
|
||||
// Sets the head to point to the specified position
|
||||
func (t *Tape) SetHead(pos uint) {
|
||||
t.GrowUpTo(pos)
|
||||
t.head = pos
|
||||
}
|
||||
|
||||
// Resizes the tape to the given size.
|
||||
// If the cell array is already bigger than the specified size, nothing happens.
|
||||
// If the cell array is smaller than the specified size, it's resized to the given size and filled with the value 0.
|
||||
func (t *Tape) GrowUpTo(size uint) {
|
||||
if uint(len(t.cells)) > size {
|
||||
// Tape is already bigger. Do nothing.
|
||||
return
|
||||
}
|
||||
|
||||
// Append as many zeroes as required.
|
||||
for uint(len(t.cells)) <= size {
|
||||
t.cells = append(t.cells, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Moves the tape head left
|
||||
func (t *Tape) MoveLeft() {
|
||||
if t.head > 0 {
|
||||
t.SetHead(t.head - 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Moves the tape head left
|
||||
func (t *Tape) MoveRight() {
|
||||
t.SetHead(t.head + 1)
|
||||
}
|
||||
|
||||
// Returns the current cell value
|
||||
func (t *Tape) Get() int {
|
||||
t.GrowUpTo(t.head)
|
||||
return t.cells[t.head]
|
||||
}
|
||||
|
||||
// Sets the current cell to the new value
|
||||
func (t *Tape) Set(newVal int) {
|
||||
t.GrowUpTo(t.head)
|
||||
t.cells[t.head] = newVal
|
||||
}
|
||||
48
mexigo/main.go
Normal file
48
mexigo/main.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"github.com/maride/mexico/mexigo/interpreter"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Important things first
|
||||
printBanner()
|
||||
|
||||
// Get desired domain off arguments
|
||||
flag.Parse()
|
||||
domain := flag.Arg(0)
|
||||
if domain == "" {
|
||||
// No domain entered.
|
||||
log.Println("Please specify a domain to receive code from as first argument, like this: ./mexigo <domain>")
|
||||
return
|
||||
}
|
||||
|
||||
// Get program code from that domain
|
||||
log.Printf("Resolving %s for MX records", domain)
|
||||
code := LookupMX(domain)
|
||||
if len(code) == 0 {
|
||||
// Failed to look up mexico code on that domain. Log and exit.
|
||||
log.Printf("No code found on domain '%s'. Exiting.", domain)
|
||||
return
|
||||
}
|
||||
|
||||
// Inform user about successful resolving
|
||||
log.Printf("Found %d code lines, interpreting them...", len(code))
|
||||
|
||||
// Set up interpreter
|
||||
runErr := interpreter.Run(code)
|
||||
if runErr != nil {
|
||||
// Encountered error while executing code. Log and exit.
|
||||
log.Println(runErr.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Prints a small banner :)
|
||||
func printBanner() {
|
||||
println("mexigo - the reference interpreter for the mexico esolang!")
|
||||
println("See github.com/maride/mexico for further information.")
|
||||
println()
|
||||
}
|
||||
71
mexigo/resolver.go
Normal file
71
mexigo/resolver.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/maride/mexico/mexigo/interpreter"
|
||||
"log"
|
||||
"math"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// The fake base domain which classifies a domain name as a mexico command, rather than a "normal" domain name
|
||||
MexicoFakeDomain = "mexico.invalid."
|
||||
)
|
||||
|
||||
// This is a wrapper function for net.LookupMX(), filtering for mexico records, and sorting the remaining by linenum
|
||||
func LookupMX(basedomain string) []interpreter.Codeline {
|
||||
// Do the basic lookup
|
||||
rawMX, lookupErr := net.LookupMX(basedomain)
|
||||
if lookupErr != nil {
|
||||
// Encountered error while looking up basedomain - log and return
|
||||
log.Printf("Failed to resolve '%s': %s", basedomain, lookupErr.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Filter results for the (fake) domain "mexico.invalid."
|
||||
var filteredMX []*net.MX
|
||||
|
||||
// Iterate over all returned MX records
|
||||
for _, raw := range rawMX {
|
||||
// Check if it's a mexico MX record
|
||||
if strings.HasSuffix(raw.Host, MexicoFakeDomain) {
|
||||
// it is, add to filtered array
|
||||
filteredMX = append(filteredMX, raw)
|
||||
}
|
||||
}
|
||||
|
||||
// Sort filtered results, based on the priority - or line number, in the words of this esolang :)
|
||||
var records []interpreter.Codeline
|
||||
var smallestPriority uint16 = math.MaxInt16
|
||||
smallestPriorityIndex := 0
|
||||
|
||||
// Iterate over filteredMX and delete the record with the smallest priority until we don't have any more filteredMX
|
||||
for len(filteredMX) > 0 {
|
||||
// Iterate over the filteredMX to find the one with the smallest priority
|
||||
for i, f := range filteredMX {
|
||||
if f.Pref < smallestPriority {
|
||||
// Found entry with smaller index than the current one
|
||||
smallestPriority = f.Pref
|
||||
smallestPriorityIndex = i
|
||||
}
|
||||
}
|
||||
|
||||
// Remove mexico fake domain suffix
|
||||
command := strings.TrimSuffix(filteredMX[smallestPriorityIndex].Host, "." + MexicoFakeDomain)
|
||||
|
||||
// Replace '-' with space.
|
||||
// This reserves the process done by the compiler to transform this command + arg into a FQDN
|
||||
command = strings.Replace(command, "-", " ", 1)
|
||||
|
||||
// Add hostname of "smallest" record to the records array, and delete it from filteredMX
|
||||
records = append(records, interpreter.Codeline{
|
||||
Linenumber: int(filteredMX[smallestPriorityIndex].Pref),
|
||||
Code: command,
|
||||
})
|
||||
filteredMX = append(filteredMX[:smallestPriorityIndex], filteredMX[smallestPriorityIndex + 1:]...)
|
||||
}
|
||||
|
||||
// Return filtered and sorted records
|
||||
return records
|
||||
}
|
||||
Reference in New Issue
Block a user