Init commit

This commit is contained in:
2019-12-08 17:55:49 +01:00
commit 3c7376cca6
15 changed files with 977 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
package interpreter
type Codeline struct {
Linenumber int
Code string
}

View 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)
}
}

View 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
}
}
}

View 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
}

View 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
}

View 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
View 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
View 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
}