# *Acc!*, [Cracc'd](https://codegolf.stackexchange.com/a/62446/16766) 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](https://en.wikipedia.org/wiki/Indent_style#Variant:_Stroustrup)):

    Count i while i-5 {
     ...
    }

2. `Write <charcode>`

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

3. 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 `exec`s 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()