102
\$\begingroup\$

99 (pronounced "ninety-nine") is a brand new Esoteric programming language (not to be confused with 99, note the italics). Your task in this challenge is to write an interpreter for 99 that is as short as possible. The submission with the fewest bytes wins. Tiebreaker goes to the submission posted first.

Since this question is a bit more in depth than usual, and I'm eager to see good answers, I'll be awarding a 250 rep bounty to my favorite answer (not necessarily the winner).

99 Spec

99 is an imperative language. Each line in a 99 program is a single statement, and during execution, the instruction pointer starts at the top line and goes through each of the subsequent lines in order, executing them along the way. The program ends when the last line has been executed. Goto statements may reroute the path of the instruction pointer.

Newline, space, and 9 are the only three characters that matter in a 99 program. All other characters are completely ignored. Additionally, trailing spaces on each line are ignored, and multiple spaces in a row are read as one space. ("Newline" refers to any common line break encoding. It doesn't matter which one your interpreter uses.)

So this program:

   9      BLAH        99   9a9bb9c9
9 this line and the next have 6 trailing spaces 9      
      

Is identical to this program:

 9 99 9999
9 9

Variables

Variables in 99 all have names that are one or more 9's strung together (9+ in regex). For example, 9, 99, and 9999999999 are all distinct variables. Naturally, there are infinitely many (barring memory limitations).

The value of each variable is a signed, arbitrary precision integer. By default, each variable is assigned to its own numeric representation. So unless it has been reassigned, the value of the variable 9 is the number 9, and the value of the variable 99 is the number 99, and so on. You could think of it as treating the variables as plain numbers until they are explicitly assigned.

I will use V to refer to an arbitrary variable name below.
Each instance of V could be replaced with 9, 99, 999, 9999, etc.

Statements

There are five different statement types in 99. Each line in a 99 program contains exactly one statement.

The syntax described here assumes all extraneous characters have been removed, all trailing spaces have been removed, and all sequences of multiple spaces have been replaced with single spaces.

1. No Operation


An empty line is a no-op. It does nothing (besides incrementing the instruction pointer).

2. Output

V

A single variable V on a line prints that variable to stdout.

If V has an odd number of 9's (9, 999, etc.) then the integer value of V divided by 9 will be printed (in decimal).

If V has an even number of 9's (99, 9999, etc.) then the ASCII character with code V divided by 9, mod 128 will be printed. (That's (V / 9) % 128, a value from 0 to 127.)

Example: The program

9
9999

would print 1W. The first line prints 1 because 9/9 is 1. The second line prints W because 9999/9 is 1111, and 1111 mod 128 is 87, and 87 is the character code for W.

Note that line breaks are not printed between output tokens. \n needs to be explicitly printed for a line break.

3. Input

 V

A single variable V on a line with a leading space takes input from stdin and stores it in that variable.

If V has an odd number of 9's then the user may type in any signed integer, and V will be set to 9 times that value.

If V has an even number of 9's then the user may type in any ASCII character, and V will be set to 9 times its character code.

Example: Given -57 and A as input, this program

 9
9
 99
99

would output -57A. Internally, the variable 9 would have the value -513, and 99 would have the value 585.

Your interpreter may assume that the inputs are always syntactically valid.

4. Assignment

This statement can be arbitrarily long. It is two or more variables on a line, separated by spaces:

V1 V2 V3 V4 V5 ...

This assigns V1 to the sum of all the V's with even indices, minus the sum of the V's with odd indices (excluding V1). Assignments are by value, not by reference.

It could be translated in most languages as V1 = V2 - V3 + V4 - V5 + ....

So, if there are only two variables, it's normal assignment:

V1 V2V1 = V2

If there are three, then it's subtraction:

V1 V2 V3V1 = V2 - V3

And the +/- sign keeps switching back and forth with each additional variable:

V1 V2 V3 V4V1 = V2 - V3 + V4

Example: This program would output 1110123:

999           Prints triple-nine divided by nine (111).
999 9 9       Assigns triple-nine to zero (nine minus nine).
999           Prints triple-nine divided by nine (0)
9 999 9       Assigns single-nine to negative nine (zero minus nine).
999 999 9     Adds nine to triple-nine (really subtracts negative nine).
999           Prints triple-nine divided by nine (1).
999 999 9     Adds nine to triple-nine (really subtracts negative nine).
999           Prints triple-nine divided by nine (2).
999 999 9     Adds nine to triple-nine (really subtracts negative nine).
999           Prints triple-nine divided by nine (3).

5. Goto (jump if all zero)

This statement can also be arbitrarily long. It is two or more variables on a line, separated by spaces, with a leading space:

 V1 V2 V3 V4 V5 ...

If some of the values besides V1 are non-zero, then this behaves just like a no-op. The instruction pointer is moved to the next line as usual.

If all of the values besides V1 are zero, then the instruction pointer is moved to line number V1. The lines are zero-indexed, so if V1 is zero, then the pointer moves to the top line. The program terminates (normally, without error) if V1 is negative or is larger than the highest possible index (number of lines minus one).

Note that V1 was not divided by 9 here. And since it's impossible to have a variable be a value that's not a multiple of 9, only line numbers that are multiples of 9 can be jumped to.

Examples:

This program will print 1's forever:

