Skip to main content
3 of 4
Reduced unnecessary stuff
DLosc
  • 39.2k
  • 5
  • 83
  • 141

Acc!, Cracc'd by ppperry

This language has one looping structure, basic integer math, character I/O, and an accumulator (thus the name). Just one accumulator. Thus, the name.

Statements

Commands are parsed line by line. There are three types of command:

  1. Count <var> while <cond>

Counts <var> up from 0 as long as <cond> is nonzero, equivalent to C-style for(<var>=0; <cond>; <var>++). The loop counter can be any single lowercase letter. The condition can be any expression, not necessarily involving the loop variable. The loop halts when the condition's value becomes 0.

Loops require K&R-style curly braces (in particular, the Stroustrup variant):

Count i while i-5 {
 ...
}
  1. Write <charcode>

Outputs a single character with the given ASCII/Unicode value to stdout. The charcode can be any expression.

  1. Expression

Any expression standing by itself is evaluated and assigned back to the accumulator (which is accessible as _). Thus, e.g., 3 is a statement that sets the accumulator to 3; _ + 1 increments the accumulator; and _ * N reads a character and multiplies the accumulator by its charcode.

Note: the accumulator is the only variable that can be directly assigned to; loop variables and N can be used in calculations but not modified.

The accumulator is initially 0.

Expressions

An expression can include integer literals, loop variables (a-z), _ for the accumulator, and the special value N, which reads a character and evaluates to its charcode each time it is used. Note: this means you only get one shot to read each character; the next time you use N, you'll be reading the next one.

Operators are:

  • +, addition
  • -, subtraction; unary negation
  • *, multiplication
  • /, integer division
  • %, modulo
  • ^, exponentiation

Parentheses can be used to enforce precedence of operations. Any other character in an expression is a syntax error.

Whitespace and comments

Leading and trailing whitespace and empty lines are ignored. Whitespace in loop headers must be exactly as shown, with a single space between the loop header and opening curly brace as well. Whitespace inside expressions is optional.

# begins a single-line comment.

Input/Output

Acc! expects a single line of characters as input. Each input character can be retrieved in sequence and its charcode processed using N. Trying to read past the last character of the line causes an error. A character can be output by passing its charcode to the Write statement.

Interpreter

The interpreter (written in Python 3) translates Acc! code into Python and execs it.

import re, sys

def main():
    if len(sys.argv) != 2:
        print("Please supply a filename on the command line.", file=sys.stderr)
        return
    codeFile = sys.argv[1]
    with open(codeFile) as f:
        code = f.readlines()
    code = translate(code)
    exec(code, {"inputStream": (ord(char) for char in input())})

def translate(accCode):
    indent = 0
    loopVars = []
    pyCode = ["_ = 0"]
    for lineNum, line in enumerate(accCode):
        if "#" in line:
            # Strip comments
            line = line[:line.index("#")]
        line = line.strip()
        if not line:
            continue
        lineNum += 1
        if line == "}":
            if indent:
                loopVar = loopVars.pop()
                if loopVar is not None:
                    pyCode.append(" "*indent + loopVar + " += 1")
                indent -= 1
            else:
                raise SyntaxError("Line %d: unmatched }" % lineNum)
        else:
            m = re.fullmatch(r"Count ([a-z]) while (.+) \{", line)
            if m:
                expression = validateExpression(m.group(2))
                if expression:
                    loopVar = m.group(1)
                    pyCode.append(" "*indent + loopVar + " = 0")
                    pyCode.append(" "*indent + "while " + expression + ":")
                    indent += 1
                    loopVars.append(loopVar)
                else:
                    raise SyntaxError("Line %d: invalid expression " % lineNum
                                      + m.group(2))
            else:
                m = re.fullmatch(r"Write (.+)", line)
                if m:
                    expression = validateExpression(m.group(1))
                    if expression:
                        pyCode.append(" "*indent
                                      + "print(chr(%s), end='')" % expression)
                    else:
                        raise SyntaxError("Line %d: invalid expression "
                                          % lineNum
                                          + m.group(1))
                else:
                    expression = validateExpression(line)
                    if expression:
                        pyCode.append(" "*indent + "_ = " + expression)
                    else:
                        raise SyntaxError("Line %d: invalid statement "
                                          % lineNum
                                          + line)
    return "\n".join(pyCode)

def validateExpression(expr):
    "Translates expr to Python expression or returns None if invalid."
    expr = expr.strip()
    if re.search(r"[^ 0-9a-z_N()*/%^+-]", expr):
        # Expression contains invalid characters
        return None
    elif re.search(r"[a-zN_]\w+", expr):
        # Expression contains multiple letters or underscores in a row
        return None
    else:
        # Not going to check validity of all identifiers or nesting of parens--
        # let the Python code throw an error if problems arise there
        # Replace short operators with their Python versions
        expr = expr.replace("^", "**")
        expr = expr.replace("/", "//")
        # Replace N with a call to get the next input character
        expr = expr.replace("N", "inputStream.send(None)")
        return expr

if __name__ == "__main__":
    main()
DLosc
  • 39.2k
  • 5
  • 83
  • 141