9
\$\begingroup\$

This is my code that interprets my own golfing and esoteric language, Fuzzy-Octo-Guacamole.

It has 2 stacks to store information in, and various operations can be performed on each stack, or both at once.

This is also my first real project using classes, so any advice on how I did would be much appreciated.

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
^ push
ಠ or . shift
_ pop
: get
o peek
; print
number push number
{ } if
( ) loop
@ exit
+ increment
- decrement
? increment global counter
! set global counter to top of stack
' ' no-op
c clear active stack
" string literal
' string literal
U push 0
N push None
K print _UNK
I interleave
🐐 print goat ascii art
g print goat ascii art
= ala ruby rocketship op
< start a list with spaces as separators
s move the top item of the active stack to the inactive one
i invert the ToS
e throw error
r clear the global counter and push it to the stack
a add the top 2 items of the active stack
R string replacement
C copy the active stack to the inactive
j string.join
z reverse the ToS, i.e. 'asd' -> 'dsa'
* multiply the top 2 items on the active stack
"""


import re
import sys
import string
from stack import Stack


class FOG(object):

    # Storage
    commands = {
        "^": "_input",#              ^_^
        "ಠ": "_swap_stacks", #       ಠ_ಠ
        ".": "_swap_stacks", #       ._.
        "_": "_pop",
        ":": "_print_all",#          :_:
        "o": "_peek", #              o_O
        ";": "_print_register",#     ;_;
        "{": "_skip",#               {_{
        "@": "_exit", #              @_@
        "+": "_increment",#          +_+
        "-": "_decrement",#          -_-
        "?": "_increment_register",# ?_?
        "c": "_clear",#              c_c
        '"': "_string_lit",#         "_"
        "(": "_infinite_loop",#      (_(
        "[": "_for_loop",#           [_[
        "!": "_set_register",#       !_!
        "U": "_zero",#               U_U
        "N": "_none",#               N_N
        "K": "_unk",#                K_K
        "I": "_interleave",#         I_I
        ")": "_noop",#               )_)
        "}": "_noop",#               }_}
        "]": "_noop",#               ]_]
        " ": "_noop",
        "'": "_string_lit2",#        '_'
        ">": "_noop",#               >_>
        "🐐": "_goat",#             🐐 _🐐
        "g": "_goat",#               g_g
        "=": "_rocket",#             =_=
        "<": "_list",#               <_<
        "s": "_switch",#             s_s 
        "i": "_invert",#             i_i
        "e": "_error", #             e_e
        "r": "_clear_register",#     r_r
        "a": "_add",#                a_a
        "R": "_replace",#            R_R
        "C": "_copy",#               C_C
        "j": "_join",#               j_j
        "z": "_reverse",#            z_z
        "*": "_mult",#               *_*
    }

    # Main

    def __init__(self):
        self.reg = 0
        self.ccount = 0
        self.idx = 0
        self.temp = None
        self.active = Stack()
        self.inactive = Stack()

    def _uncomment(self, program):
        program = program.split('\n')
        p = []
        for i in program:
            p.append(re.sub('#.+','',i))
        return ''.join(p)

    def execute(self, program, idx=0):
        self.idx = idx
        program = self._encode(program)
        program = self._uncomment(program)
        for char in program:
            self.idx += 1
            if self.ccount:
                self.ccount -= 1
                continue
            cmd = FOG.commands.get(char, "_push")
            if cmd == "_push":
                getattr(self, cmd)(char)
            elif cmd in ["_for_loop",'_infinite_loop','_list','_string_lit','_skip', '_string_lit2']:
                getattr(self, cmd)(program)
            else:
                getattr(self, cmd)()

        if not any([i in program for i in [';',':', 'K']]):
            print self.active.peek()

    # Helpers

    def _encode(self, val):
        return val.replace(u"ಠ", ".").replace(u'🐐','g')

    def _decode(self, val):
        return val.replace(".", u"ಠ").replace('g',u'🐐')

    def _simplify(self, val):
        if all(e in unicode(string.digits) for e in unicode(val)) and type(val) != list and val:
            return int(val)
        elif type(val) == str and re.findall("\d?\.\d+", val):
            if re.findall("\d?\.\d+", val)[0] == val:
                return float(val)
        else:
            return unicode(val)


    # Operators

    def _noop(self):
        pass

    def _input(self):
        y = [self._simplify(i) for i in raw_input().split(' ')]
        for i in y:
            self.active.push(i) 

    def _swap_stacks(self):
        self.active, self.inactive = self.inactive, self.active

    def _pop(self):
        self.temp = str(self.active.pop())

    def _print_all(self):
        print self.active.get()

    def _peek(self):
        self.temp = self.active.peek()

    def _print_register(self):
        if self.temp:
            print self._simplify(self.temp) 
        elif self.temp == 0:
            print self._simplify(self.temp) 
        else:
            print self.temp

    def _skip(self, program):
        if self.active.peek():
                self.ccount += program.index("}", self.idx) - self.idx

    def _exit(self):
        sys.exit()

    def _increment(self):
        x = self._simplify(self.active.pop())
        if isinstance(x, (int, float)):
                self.active.push(x + 1)

    def _decrement(self):
        x = self._simplify(self.active.pop())
        if isinstance(x, (int, float)):
                self.active.push(x - 1)

    def _increment_register(self):
        self.reg += 1

    def _clear(self):
        self.active.clear()

    def _string_lit(self, program):
        y = program[self.idx:program.index('"', self.idx)]
        self.active.push(y)
        self.ccount += program.index('"', self.idx) - self.idx + 1

    def _string_lit2(self, program):
        y = program[self.idx:program.index("'", self.idx)]
        self.active.push(y)
        self.ccount += program.index("'", self.idx) - self.idx + 1

    def _list(self, program):
        y = [self._simplify(i) for i in program[self.idx:program.index(">", self.idx)].split(' ')]
        self.active.push(y)
        self.ccount += program.index(">", self.idx) - self.idx

    def _infinite_loop(self, program):
        p = program[self.idx:program.index(")", self.idx)]
        while True:
            self.execute(p)

    def _for_loop(self, program):
        x = program.index(']',self.idx)+1
        p = program[self.idx:x]
        while self.reg > 1: 
            self.execute(p, 0) 
            self.reg-=1

    def _set_register(self):
        x = self._simplify(self.active.peek())
        if isinstance(x, (int, float)):
                self.reg = x
        else: self.reg = len(x)

    def _clear_register(self):
        self.active.push(self.reg)
        self.reg = 0

    def _zero(self):
        self.active.push(0)

    def _none(self):
        self.active.push(None)

    def _unk(self):
        print "_UNK" 

    def _interleave(self):
        self.inactive = self.active.weave(self.inactive)

    def _goat(self):
        print '''
                          ___.
                         //  \\\\
                        ((   ''
                         \\\\__,
                        /6 (%)\\,
                       (__/:";,;\\--____----_
                        \a;; :';,:';`;,';,;';`,`_
                          ;:,;;';';,;':,';';,-Y\\
                           ;,;,;';';,;':;';'; Z/
                           / ;,';';,;';,;';;'
                          / / |';/~~~~~\\';;'
                         ( K  | |      || |
                          \\_\\ | |      || |
                           \\Z | |      || |
                              L_|      LL_|
                              LW/      LLW/'''

    def _push(self, char):
        self.active.push(self._simplify(char))

    def _rocket(self):
        x = self.active.peek()
        y = self.inactive.peek()
        if x < y or type(x) != type(y):
            self.active.push(self._simplify('-1'))
        elif x > y:
            self.active.push(self._simplify('1'))
        else:
            self.active.push(self._simplify('0'))

    def _switch(self):
        self.inactive.push(self.active.pop())

    def _reverse(self):
        x = self.active.pop()
        if type(x) == str or type(x) == unicode or type(x) == list:
            self.active.push(x[::-1])
        else:
            self.active.push(x)

    def _invert(self):
        x = self.active.pop()
        if type(x) == int:
            self.active.push(int(not x))
        elif type(x) == list:
            if x:
                self.active.push([])
            else:
                self.active.push([1])
        elif type(x) == str or type(x) == unicode:
            if x:
                self.active.push('')
            else:
                self.active.push('avocad')
        elif type(x) == bool:
            self.active.push(not x)
        else:
            self.active.push(not x)

    def _error(self):
        raise ValueError("This is an error, in case you can't tell.")

    def _add(self):
        x = self.active.pop()
        y = self.active.pop()
        if type(x) == type(y):
            self.active.push(x + y)
        else:
            self.active.push(y)
            self.active.push(x)

    def _copy(self):
        self.inactive = Stack(self.active)

    def _replace(self):
        self.active.push(self._simplify(str().replace(str(self.inactive.pop()), str(self.inactive.pop()))))

    def _join(self):
        for i in self.active:
            i = str(i)
        x = unicode(''.join(self.active))
        self.active.clear()
        self.active.push(x)

    def _mult(self):
        x = self.active.pop()
        y = self.active.pop()
        if type(x) == str and type(y) == str:
            self.active.push(y)
            self.active.push(x)
        else:
            self.active.push(x*y)

# Debug


lang_inst = FOG()

#^!["a"'b'C.ss.jo;]

code = u"""^i_;""" 
code = lang_inst._decode(code)

lang_inst.execute(lang_inst._encode(code))
\$\endgroup\$
0

3 Answers 3

3
+50
\$\begingroup\$

Based on your Github repo, you've since switched to Python 3 and made a lot of other changes, but I'll be reviewing based on just the code above since that's what you've posted.

Style inconsistencies

In general, you're quite inconsistent with style. For example, in the commands dict you're inconsistent about whether or not there's a space before the #:

commands = {
    "^": "_input",#              ^_^
    "ಠ": "_swap_stacks", #       ಠ_ಠ
    ".": "_swap_stacks", #       ._.

Note also that, while having all the faces line up is nice, the dict could get annoying to maintain if you add an entry with a long function name in the future. Just something to consider.

In lists you're also inconsistent about whether there's a space after the comma:

["_for_loop",'_infinite_loop','_list','_string_lit','_skip', '_string_lit2']
[';',':', 'K']

Another side note – the second one can just be ";:K" in context since you only need to iterate over each char.

Indentation is also inconsistent:

def _skip(self, program):
    if self.active.peek():
            self.ccount += program.index("}", self.idx) - self.idx

As is spaces around operators:

self.reg-=1

Finally, you're inconsistent about single quotes or double quotes for chars/strings:

return val.replace(u"ಠ", ".").replace(u'🐐','g')
["_for_loop",'_infinite_loop','_list','_string_lit','_skip', '_string_lit2']
self.active.push('avocad')
raise ValueError("This is an error, in case you can't tell.")

_uncomment

_uncomment looks like this:

def _uncomment(self, program):
    program = program.split('\n')
    p = []
    for i in program:
        p.append(re.sub('#.+','',i))
    return ''.join(p)

This can be written more succinctly as:

def _uncomment(self, program):
    return ''.join(re.sub('#.+', '', line) for line in program.splitlines())

Note, however, that this code is actually buggy since # can appear in a string, e.g. "# this is a string"_; (which would get turned into the invalid ").

The function name could also be a bit clearer, e.g. _strip_comments or _remove_comments.

Floats in _simplify

Here's your parsing for floats in _simplify:

elif type(val) == str and re.findall("\d?\.\d+", val):
    if re.findall("\d?\.\d+", val)[0] == val:
        return float(val)

A few comments to be said here:

  • You're making re.findall potentially do twice as much work as it needs to.
  • \d?\.\d+ means it won't match anything like 123.0 or 5.
  • Since you do re.findall("\d?\.\d+", val)[0] == val anyway, you might want to consider re.match and ^$ anchors in regex.
  • Are negative floats supported?

Of course, Mathias' suggestion would be a better way to do this, but note that strings like "nan" would get turned into a float too with that method. Also, once again, _simplify isn't exactly the clearest name given the purpose of the function seems to be parsing input and converting to another type.

Other nits

  • _print_register looks unnecessarily complicated – isn't self._simplify(0) just 0?
  • You might want to consider using r''' ... ''' raw strings for the goat ASCII art, since the double backslashes make it difficult to decipher at a glance.
  • NNa tries to add two Nones and fails. Similarly, <><>* fails trying to multiply lists. I don't know what a sensible course of action in these situations might be, but you might want to think about edge cases a bit more, since there are quite a few.

I didn't have time to go through the whole file carefully, but hopefully that's enough comments from me.

\$\endgroup\$
5
\$\begingroup\$

I didn't got much into the language but there are a few things that doesn't seems quite right:

  • Documentation says {} is if, code says it is skip. As such u'0{g}' prints a goat and u'0i{g}' doesn't, it is weird for an if to be triggered by a falsey value instead of a truthy one. Choose one, fix the other.
  • Why bother encoding and decoding when all your symbols are in the commands dictionary?
  • _simplify is better written

    def _simplify(self, val):
        try:
            return int(val)
        except ValueError:
            try:
                return float(val)
            except ValueError:
                return unicode(val)
    

    Also it's usage seems weird as you simplify most of the stuff you push in your stacks and also simplify on pop. Since you can push lists using <>, I don't understand why you’d want to convert them to unicode before ! for instance.

  • How am I supposed to push 20 on top of the stack? u'20_;' prints 0. Do I really need to use u'45*_;' to print 20? And for 111, is it really u'66*1a3*_;'? Try to not push unrecognized characters one at a time.
  • You need a parser. Indexes won't get you very far when it comes to nested structures: u'<20 <10>>' builds [20, u'<10'] instead of [20, [10]]; u'0i{^i{g}"auie"_;}' prints auie instead of skipping the whole block; and so on.
\$\endgroup\$
2
  • \$\begingroup\$ The if statements are like bf, a goto if like thing. And why would a falsy and a truthy value both trigger the same if statement? \$\endgroup\$
    – user95591
    Commented Apr 4, 2016 at 22:08
  • \$\begingroup\$ The else would be a goto where only one can trigger, i.e. invert the ToS before hitting the }, and having a { after. \$\endgroup\$
    – user95591
    Commented Apr 4, 2016 at 22:12
4
\$\begingroup\$

I copied and pasted your code into a file, Easterly.py, and ran pep8 --statistics Easterly.py on it. These were my results:

1       E225 missing whitespace around operator
10      E231 missing whitespace after ','
39      E261 at least two spaces before inline comment
38      E262 inline comment should start with '# '
1       E265 block comment should start with '# '
1       E303 too many blank lines (2)
4       E501 line too long (101 > 79 characters)
1       E701 multiple statements on one line (colon)
8       W291 trailing whitespace

(I also got a warning about no newline at the end of the file, but given that Stack Exchange trims trailing newlines, I'm not sure if that's your fault or theirs. Worth noting, though.)

That's a lot of PEP8 violations. I'd suggest refactoring your code so it conforms to the standard; it's available online, and there's also a command-line tool to check it automatically for you.


Your naming is a bit odd in places.

Your variable names are surprisingly uninformative -- ccount, reg, and idx mean nothing until you read carefully through the code and figure them out. I'd suggest, at the very least, a comment explaining what they are, and at best, better names.

_uncomment is a really weird name for a function that seems to entirely delete comments, not uncomment lines.

Aside from that, your first docstring is a good reference, and most of the rest of your names are good (I haven't looked through all of the code)


Why does g / 🐐 exist? It's a waste1. I would like the use of if it was for inspecting the stack(s) as a debug action; it seems irrelevant for shift.


In the initial comment, you never explain active vs. inactive stack or what ToS means. I could glean it from the GitHub page, but it's better to include a full (if basic) reference or no reference at all, rather than a partial one.


1: Though there's hardly any harm in it. Most languages have Easter Eggs. As long as it doesn't affect anything, I don't see a problem with keeping it. I'm just confused about why it exists

\$\endgroup\$
1
  • 2
    \$\begingroup\$ 🐐 exists because it was @Downgoat's idea, and because GOATS!! And the original list of command was based on common emoticons in The Nineteenth Byte, so does something. . is the ascii version for codegolf. \$\endgroup\$
    – user95591
    Commented Apr 27, 2016 at 11:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.