9          Prints single-nine divided by nine (always 1).
99 9 9     Assigns double-nine to zero.
 99 99     Jumps to line zero (top line) if double-nine is zero.

This program

99999999                                              Print G.
999 99                                                Set triple-nine to ninety-nine.
9999999999 9999999999 9999999999 99 99 9 9 999 999    Set 10-nine to zero.
99999999999 9999999999                                Set 11-nine to zero.





999                                                   Print triple-nine's value divided by nine. (This is the ninth line.)
99999999                                              Print G.
999 999 9                                             Subtract nine from triple-nine.
 99999 999                                            Jump to line 5-nines if triple-nine is zero (ends program).
 9 99999999999 9999999999                             Jump to line nine if 10-nine and 11-nine are zero (always jumps).

will output the numbers 11 to 1, in decreasing order, surrounded by G's:

G11G10G9G8G7G6G5G4G3G2G1G

Additional Details

The ideal interpreter will run from the command line with the 99 program file name as an argument. The I/O will also be done on the fly in the command line.

You may, however, just write an interpreter function that takes in the program as a string as well as a list of the input tokens (e.g. ["-57", "A"]). The function should print or return the output string.

Slightly different ways of running the interpreter and handling I/O are fine if these options are impossible in your language.


Bonus: Write something cool in 99 and I'll gladly put it in this post as an example.


Hope you enjoyed my 99th challenge! :D

\$\endgroup\$
21
  • 11
    \$\begingroup\$ I considered upvoting, but your current score is 9… \$\endgroup\$
    – wchargin
    Commented Mar 10, 2015 at 3:38
  • 33
    \$\begingroup\$ @WChargin looks like now you'll have to try to get it 99. \$\endgroup\$
    – trlkly
    Commented Mar 10, 2015 at 5:55
  • 5
    \$\begingroup\$ Surely there's a bonus for self-hosting (writing a 99 interpreter in 99), no? \$\endgroup\$
    – Gabe
    Commented Mar 11, 2015 at 15:56
  • 5
    \$\begingroup\$ @Gabe An answer like that probably would get the bounty, but if that were the only answer, what would interpret the interpreter? ;) \$\endgroup\$ Commented Mar 11, 2015 at 17:22
  • 6
    \$\begingroup\$ @Quintec ugh, I came here to upvote, but it's at a score of 99, so I can't. \$\endgroup\$
    – Giuseppe
    Commented Dec 10, 2019 at 17:09

9 Answers 9

26
+250
\$\begingroup\$

Python 3, 421 414 410 404 388 395 401 bytes

Golfed:

