Width
The interpreter is still a work in progress (I have several unused command slots still). The repo, with more documentation, can be found here.
Dennis added Width to TIO less than a minute ago: Try It Online!
Width is an esoteric stack-based language I developed recently based on ideas I first dumped in this question. It is based entirely on how wide, more or less, a letter is in a "regular" font. The only characters that do anything are letters, uppercase and lowercase. All other characters are ignored. I split letters up into 10 different width categories, which form the 10 different actions possible in Width:
0: i j l # do while counter > 0
1: f r t I # end
2: c k s v x y z J # 0 in commands
3: a b d e g h n o p q u L # separator (no-op)
4: F T Z # push base 10 number, using left side row titles (width numbers); terminated with original char
5: A B E K P S V X Y # 1 in commands
6: w C D H N R U # 2 in commands
7: G O Q # push string literal; sets of 2 width numbers equate to index in printable ASCII; terminated with original char
8: m M # if top of stack
9: W # else
2
, 5
, and 6
provide access to the commands, most of which interact with the stack. More information can be found at the info.txt
page in the Github repo.
This is the Python code of the interpreter. I still have several commands to add, and I'm considering how to work with error-handling, but otherwise it should be complete. (I'm also going to add a flag at some point to allow testing with a more abstract syntax, because otherwise this language is a giant pain to work with)
import sys
import math
import numbers
import functools
try:
file = sys.argv[1]
except IndexError:
file = "source.wide"
with open(file) as f:
source = f.read()
translation = ("ijl", "frtI", "cksvxyzJ", "abdeghnopquL", "FTZ", "ABEKPSVXY", "wCDHNRU", "GOQ", "mM", "W")
chars = "".join(sorted("".join(translation)))
strings = """ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~\n\t"""
def trans(letter):
for each in translation:
if letter in each:
return translation.index(each)
COMMAND = "COMMAND"
COMMANDS = (2, 5, 6)
NUMBER = "NUMBER"
STRING = "STRING"
DO = "DO"
IF = "IF"
ELSE = "ELSE"
END = "END"
SEPARATOR = "SEPARATOR"
class Token:
def __init__(self, val, type_):
self.val = val
self.type = type_
class Lexer:
def __init__(self, src):
self.src = src
self.pos = 0
self.char = self.src[self.pos]
def read(self):
if self.char is None:
return None
command = trans(self.char)
if command == 0:
self.advance()
return Token(0, DO)
elif command == 1:
self.advance()
return Token(1, END)
elif command == 3:
self.advance()
return Token(3, SEPARATOR)
elif command == 4:
return self.read_num()
elif command == 7:
return self.read_str()
elif command == 8:
self.advance()
return Token(8, IF)
elif command == 9:
self.advance()
return Token(9, ELSE)
else:
return self.read_cmd()
def advance(self):
self.pos += 1
try:
self.char = self.src[self.pos]
except IndexError:
self.char = None
def read_num(self):
delim = self.char
self.advance()
res = ""
while self.char is not None and self.char != delim:
res += str(trans(self.char))
self.advance()
self.advance()
return Token(int(res), NUMBER)
def read_str(self):
def read_char():
res_ = str(trans(self.char))
self.advance()
res_ += str(trans(self.char))
self.advance()
try:
return strings[int(res_)]
except IndexError:
return " "
delim = self.char
self.advance()
res = ""
while self.char is not None and self.char != delim:
res += read_char()
self.advance()
return Token(res, STRING)
def read_cmd(self):
command = ""
while self.char is not None and trans(self.char) in COMMANDS and len(command) <= 4:
command += str(COMMANDS.index(trans(self.char)))
self.advance()
return Token(command, COMMAND)
source = "".join(filter(lambda c: c in chars, source))
stack = []
backburner = []
counter = 0
def set_counter(val):
global counter
counter = int(val)
if counter < 0:
counter = 0
def set_stack(val):
global stack
stack = val
def num_input():
inp = input()
try:
stack.append(int(inp))
except ValueError:
try:
stack.append(float(inp))
except ValueError:
pass
def flip_ends():
if len(stack) > 1:
stack[0], stack[-1] = stack[-1], stack[0]
def clear_stack():
global stack
stack = []
def reload_stack(is_top):
global backburner, stack
if is_top:
stack.extend(backburner)
else:
stack = backburner + stack
backburner = []
# https://stackoverflow.com/a/15285588/7605753
def is_prime(n):
if n == 2 or n == 3:
return True
if n < 2 or n % 2 == 0:
return False
if n < 9:
return True
if n % 3 == 0:
return False
r = int(math.sqrt(n))
_f = 5
while _f <= r:
if n % _f == 0:
return False
if n % (_f + 2) == 0:
return False
_f += 6
return True
def error():
raise Exception
commands = {
"0": lambda: stack.append(stack[-1]),
"1": lambda: stack.append(stack.pop(-2)),
"2": lambda: stack.pop(),
"00": lambda: set_counter(stack[-1]),
"01": lambda: stack.append(len(stack)),
"02": lambda: stack.append(input()),
"10": num_input,
"11": lambda: stack.append(str(stack.pop())),
"12": lambda: stack.append(int(stack.pop())),
"20": lambda: set_counter(counter + 1),
"21": lambda: set_counter(counter - 1),
"22": lambda: print(stack.pop()),
"000": lambda: stack.append(float(stack.pop())),
"001": lambda: stack.append(-stack.pop()),
"002": lambda: stack.append(not stack.pop()),
"010": lambda: stack.append(stack.pop(-2) + stack.pop()),
"011": lambda: stack.append(stack.pop(-2) - stack.pop()),
"012": lambda: stack.append(stack.pop(-2) / stack.pop()),
"020": lambda: stack.append(stack.pop(-2) // stack.pop()),
"021": lambda: stack.append(stack.pop(-2) * stack.pop()),
"022": lambda: stack.append(stack.pop(-2) % stack.pop()),
"100": lambda: stack.append(math.factorial(stack.pop())),
"101": lambda: stack.append(str(stack.pop(-2)) + str(stack.pop())),
"102": lambda: stack.append(math.pow(stack.pop(-2), stack.pop())),
"110": lambda: stack.append(math.sqrt(stack.pop())),
"111": lambda: stack.append(math.log(stack.pop(-2), stack.pop())),
"112": lambda: stack.append(~stack.pop()),
"120": lambda: stack.append(stack.pop(-2) | stack.pop()),
"121": lambda: stack.append(stack.pop(-2) & stack.pop()),
"122": lambda: stack.append(stack.pop(-2) << stack.pop()),
"200": lambda: stack.append(stack.pop(-2) >> stack.pop()),
"201": lambda: stack.append(stack.pop(-2)[stack.pop()]),
"202": lambda: stack.append(str(stack.pop(-2)) * stack.pop()),
"210": lambda: stack.append(counter),
"211": lambda: set_counter(stack.pop()),
"212": lambda: stack.extend(list(str(stack.pop()))),
"220": flip_ends,
"221": lambda: stack.append(len(stack[-1])),
"222": lambda: print(stack[-1]),
"0000": lambda: stack.reverse(),
"0001": lambda: stack.sort(),
"0002": lambda: stack.append(stack[counter]),
"0010": lambda: stack.append(stack[stack.pop()]),
"0011": 0,
"0012": 0,
"0020": lambda: stack.append(sum(n for n in stack if isinstance(n, numbers.Number))),
"0021": lambda: stack.append(functools.reduce(lambda x, y: x*y, [n for n in stack if isinstance(n, numbers.Number)], 1)),
"0022": 0,
"0100": lambda: (backburner.extend(stack), clear_stack()),
"0101": lambda: reload_stack(True),
"0102": lambda: reload_stack(False),
"0110": lambda: backburner.append(stack.pop()),
"0111": lambda: backburner.append(list(stack.pop())),
"0112": lambda: stack.pop().split(stack.pop()),
"0120": lambda: stack.append(backburner[-1]),
"0121": lambda: (lambda depth=stack.pop(): (set_stack(stack[-depth:]), backburner.append(stack[:depth])))(),
"0122": lambda: (lambda depth=stack.pop(): (set_stack(stack[:-depth]), backburner.append(stack[depth:])))(),
"0200": lambda: set_stack([stack.pop().join(stack)]),
"0201": lambda: set_stack(["".join(stack)]),
"0202": lambda: (lambda depth=stack.pop(-2): set_stack(stack[-depth:] + [stack.pop().join(stack[:depth])]))(),
"0210": lambda: (lambda depth=stack.pop(): set_stack(stack[-depth:] + ["".join(stack[:depth])]))(),
"0211": 0,
"0212": 0,
"0220": lambda: stack.append(stack.pop().split(stack.pop())),
"0221": lambda: stack.append(stack.pop().split(", ")),
"0222": 0,
"1000": lambda: stack.append(is_prime(stack[-1])),
"1001": lambda: stack.append((lambda s=stack.pop(): s == s[::-1])()),
"1002": lambda: stack.append(1 / stack.pop()),
"1010": lambda: stack.append(stack.pop() - 1),
"1011": lambda: stack.append(stack.pop() + 1),
"1012": lambda: stack.append(stack.pop() * 2),
"1020": lambda: stack.append(stack.pop() / 2),
"1021": lambda: stack.append(stack.pop() ** 2),
"1022": lambda: float("." + str(stack.pop())),
"1100": lambda: stack.append(stack.pop() == stack.pop()),
"1101": lambda: stack.append(stack.pop() != stack.pop()),
"1102": lambda: stack.append(stack.pop() > stack.pop()),
"1110": lambda: stack.append(stack.pop() < stack.pop()),
"1111": lambda: stack.append(stack.pop() >= stack.pop()),
"1112": lambda: stack.append(stack.pop() <= stack.pop()),
"1120": lambda: stack.append(stack.pop() in stack),
"1121": lambda: stack.append(stack.pop() in backburner),
"1122": lambda: stack.append(stack.pop() == counter),
"1200": lambda: stack.append(stack.pop() in stack.pop()),
"1201": lambda: stack.append(stack.pop(-2).find(stack.pop())),
"1202": 0,
"1210": 0,
"1211": 0,
"1212": lambda: stack.append(stack.pop().lower()),
"1220": lambda: stack.append(stack.pop().upper()),
"1221": lambda: stack.append(ord(stack.pop())),
"1222": lambda: stack.append(chr(stack.pop())),
"2000": lambda: stack.append(math.floor(stack.pop())),
"2001": lambda: stack.append(math.ceil(stack.pop())),
"2002": lambda: stack.append(round(stack.pop())),
"2010": lambda: stack.append(abs(stack.pop())),
"2011": 0,
"2012": 0,
"2020": lambda: stack.append(len(stack.pop())),
"2021": 0,
"2022": 0,
"2100": lambda: stack.append(min(stack)),
"2101": lambda: stack.append(max(stack)),
"2102": lambda: stack.append(stack.count(stack.pop())),
"2110": lambda: stack.append(sum(stack) / len(stack)),
"2111": 0,
"2112": 0,
"2120": 0,
"2121": 0,
"2122": 0,
"2200": lambda: stack.append(stack.pop(-3).replace(stack.pop(-2), stack.pop())),
"2201": lambda: stack.append(stack.pop(-3).replace(stack.pop(-2), stack.pop(), 1)),
"2202": lambda: stack.append(stack.pop(-2).replace(stack.pop(), "")),
"2210": lambda: stack.append(stack.pop(-2).replace(stack.pop(), "", 1)),
"2211": 0,
"2212": lambda: stack.append(eval(stack.pop())),
"2220": lambda: stack.append(eval(input())),
"2221": lambda: print(stack[-1]),
"2222": lambda: error()
}
def run_cmd(name):
global stack, counter, backburner
state = {
"stack": list(stack),
"counter": counter,
"backburner": list(backburner)
}
# TODO: unknown command
try:
commands[name]()
except IndexError:
stack = state["stack"]
counter = state["counter"]
backburner = state["backburner"]
class AST:
pass
class Main(AST):
def __init__(self):
self.nodes = []
class Do(AST):
def __init__(self, node):
self.node = node
class If(AST):
def __init__(self, first, second):
self.first = first
self.second = second
class Literal(AST):
def __init__(self, val):
self.val = val
class Command(AST):
def __init__(self, val):
self.val = val
class Parser:
def __init__(self, lexer):
self.lexer = lexer
self.token = None
def parse(self):
pgm = Main()
self.token = self.lexer.read()
while self.token is not None and self.token.type != END and self.token.type != ELSE:
if self.token.type == DO:
pgm.nodes.append(Do(self.parse()))
elif self.token.type == NUMBER or self.token.type == STRING:
pgm.nodes.append(Literal(self.token.val))
elif self.token.type == IF:
first = self.parse()
if self.token.type == ELSE:
second = self.parse()
else:
second = None
pgm.nodes.append(If(first, second))
elif self.token.type == COMMAND:
pgm.nodes.append(Command(self.token.val))
self.token = self.lexer.read()
return pgm
class Interpreter:
def __init__(self, tree):
self.tree = tree
def visit(self, node):
method_name = "visit_" + type(node).__name__
visitor = getattr(self, method_name.lower())
return visitor(node)
def interpret(self):
self.visit(self.tree)
def visit_main(self, node):
for each in node.nodes:
self.visit(each)
def visit_do(self, node):
while counter:
self.visit(node)
def visit_if(self, node):
if stack[-1]:
self.visit(node.first)
elif node.second:
self.visit(node.second)
def visit_literal(self, node):
stack.append(node.val)
def visit_command(self, node):
run_cmd(node.val)
if source == "":
with open("info.txt") as f:
info = f.read()
print(info)
else:
main = Parser(Lexer(source)).parse()
interpreter = Interpreter(main)
interpreter.interpret()
try:
sys.exit(stack[-1])
except IndexError:
pass
Let us continue this discussion in chat.
– Post Rock Garf Hunter – 2017-05-02T18:19:26.8871Came here to downvote, then read the spec. +1 – trichoplax – 2017-05-21T21:49:34.377
Is the one week up? – tuskiomi – 2017-05-22T18:36:01.303
@tuskiomi Yes. I haven't gotten any answers so I have not revealed it yet. I'll update the question to reflect this – Post Rock Garf Hunter – 2017-05-22T18:37:00.590
3What if I develop a language where the empty program sorts a list of integers, prints hello world, determines if parentheses are balanced or tests primality, depending on what's in the input? I suggest to keep these challenges only as examples, and to score submissions exclusively on other, undiscovered challenges – Leo – 2017-05-23T07:53:30.260
@Leo The already revealed categories are worth significantly less than the hidden ones. That might be a good starting point but I don't think that strategy is going to do you much in the long run. – Post Rock Garf Hunter – 2017-05-23T15:14:23.850
"every 8th bit must be odd" implies that only odd ASCII values are allowed. – CalculatorFeline – 2017-06-06T22:14:40.620
Unicode allowed? – tuskiomi – 2017-06-16T20:00:45.553
@tuskiomi All that matters is the bytes, you may have any encoding you wish. – Post Rock Garf Hunter – 2017-06-16T22:24:33.560
How would double characters work for that then? – tuskiomi – 2017-06-16T22:28:23.380
@tuskiomi That should be double bytes. – Post Rock Garf Hunter – 2017-06-16T22:28:54.877
But \u\u isn't a valid character? – tuskiomi – 2017-06-16T22:30:04.950
@tuskiomi The definition of character is unimportant. The source's hexdump is the only thing relevant to the source restrictions. – Post Rock Garf Hunter – 2017-06-16T22:33:47.230
So it would be better to create a really smart compiler rather than a really intricate language, huh? – tuskiomi – 2017-06-16T22:35:03.490
1
Let us continue this discussion in chat.
– Post Rock Garf Hunter – 2017-06-16T22:35:31.313I an a little confused by the line "Command line flags need not obey [...]." So, is it okay to use a different command line flag for every program, or am I getting confused and a command line flag is the same thing as a command line argument? Basically, is it okay to use
-x
in the command line for one program and-y
in another? – Comrade SparklePony – 2017-06-23T19:07:19.9331@ComradeSparklePony The command line flags need to be the same for all programs. – Post Rock Garf Hunter – 2017-06-23T19:08:27.347
Is modifying existing languages acceptable? If so, how should credit be handled? – Comrade SparklePony – 2017-06-26T00:45:11.980
an additional 8 one week after the second answer
-> the competition closes then, correct? Otherwise earlier answers are at a steep disadvantage. – Stephen – 2017-08-19T12:03:59.690@StepHen Yes that is the case. – Post Rock Garf Hunter – 2017-08-19T13:59:47.703