import sys,re
v,i,c,g,L={},0,[re.sub('( ?)[^9]+','\\1',l).rstrip().split(' ')for l in open(sys.argv[1])],lambda i:v.get(i,int(i)//9),len
while-1<i<L(c):
 d=c[i];l=L(d);e,*f=d;i+=1
 if l>1:
  x,*y=f
  if e:w=list(map(g,f));v[e]=sum(w[::2])-sum(w[1::2])
  elif l==2:j=input();v[x]=int(j)if L(x)%2 else ord(j)
  elif~-any(g(j)for j in y):i=g(x)*9
 elif e:w=g(e);print(w if L(e)%2 else chr(w%128),end='')

Ungolfed:

import sys, re

# Intialise variable table.
vars_ = {}
get_var = lambda i: vars_.get(i, int(i)//9)

# Parse commands.
commands=[re.sub('( ?)[^9]+','\\1',l).rstrip().split(' ') for l in open(sys.argv[1])]

# Run until the current instruction index is out of bounds.
index=0
while 0 <= index < len(commands):
    # Get the current command and increment the index.
    command = commands[index]
    l = len(command)
    first = command[0]
    index += 1

    if l > 1:
        # Handle the "assignment" command.
        if first:
            operands = [get_var(i) for i in command[1:]]
            vars_[first] = sum(operands[0::2]) - sum(operands[1::2])
        # Handle the "input" command.
        elif l==2:
            inp = input()
            vars_[command[1]] = int(inp) if len(command[1]) % 2 else ord(inp)
        # Handle the "goto" command.
        elif not any(get_var(i) for i in command[2:]):
            index = get_var(command[1]) * 9
    # Handle the "output" command.
    elif first:
        val = get_var(first)
        print(val if len(first) % 2 else chr(val % 128),end='')

Pretty much just a literal implementation of the spec, golfed down as far as I can get it.

Run from the command line by supplying a 99 source-code file as the sole argument (e.g. the last example from the OP):

> python3 ninetynine.py countdown.txt
G11G10G9G8G7G6G5G4G3G2G1G
>

As an added bonus, here's a (rather poor) implementation of "99 bottles" in 99: http://pastebin.com/nczmzkFs

\$\endgroup\$
14
  • 1
    \$\begingroup\$ @DLosc: regarding your first point: I too though that else after a number could be removed, but when I tried it earlier I got a syntax error. Your other tips are much appreciated though! \$\endgroup\$
    – Mac
    Commented Mar 10, 2015 at 1:29
  • 3
    \$\begingroup\$ @coredump: the way the spec is written, each variable will always have a value that is divisible by nine. I found it more concise to allow the variables to take on any value, and to only multiply/divide by nine as needed (in particular, in the goto routine and when getting the default value of the variable). As far as a user of the language is concerned, it makes no difference. \$\endgroup\$
    – Mac
    Commented Mar 10, 2015 at 1:41
  • 2
    \$\begingroup\$ Not the else itself, just the space before it. E.g. 3*n+1if n%2else n//2. \$\endgroup\$
    – DLosc
    Commented Mar 10, 2015 at 1:46
  • 1
    \$\begingroup\$ @DLosc: sorry, I misspoke - I did indeed mean the space, not the else. For example, I tried replacing print(w if L(e)%2 else chr(w%128)) with print(w if L(e)%2else chr(w%128)) and got a syntax exception. \$\endgroup\$
    – Mac
    Commented Mar 10, 2015 at 1:50
  • 1
    \$\begingroup\$ Odd--I tested on ideone.com and it worked, but you're right, it doesn't work in the actual Python3 interpreter (3.4.0 on Ubuntu). This tips post clarifies: a number followed by an alphabetical token works in general, but not for tokens starting with e or E, and (from the comments) not for 0or either. \$\endgroup\$
    – DLosc
    Commented Mar 10, 2015 at 1:58
17
\$\begingroup\$

Common Lisp, 1180 857 837 836 bytes

I know this is not going to win, but I had fun golfing this one. I managed to remove 343 bytes, which is more than two 99 interpreters written in CJam.

Also, quite amusingly, the more I try to compress it, the more I am persuaded that for Common Lisp, it is shorter to compile the code than to try interpreting it on the fly.

(defmacro g(g &aux a(~ -1)> d x q(m 0)r v(n t)c(w 0)? u z)(flet((w(n p)(intern(format()"~a~a"p n))))(#1=tagbody %(case(setf c(ignore-errors(elt g(incf ~))))(#\  #2=(when(> w 0)(pushnew w v)(if u()(setq ?(oddp w)))(#5=push(w w'V)u)(setf w 0))(setf z t))(#\9(incf w)(setf >(or >(and n z))z()n()))((#\Newline())#2#(#5#(when u(setf u(reverse u)a(pop u))(if >(if u`(when(every'zerop(list,@u))(setf @,a)(go ^))`(setf,a,(if ?'(read)'(char-code(read-char)))))(if u`(setf,a,(do(p m)((not u)`(-(+,@p),@m))(#5#(pop u)p)(#5#(if u(pop u)0)m)))`(princ,(if ? a`(code-char(mod,a 128)))))))r)(incf m)(setf ?()u()z()>()n t)))(if c(go %))$(decf m)(setq d(pop r))(if d(#5# d x))(when(=(mod m 9)0)(#5#(w #3=(/ m 9)'L)x)(#5#`(,#3#(go,(w #3#'L)))q))(if(>= m 0)(go $)))`(let(@,@(mapcar(lambda(n)`(,(w n'V),(/(1-(expt 10 n))9)))v))(#1#,@x(go >)^(case @,@q)>))))
  • lexical analysis and code generation are interleaved: I do not store the internal representation, but process directly each line.

  • there is a single tagbody to perform 2 loops:

     (... (tagbody % ... (go %) $ ... (go $)) result)
    
  • local variables are declared in &aux

  • don't generate a closure, but directly the interpreted code

  • etc.

Ungolfed, commented

(defmacro parse-99
    (string &aux
              (~ -1) ; current position in string
              a      ; first variable in a line 
              >      ; does current line starts with a leading space?
              d      ; holds a statement during code generation
              x      ; all statements (labels + expressions)
              q      ; all generated case statements 
              (m 0)  ; count program lines (first increases, then decreases) 
              r      ; list of parsed expressions (without labels)
              v      ; set of variables in program, as integers: 999 is 3
              (n t)  ; are we in a new line without having read a variable? 
              c      ; current char in string 
              (w 0)  ; currently parsed variable, as integer 
              ?      ; is first variable odd? 
              u      ; list of variables in current line, as integers
              z)     ; is the last read token a space?
  (flet((w(n p)
          ;; produce symbols for 99 variables
          ;; e.g. (10 'V) => 'V10
          ;;      (4 'L)  => 'L4
          (intern(format()"~a~a"p n))))
    (tagbody
     parse
       (case (setf c
                   ;; read current char in string,
                   ;; which can be NIL if out-of-bounds
                   (ignore-errors(aref string (incf ~))))

         ;; Space character
         (#\Space
          #2=(when(> w 0)
               (pushnew w v)            ; we were parsing a variable, add it to "v"
               (if u()(setq ?(oddp w))) ; if stack is empty, this is the first variable, determine if odd
               (push(w w'V)u)           ; add to stack of statement variable
               (setf w 0))              ; reset w for next variable

          ;; Space can either be significant (beginning of line,
          ;; preceding a variable), or not. We don't know yet.
          (setf z t))

         ;; Nine
         (#\9
          (incf w) ; increment count of nines
          (setf >(or >(and n z)) ; there is an indent if we were
                                 ; starting a newline and reading a
                                 ; space up to this variable (or if we
                                 ; already know that there is an
                                 ; indent in current line).
                ;; reset z and n
                z()n()))

         ;; Newline, or end of string
         ((#\Newline())
          #2#  ;; COPY-PASTE the above (when(> w 0)...) statement,
               ;; which adds previously read variable if necessary.

          ;; We can now convert the currently read line.
          ;; We push either NIL or a statement into variable R.
          
          (push(when u
                     (setf u (reverse u) ; we pushed, we must reverse
                           a (pop u))    ; a is the first element, u is popped
                     (if >
                         ;; STARTS WITH LEADING SPACE
                         (if u
                             ;; JUMP
                             `(when(every'zerop(list,@u))(setf @,a)(go ^))
                             
                             ;; READ
                             `(setf,a,(if ?'(read)'(char-code(read-char)))))

                         ;; STARTS WITH VARIABLE
                         (if u

                             ;; ARITHMETIC
                             `(setf,a,(do(p m) ; declare p (plus) and m (minus) lists

                                         ;; stopping condition: u is empty
                                         ((not u)
                                          ;; returned value: (- (+ ....) ....)
                                          `(-(+,@p),@m))

                                        ;; alternatively push
                                        ;; variables in p and m, while
                                        ;; popping u
                                        
                                        (push(pop u)p)

                                        ;; first pop must succeed, but
                                        ;; not necessarly the second
                                        ;; one.  using a zero when u
                                        ;; is empty covers a lot of
                                        ;; corner cases.
                                        
                                        (push(if u (pop u) 0) m)))

                             ;; PRINT
                             `(princ,(if ? a`(code-char(mod,a 128)))))))
               r)
          ;; increase line count
          (incf m)
          ;; reset intermediate variables
          (setf ?()u()z()>()n t)))

       ;; loop until end of string
       (if c (go parse))
       
       
     build
       ;;; Now, we can add labels in generated code, for jumps

       ;; decrease line count M, which guards our second loop
       (decf m)

       ;; Take generated statement from R
       (setq d(pop r))

       ;; we pop from R and push in X, which means X will eventually
       ;; be in the correct sequence order. Here, we can safely
       ;; discard NIL statements.
       
       ;; We first push the expression, and THEN the label, so that
       ;; the label ends up being BEFORE the corresponding statement.
       (if d(push d x))

       ;; We can only jump into lines multiple of 9
       (when (=(mod m 9)0)
         ;; Push label
         (push(w #3=(/ m 9)'L)x)
         ;; Also, build a case statement for the jump table (e.g. 2(go L2))
         (push`(,#3#(go,(w #3#'L)))q))
       ;; loop
       (if(>= m 0)(go build)))

    ;; Finally, return the code
    `(let(@ ; target of a jump instruction

          ;; other variables: V3 represents 999 and has a default value of 111
          ,@(mapcar(lambda(n)`(,(w n'V),(/(1-(expt 10 n))9)))v))

       ;; build a tagbody, inject statements from X and case statements from Q
       ;; label ^ points to jump table : we go to ^ each time there is a JUMP
       ;; label > is the end of program

       ;; note that if the case does not match any authorized target
       ;; address, we simply end the programs.
       (tagbody,@x(go >)^(case @,@q)>))))

We use standard input/output during evaluation, meaning we use standard read and princ functions. Hence, the resulting code can be made executable on the command-line, as show below.

Inputs are not completely well sanitized when running 99 programs: it is assumed that the user knows what kind of values are expected.

The only possible runtime overhead can occur when jumping, since we must evaluate the value of a variable and match that value to a label. Except that, the interpreter shall be quite efficient.

Based on the clever obseravtion from Mac that we don't need to divide and multiply by 9 each time, the current version manages to never divides nor multiply by 9 during execution.

Example

If we replace defmacro by defun, we see the generated code. For example:

(g
"99999999                                              Print G.
999 99                                                Set triple-nine to ninety-nine.
9999999999 9999999999 9999999999 99 99 9 9 999 999    Set 10-nine to zero.
99999999999 9999999999                                Set 11-nine to zero.





999                                                   Print triple-nine's value divided by nine. (This is the ninth line.)
99999999                                              Print G.
999 999 9                                             Subtract nine from triple-nine.
 99999 999                                            Jump to line 5-nines if triple-nine is zero (endsprogram).
 9 99999999999 9999999999                             Jump to line nine if 10-nine and 11-nine are zero (alwa

")

Here is the resulting code:

(LET (@
      (V5 11111)
      (V11 11111111111)
      (V1 1)
      (V10 1111111111)
      (V2 11)
      (V3 111)
      (V8 11111111))
  (TAGBODY
   L0
    (PRINC (CODE-CHAR (MOD V8 128)))
    (SETF V3 (- (+ V2) 0))
    (SETF V10 (- (+ V3 V1 V2 V10) V3 V1 V2 V10))
    (SETF V11 (- (+ V10) 0))
   L1
    (PRINC V3)
    (PRINC (CODE-CHAR (MOD V8 128)))
    (SETF V3 (- (+ V3) V1))
    (WHEN (EVERY 'ZEROP (LIST V3)) (SETF @ V5) (GO ^))
    (WHEN (EVERY 'ZEROP (LIST V11 V10)) (SETF @ V1) (GO ^))
    (GO >)
   ^
    (CASE @ (0 (GO L0)) (1 (GO L1)))
   >))

When executed, prints "G11G10G9G8G7G6G5G4G3G2G1G"

Command-line

We can build an executable by dumping a core and specifying the toplevel function. Define a file named boot.lisp where you put the defmacro, and then write the following:

(defun main()(parse-99 <PROGRAM>))
(save-lisp-and-die "test-99" :executable t :toplevel #'main)

Running sbcl --load boot.lisp gives the following output:

$ sbcl --load boot.lisp 
This is SBCL 1.2.8.32-18c2392, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.

SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses.  See the CREDITS and COPYING files in the
distribution for more information.
[undoing binding stack and other enclosing state... done]
[saving current Lisp image into test-99:
writing 5824 bytes from the read-only space at 0x20000000
writing 3120 bytes from the static space at 0x20100000
writing 55771136 bytes from the dynamic space at 0x1000000000
done]

Then, running the compiled 99 program:

$ time ./test-99
G11G10G9G8G7G6G5G4G3G2G1G
real    0m0.009s
user    0m0.008s
sys     0m0.000s

99 bottles

If you are interested, here is the compiled code for the 99 bottles program written in Mac's answer: http://pastebin.com/ZXe839CZ (this is the old version where we have jmp and end labels, a surrounding lambda and prettier arithmetic).

Here is an execution with the new version, to prove it still works: http://pastebin.com/raw.php?i=h73q58FN

\$\endgroup\$
16
\$\begingroup\$

CJam, 157 bytes

{:I;_N" 9"+--N/:P:,$W=){1a*Ab}%:V;{PT):T(=:LS%_{LS#\:,_,({(\{V=}%@{V-1@{2$*+0@-\}*\;t:V;}{:|T@V=9*?:T;}?}{~\{_V=\1&!{128%c}*o}{VIW):W=it:V;}?}?}R?Tg)TP,<*}g}

Try it online:

Explanation

Trying to format this with proper indentation and comments would probably take forever, so I'll just give an algorithmic summary.

The code is a block, CJam's analog to anonymous functions. The block expects the program string and list of inputs on the stack when executed.

Initialization consists of three steps. First, the input list is saved. Then, every character in the program that isn't meaningful is removed and the result is split into a list of lines and saved. Finally, the variable list is initialized. This list maps each variable, indexed by name length, to its value divided by 9 (a variable can never hold a value that's not a multiple of 9, and all operations except goto benefit from this change). The list is initialized up to the length of the longest line, which is an upper bound on the longest varaible name present. There's also a bit of implicit initialization due to initial variable values: the line number is 0 and the input index is -1.

The interpreter is implemented as one would expect: a loop that reads the next line, increments the line number, and executes the line while the line number points to an existing line. Line parsing first checks that the line isn't empty, then branches based on whether the arity is 1 or >1, then branches based on whether there was a leading space. These four branches emulate the four (excluding no-op) operations in a mostly straightforward manner, albeit agressively golfed like everything else. Perhaps one optimization of note is that, since a valid input sequence should always produce an element of type expected by the program, I omitted making separate cases for input based on the length of the variable name. It is simply assumed that the element read from the input list is of the expected type.

\$\endgroup\$
4
  • 16
    \$\begingroup\$ +1. Quite short. Now, can you write a CJam interpreter in 99? ;-) \$\endgroup\$
    – coredump
    Commented Mar 10, 2015 at 16:08
  • 10
    \$\begingroup\$ @coredump *shudders* \$\endgroup\$
    – Runer112
    Commented Mar 10, 2015 at 16:38
  • \$\begingroup\$ Damn, I can only get 195 and then I lost hope and gave up :P \$\endgroup\$
    – Optimizer
    Commented Mar 12, 2015 at 12:51
  • \$\begingroup\$ This doesn't take the correct modulo when printing negative values. This can be fixed by replacing 128% with 128,=. \$\endgroup\$ Commented Aug 30, 2015 at 11:45
6
\$\begingroup\$

TI-84 Basic (Calculator Script), 376 373 377 381 bytes

If it runs on a TI-84 calculator, you'll be able to use it on a standardized test... so it's useful ;)

Minimum operating system version - 2.53MP (MathPrint) due to the summation sigma

#Get input from STDIN
:Ans+":"->Str0
#Initialize instruction pointer
:1->I
#Initialize variable set
:DelVar L1999->dim(L1
#Strip out those pesky non-newline/space/9 characters
:For(J,1,length(Ans
:sub(Str0,J,1
:If not(inString(": 9",Ans
:sub(Str0,1,J-1)+sub(Str0,J+1,length(Str0)-J->Str0
:End
#Main interpreting loop
:While I<length(Str0
:sub(Str0,I+1,inString(Str0,":",I+1)-I-1->Str1
:DelVar A" "=sub(Ans,1,1->A
:inString(Str0,":",I+1->I
:If A
:sub(Str1,2,length(Str1)-1->Str1
:End
:length(Str1->L
#0 is Output, 1 is Input, 2 is Assignment, 3 is Goto
:2A+inString(Str1," ->B
:If not(Ans
:Disp L1(L
:If Ans=1
:Then
:Input C
:C->L1(L
:End
#Get those delimited variables
:If B>1
:Then
:"{"+Str1->Str2
:While inString(Ans," 
:inString(Ans," 
:sub(Str2,1,Ans-1)+sub(Str2,Ans+1,length(Str2)-Ans->Str2
:End
:log(expr(Ans)+1->L2
:End
:If B=2
#Gotta expand that -+ pattern
:Ans(2->L1(Ans(1
;Love that summation Σ
:If B=3 and Σ(L2(K),K,2,dim(L2
:Then
:DelVar IFor(K,0,9L2(1
:inString(Str0,":",I+1->I
:End
:End

P.S. ASCII guidelines could not be followed exactly, but in TI-Basic : is a newline. Thus, all the actual newlines in the code mean that the : or # at the beginning of each line are not required. The beginning tokens : and # just differentiate between comments and code.

Original Hex Dump (376 Bytes)

49 3f bb 54 5d 20 39 39 39 04 b5 5d 20 3f 72 04 aa 09 3f d3 4a 2b 31 2b bb 2b 72 3f bb 0c aa 09 2b 4a 2b 31 3f ce b8 bb 0f 2a 3e 29 39 2a 2b 72 3f bb 0c aa 09 2b 31 2b 4a 71 31 11 70 bb 0c aa 09 2b 4a 70 31 2b 72 71 4a 04 aa 09 3f d4 3f d1 49 6b bb 2b aa 09 3f bb 0c aa 09 2b 49 70 31 2b bb 0f aa 09 2b 2a 3e 2a 2b 49 70 31 11 71 49 71 31 04 aa 20 3f bb 54 41 2a 29 2a 6a bb 0c 72 2b 31 2b 31 04 41 3f bb 0f aa 09 2b 2a 3e 2a 2b 49 70 31 04 49 3f ce 41 3f bb 0c aa 20 2b 32 2b bb 2b aa 20 11 71 31 04 aa 20 3f d4 3f bb 2b aa 20 04 4c 3f 32 41 70 bb 0f aa 20 2b 2a 29 04 42 3f ce b8 72 3f de 5d 20 10 4c 11 83 39 3f ce 72 6a 31 3f cf 3f dc 43 3f 39 43 04 5d 20 10 4c 3f d4 3f ce 42 6c 31 3f cf 3f 2a 08 2a 70 aa 20 04 aa 01 3f d1 bb 0f 72 2b 2a 29 3f bb 0f 72 2b 2a 29 3f bb 0c aa 01 2b 31 2b 72 71 31 11 70 bb 0c aa 01 2b 72 70 31 2b bb 2b aa 01 11 71 72 04 aa 01 3f d4 3f c0 bb 2a 72 11 70 31 04 5d 01 3f d4 3f ce 42 6a 32 3f 72 10 32 04 5d 20 10 72 10 31 3f ce 42 6a 33 40 ef 33 5d 01 10 4b 11 2b 4b 2b 32 2b b5 5d 01 3f cf 3f bb 54 49 d3 4b 2b 30 2b 5d 01 10 31 3f bb 0f aa 09 2b 2a 3e 2a 2b 49 70 31 04 49 3f d4 3f d4 2e 76

Edit #1 - Optimized 3 bytes using Mac's observation Edits #2 & #3 - Fixed bugs spotted by Runer112.

\$\endgroup\$
14
  • 13
    \$\begingroup\$ Being easy to use in stressful situations like standardized tests is exactly what I designed 99 for. \$\endgroup\$ Commented Mar 11, 2015 at 0:01
  • 1
    \$\begingroup\$ Can I suggest using a different character, like #, for the comments? (NB: Comments in the actual code are implemented as a line with only an unclosed string, which clobbers Ans) \$\endgroup\$
    – Riking
    Commented Mar 11, 2015 at 5:35
  • 8
    \$\begingroup\$ Have you actually tried running this? I haven't, but just from looking at it a bit more, I've spotted what seem to be at least half a dozen bugs. For instance: variables aren't initialized with their values, the Ans input is overwritten so Ans->Str0 on line 6 will error, there are multiple instances where the length argument of a sub() command can be zero which results in an error, Ans on line 11 will be a string so Ans-J will error... And I only looked at about the first half of the program. \$\endgroup\$
    – Runer112
    Commented Mar 12, 2015 at 13:06
  • 1
    \$\begingroup\$ @Timtech That still leaves the other problems, though. As I mentioned, there's the lack of character I/O, lack of variable initialization, and multiple instances where a sub() command can have length zero and throw an error. And once the sub() invocations are fixed, I'm afraid it may reveal more issues. \$\endgroup\$
    – Runer112
    Commented Mar 12, 2015 at 17:07
  • 1
    \$\begingroup\$ @Timtech I was referring to this: "By default, each variable is assigned to its own numeric representation. So unless it has been reassigned, the value of the variable 9 is the number 9, and the value of the variable 99 is the number 99, and so on." And strings of length 0 can be produced by means like "", but it's sort of a bug that basically no string manipulation command can consume or produce an empty string, including sub(). \$\endgroup\$
    – Runer112
    Commented Mar 12, 2015 at 22:34
6
\$\begingroup\$

C 426 458 481 497

Edit Maybe I'm going too far, but this works with Visual C: removed stdio.h, using int instead of FILE* for fopen and getc

Edit 2 Reorder execution step, more clutter, 32 chars saved

B[99999],*r,*i[9999],V[999],v,w,m,n;unsigned p,s;
main(b,a)char*a[];{r=i[0]=B;m=fopen(a[1],"r");
do if(w=getc(m),n+=w==57,w<33){
if(n){for(v=1,b=n;--b;)v=v*10+1;V[n]=v;*r++=p?-n:n;b=n=0;};
w-32?(*r=p=0,b=i[++s]=++r):(p=b,b=0);}while(w>=0);
while(p<s)if(w=0,r=i[p++],v=*r++)
if(m=v>0,*r){for(;b=*r++;m=-m)w=w+m*V[b]|!m*V[b];m?V[v]=w:(p=w?p:9*V[-v]);
}else~v&1?!m?V[-v]=getchar():putchar(V[v]&127):m?printf("%d",V[v]):scanf("%d",V-v);
}

Stand alone console program, program name taken on command line and input/output via console.

Old style K&R, default type int for global vars and parameters. Assuming EOF defined as -1 (as it is in every C implementation I'm aware of)

Compiles with warnings with Visual Studio 2010 (Win32 console C++ project, compile as C) Compiles on Ideone, but can't run as it needs a file.

First step, the source code is read and parsed, each line is stored as a sequence of integers based on the numbers of 9s. If there is a leading blank, the first number is negative. So: 9 BLAH 99 9a9bb9c9 ( 9 99 9999) becomes -1,2,4 There is a shortcut - not so legal : all ascii codes less than ' ' are considered newlines.

In this step all the used variable are preinitialised.

The execution step follows the specifications, no frills, save storing numbers divided by 9.

More readable same code (I hope), spaces and newlines added

B[99999],*r,*i[9999],V[999],v,w,m,n;
unsigned p,s;
main(b,a)char*a[];
{
  r=i[0]=B;
  m=fopen(a[1],"r");
  do if(w=getc(m),n+=w==57,w<33)
  {
     if(n){for(v=1,b=n;--b;)v=v*10+1;V[n]=v;*r++=p?-n:n;b=n=0;};
     w-32?(*r=p=0,b=i[++s]=++r):(p=b,b=0);
  }
  while (w>=0);
  while (p<s)
    if (w = 0, r = i[p++], v = *r++)
        if (m = v > 0, *r){
            for(; b = *r++; m = -m)
                w = w + m*V[b] | !m*V[b];
            m ? V[v]=w : (p = w ? p : 9*V[-v]);
        } else
            ~v & 1 
            ? !m ? V[-v] = getchar() : putchar(V[v] & 127)  
            : m ? printf("%d", V[v]) : scanf("%d", V - v);
}
\$\endgroup\$
1
  • 2
    \$\begingroup\$ Also works with GCC 4.8.2. Compiles as C99! \$\endgroup\$
    – EMBLEM
    Commented Mar 18, 2015 at 19:20
5
\$\begingroup\$

Haskell, 550 bytes

import Data.List.Split
import System.Environment
a#b=takeWhile(/=a)b
(!)=map
main=do(f:_)<-getArgs;readFile f>>=e.(p!).lines
p l=(if ' '#l<'9'#l then[0]else[])++length!(wordsBy(/='9')l)
e l=(\x->div(10^x-1)9)%l where
 _%[]=return()
 v%([]:r)=v%r
 v%([n]:r)=putStr(if odd n then show(v n)else[toEnum$v n`mod`128])>>v%r
 v%([0,n]:r)=do i<-getLine;u n(if odd n then read i else fromEnum$head i)v%r
 v%((0:n:m):r)|any(/=0)(v!m)=v%r|v n<0=v%[]|1<2=v%drop(9*v n)l
 v%((n:m):r)=u n(sum$zipWith(*)(v!m)(cycle[1,-1]))v%r
u n i v= \x->if x==n then i else v x

Example run with the "countdown" program stored in the file i.99

$ ./99 i.99
G11G10G9G8G7G6G5G4G3G2G1G

Ungolfed version:

import Data.List.Split
import System.Environment

-- The main function takes the first command line argument as a file name,
-- reads the content, splits it into lines, parses each line and evaluates
-- the list of parsed lines.
main = do
 (f:_)<-getArgs
 readFile f >>= eval.map parse.lines

-- each line is coverted into a list of integers, which represent the number
-- of 9s (e.g. "999 99 9999" -> [3,2,4]). If there's a space before the first
-- 9, a 0 is put in front of the list (e.g. " 9 9 999" -> [0,1,1,3]).
parse l = (if takeWhile (/=' ') l < takeWhile (/='9') l then [0] else [])
   ++ map length (wordsBy(/='9') l)

-- The work is done by the helper function 'go', which takes two arguments
--   a) a functions which takes an integer i and returns the value of the
--      variable with i 9s (e.g: input: 4, output: value of 9999). To be
--      exact, the value divided by 9 is returned.
--   b) a list of lines to work on
-- 'eval' starts the process with a function that returns i 1s for every i and
-- the list of the parsed input. 'go' checks which statement has to be
-- executed for the next line and calls itself recursively
eval list = go (\x -> div (10^x-1) 9) list
   where
   go _ []                  = return ()
   go v ([]:r)              = go v r
   go v ([n]:r)             = putStr (if odd n then show(v n) else [toEnum (v n`mod`128)]) >> go v r
   go v ([0,n]:r)           = do i<-getLine ; go (update n (if odd n then read i else fromEnum$head i) v) r
   go v ((0:n:m):r)
      | any (/=0) (map v m) = go v r
      | v n < 0             = go v []
      | otherwise           = go v (drop (9*v n) list)
   go v ((n:m):r)           = go (update n (sum $ zipWith (*) (map v m) (cycle[1,-1])) v) r

-- updates a function for retrieving variable values.
-- n = position to update
-- i = new value
-- v = the function to update
update n i v = \x->if x==n then i else v x
\$\endgroup\$
4
\$\begingroup\$

JavaScript (ES6) 340 352

A function with 2 parameters

  • program code as a multiline string
  • input as an array

The third optional parameter (default 10k) is the max number of iterations - I don't like a program that run forever

JSFiddle To test

I=(c,i,k=1e5,
  V=v=>v in V?V[v]:v/9 // variable getter with default initial value
)=>(c=>{
 for(p=o='';--k&&p<c[L='length'];)
   (v=(r=c[p++].split(' '))[S='shift']())? // no leading space
      r[r.map(t=>w-=(m=-m)*V(t),w=0,m=1),0]?V[v]=w // Assign
      :o+=v[L]&1?V(v):String.fromCharCode(V(v)&127) // Output
   : // else, leading space
    (v=r[S]())&&
       (r[0]?r.some(t=>V(t))?0:p=9*V(v) // Goto
       :(t=i[S](),V[v]=v[L]&1?t:t.charCodeAt()) // Input
    )
})(c.replace(/ (?=[^9])|[^9\s]/g,'').split('\n'))  // code cleaning
||o
\$\endgroup\$
4
\$\begingroup\$

q/k, 490 469

M:mod;T:trim;R:read0;S:set;s:" "
f:(rtrim')(f:R -1!`$.z.x 0)inter\:"9 \n"
k)m:{@[x;&M[!#x;2];-:]}
b:{}
k)p:{1@$$[1=M[#x;2];(K x)%9;"c"$M[(K x)%9;128]];}
k)i:{S[(`$T x);$[1=M[#T x;2];9*"J"$R 0;*9*"i"$R 0]]}
k)K:{$[#!:a:`$x;.:a;"I"$x]}
k)v:{(S).(`$*:;+/m@K'1_)@\:T's\:x}
k)g:{$[&/0=C:K'c:1_J:s\:T x;n::-1+K@*J;|/~0=C;;(d<0)|(d:*C)<#f;exit 0]}
k)r:{`b`p`i`v`g@*&(&/x=s;q&1=c;(e~s)&1=C;(q:e~"9")&1<c:#s\:x;((e:*x)~s)&1<C:#s\:1_x)}
k)n:0;while[~n>#o:(r')f;(o n)f n;n+:1]
\\

.

$ q 99.q countdown.txt -q
G11G10G9G8G7G6G5G4G3G2G1G

The script is a mixture of q and k, so first up I define a few q keywords that I want to use multiple times in k functions. (basically #define macros)

M:mod;T:trim;R:read0;S:set

f reads the file passed into the program and strips out unnecessary characters

q)f
"99999999"
"999 99"
"9999999999 9999999999 9999999999 99 99 9 9 999 999"
"99999999999 9999999999"
""
""
""
""
""
"999"
"99999999"
"999 999 9"
" 99999 999"
" 9 99999999999 9999999999"

m takes a list/vector and multiples the odd indices by -1

q)m 1 2 3 4 5
1 -2 3 -4 5

b is just an empty function, used for the no-op lines

p is the print function.

K is a function that examines a variable. If the variable exists, it returns it, otherwise it just returns the literal.

//999 not defined, so just return 999
q)K "999"
999
//Set 999 to 9
q)v "999 9"
//K now returns 9
q)K "999"
9

v is the assignment function.

g is the goto function.

r takes a string and decides which operation needs to be applied.

And then finally, I just iterate through the f list of strings, with n as the iterator. The goto function will update n as necessary.

\$\endgroup\$
0
3
\$\begingroup\$

Perl, 273 266 255 244 238

Line breaks added for clarity.

open A,pop;
for(@c=<A>){
y/ 9//cd;s/ +/ /g;s/ $//;
$p="((99)+|9+)";$a='+';
s/^ $p$/$1='$2'?ord<>:<>/;
s/^$p$/print'$2'?chr$1%128:$1/;
s/^ $p /\$_=$1*011unless/&&y/ /|/;
s/ /=/;s/ /$a=-$a/ge;
s!9+!${x.$&}=$&/9;"\$x$&"!eg}
eval$c[$_++]until/-/|$_>@c

Program name taken on command line:

$ perl 99.pl 99beers.99

Each line of the program is converted into Perl code, for example:

print'$x99'?chr$x99999999%128:$x99999999
$x999=$x99
$x9999999999=$x9999999999-$x9999999999+$x99-$x99+$x9-$x9+$x999-$x999
$x99999999999=$x9999999999





print''?chr$x999%128:$x999
print'$x99'?chr$x99999999%128:$x99999999
$x999=$x999-$x9
$_=$x99999*011unless$x999
$_=$x9*011unless$x99999999999|$x9999999999

More details

open A,pop; # open the source file
for(@c=<A>){ # read all lines into @c and iterate over them
y/ 9//cd; # remove all but spaces and 9's
s/ +/ /g;s/ $//; # remove duplicate and trailing spaces
$p="((99)+|9+)";$a='+';
s/^ $p$/$1='$2'?ord<>:<>/; # convert input
s/^$p$/print'$2'?chr$1%128:$1/; # convert output
s/^ $p /\$_=$1*011unless/&&y/ /|/; # convert goto
s/ /=/;s/ /$a=-$a/ge; # convert assignment
s!9+!${x.$&}=$&/9;"\$x$&"!eg} # initialize and convert variables
eval$c[$_++]until/-/|$_>@c # run (program counter is in $_)
\$\endgroup\$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.