Scrivi un interprete per 99


99

99 (pronunciato "novantanove") è un nuovissimo linguaggio di programmazione esoterico (da non confondere con 99 , notare il corsivo). Il tuo compito in questa sfida è scrivere un interprete per 99 che sia il più breve possibile. Vince l'invio con il minor numero di byte . Tiebreaker va alla presentazione pubblicata per prima.

Poiché questa domanda è un po 'più approfondita del solito, e sono ansioso di vedere buone risposte, assegnerò una ricompensa di 250 rep alla mia risposta preferita (non necessariamente il vincitore).

99 Spec

99 è un linguaggio imperativo . Ogni riga in un programma 99 è una singola istruzione e durante l'esecuzione, il puntatore dell'istruzione inizia nella riga superiore e attraversa ciascuna delle righe successive in ordine, eseguendole lungo il percorso. Il programma termina quando è stata eseguita l'ultima riga. Le istruzioni Goto possono reindirizzare il percorso del puntatore dell'istruzione.

Newline, spazio e 9sono gli unici tre personaggi che contano in un programma 99 . Tutti gli altri personaggi sono completamente ignorati. Inoltre, gli spazi finali su ciascuna riga vengono ignorati e più spazi in una riga vengono letti come uno spazio. ("Newline" si riferisce a qualsiasi codifica di interruzione di riga comune . Non importa quale utilizzi l'interprete.)

Quindi questo programma:

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

È identico a questo programma:

 9 99 9999
9 9

variabili

Le variabili in 99 hanno tutte nomi che sono 9uniti da uno o più ( 9+in regex). Ad esempio, 9, 99e 9999999999sono tutte variabili distinte. Naturalmente ce ne sono infiniti (salvo limiti di memoria).

Il valore di ogni variabile è un numero intero di precisione arbitraria con segno. Per impostazione predefinita, ogni variabile è assegnata alla propria rappresentazione numerica. Quindi, a meno che non sia stato riassegnato, il valore della variabile 9è il numero 9, e il valore della variabile 99è il numero 99 e così via. Si potrebbe pensare che tratti le variabili come numeri semplici fino a quando non vengono assegnate esplicitamente.

Userò Vper fare riferimento a un nome di variabile arbitraria di seguito.
Ogni istanza di Vpotrebbe essere sostituito con 9, 99, 999, 9999, etc.

dichiarazioni

Esistono cinque diversi tipi di istruzione in 99 . Ogni riga in un programma 99 contiene esattamente un'istruzione.

La sintassi qui descritta presuppone che tutti i caratteri estranei siano stati rimossi, tutti gli spazi finali siano stati rimossi e tutte le sequenze di spazi multipli siano state sostituite con spazi singoli.

1. Nessuna operazione


Una riga vuota non è operativa . Non fa nulla (oltre a incrementare il puntatore dell'istruzione).

2. Uscita

V

Una singola variabile Vsu una riga stampa quella variabile su stdout.

Se Vha un numero dispari di 9's ( 9, 999, ecc), allora il valore intero Vdiviso per 9 verrà stampata (in decimale).

Se Vha un numero pari di 9's ( 99, 9999ecc), allora l'ASCII carattere con il codice Vdiviso per 9, mod 128 sarà stampata. (Cioè (V / 9) % 128, un valore compreso tra 0 e 127.)

Esempio : il programma

9
9999

stamperebbe 1W. La prima riga 1viene Wstampata perché 9/9 è 1. La seconda riga viene stampata perché 9999/9 è 1111 e 1111 mod 128 è 87 e 87 è il codice carattere per W.

Si noti che le interruzioni di riga non vengono stampate tra i token di output. \ndeve essere esplicitamente stampato per un'interruzione di riga.

3. Input

 V

Una singola variabile Vsu una linea con uno spazio iniziale prende l'input dallo stdin e lo memorizza in quella variabile.

Se Vha un numero dispari di 9allora l'utente può digitare qualsiasi numero intero con Vsegno e sarà impostato su 9 volte quel valore.

Se Vha un numero pari di 9, l'utente può digitare qualsiasi carattere ASCII e Vsarà impostato a 9 volte il suo codice carattere.

Esempio : dato -57e Acome input, questo programma

 9
9
 99
99

sarebbe uscita -57A. Internamente, la variabile 9avrebbe il valore -513 e 99il valore 585.

Il tuo interprete può presumere che gli input siano sempre sintatticamente validi.

4. Cessione

Questa affermazione può essere arbitrariamente lunga. Sono due o più variabili su una linea, separate da spazi:

V1 V2 V3 V4 V5 ...

Questo assegna alla somma di tutti quelli con indici pari, meno la somma di quelli con indici dispari (escluso ). Le assegnazioni sono per valore, non per riferimento.V1VVV1

Potrebbe essere tradotto nella maggior parte delle lingue come .V1 = V2 - V3 + V4 - V5 + ...

Quindi, se ci sono solo due variabili, è normale assegnazione:

V1 V2V1 = V2

Se ce ne sono tre, allora è sottrazione:

V1 V2 V3V1 = V2 - V3

E il segno +/ -continua a passare avanti e indietro con ogni variabile aggiuntiva:

V1 V2 V3 V4V1 = V2 - V3 + V4

Esempio : questo programma genererebbe 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. Vai a (salta se tutti zero)

Questa affermazione può anche essere arbitrariamente lunga. Sono due o più variabili su una linea, separate da spazi, con uno spazio iniziale :

 V1 V2 V3 V4 V5 ...

Se alcuni dei valori oltre sono diversi da zero, allora questo si comporta proprio come un no-op. Il puntatore dell'istruzione viene spostato nella riga successiva come al solito.V1

Se tutti i valori oltre a zero sono zero, il puntatore dell'istruzione viene spostato sul numero di riga . Le linee sono indicizzate a zero, quindi se è zero, il puntatore si sposta sulla riga superiore. Il programma termina (normalmente, senza errori) se è negativo o è maggiore dell'indice più alto possibile (numero di righe meno uno).V1 V1V1V1

Nota che qui non è stato diviso per 9. E poiché è impossibile che una variabile sia un valore che non sia un multiplo di 9, è possibile saltare solo ai numeri di riga multipli di 9.V1

Esempi:

Questo programma stampa 1per sempre:

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.

Questo programma

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).

produrrà i numeri da 11 a 1, in ordine decrescente, circondati da G:

G11G10G9G8G7G6G5G4G3G2G1G

dettagli aggiuntivi

L'interprete ideale verrà eseguito dalla riga di comando con il nome del file di programma 99 come argomento. L'I / O verrà eseguito anche al volo nella riga di comando.

È possibile, tuttavia, scrivere semplicemente una funzione di interprete che accetta il programma come stringa e un elenco di token di input (ad es ["-57", "A"].). La funzione dovrebbe stampare o restituire la stringa di output.

Modi leggermente diversi di eseguire l'interprete e gestire l'I / O vanno bene se queste opzioni sono impossibili nella tua lingua.


Bonus: scrivi qualcosa di interessante in 99 e lo inserirò volentieri in questo post come esempio.


Spero ti sia piaciuta la mia 99a sfida! : D


9
Ho considerato il voto, ma il tuo punteggio attuale è 9 ...
wchargin

30
@WChargin sembra che ora dovresti provare a prenderlo 99.
trlkly

5
Sicuramente c'è un bonus per i self-hosting (scrittura di un 99 interprete in 99 ), no?
Gabe,

5
@Gabe Una risposta del genere probabilmente otterrebbe la generosità, ma se fosse l'unica risposta, cosa interpreterebbe l'interprete? ;)
Calvin's Hobbies

1
@Optimizer funziona: pastebin.com/raw.php?i=h73q58FN
coredump

Risposte:


16

CJam, 157 byte

{: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}

Provalo online:

Spiegazione

Cercare di formattare questo con rientro e commenti appropriati richiederebbe probabilmente un'eternità, quindi fornirò solo un riepilogo algoritmico.

Il codice è un blocco, analogico di CJam a funzioni anonime. Il blocco prevede la stringa di programma e l'elenco degli input nello stack quando eseguito.

L'inizializzazione consiste in tre passaggi. Innanzitutto, l'elenco di input viene salvato. Quindi, ogni carattere nel programma non significativo viene rimosso e il risultato viene suddiviso in un elenco di righe e salvato. Infine, l'elenco delle variabili viene inizializzato. Questo elenco associa ogni variabile, indicizzata per lunghezza del nome, al suo valore diviso per 9 (una variabile non può mai contenere un valore che non è un multiplo di 9 e tutte le operazioni tranne goto beneficiano di questa modifica). L'elenco è inizializzato fino alla lunghezza della linea più lunga, che è un limite superiore sul nome variabile più lungo presente. C'è anche un po 'di inizializzazione implicita dovuta ai valori delle variabili iniziali: il numero di riga è 0 e l'indice di input è -1.

L'interprete è implementato come ci si aspetterebbe: un ciclo che legge la riga successiva, incrementa il numero di riga ed esegue la riga mentre il numero di riga punta a una riga esistente. L'analisi delle linee verifica innanzitutto che la linea non sia vuota, quindi si ramifica in base al fatto che l'arity sia 1 o> 1, quindi si ramifica in base alla presenza di uno spazio iniziale. Questi quattro rami emulano le quattro operazioni (escluso il non-op) in un modo per lo più semplice, anche se golfizzato in modo aggressivo come tutto il resto. Forse un'ottimizzazione della nota è che, poiché una sequenza di input valida dovrebbe sempre produrre un elemento di tipo previsto dal programma, ho omesso di creare casi separati per l'input in base alla lunghezza del nome della variabile. Si presume semplicemente che l'elemento letto dall'elenco di input sia del tipo previsto.


15
+1. Piuttosto corto. Ora, puoi scrivere un interprete CJam in 99 ? ;-)
coredump

9
@coredump *
Runer112

Accidenti, posso ottenere solo 195 e poi ho perso la speranza e ho rinunciato: P
Ottimizzatore

Questo non richiede il modulo corretto quando si stampano valori negativi. Questo può essere risolto sostituendolo 128%con 128,=.
Martin Ender,

26

Python 3, 421 414 410 404 388 395 401 byte

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='')

Praticamente solo un'implementazione letterale delle specifiche, ridimensionata per quanto posso ottenerla.

Esegui dalla riga di comando fornendo un file di codice sorgente 99 come unico argomento (ad esempio l'ultimo esempio dall'OP):

> python3 ninetynine.py countdown.txt
G11G10G9G8G7G6G5G4G3G2G1G
>

Come bonus aggiuntivo, ecco un'implementazione (piuttosto scadente) di "99 bottiglie" in 99 : http://pastebin.com/nczmzkFs


1
@DLosc: riguardo al tuo primo punto: anche io ho pensato che elsedopo che un numero poteva essere rimosso, ma quando l'ho provato prima ho avuto un errore di sintassi. I tuoi altri consigli sono comunque molto apprezzati!
Mac

3
@coredump: nel modo in cui viene scritta la specifica, ogni variabile avrà sempre un valore divisibile per nove. Ho trovato più conciso consentire alle variabili di assumere qualsiasi valore e di moltiplicare / dividere per nove solo se necessario (in particolare, nella gotoroutine e quando si ottiene il valore predefinito della variabile). Per quanto riguarda un utente della lingua, non fa alcuna differenza.
Mac

2
Non lo elsestesso, solo lo spazio prima di esso. Es 3*n+1if n%2else n//2.
DLosc

1
@DLosc: scusa, ho sbagliato a dire la parola - intendevo davvero lo spazio, non il else. Ad esempio, ho provato a sostituire print(w if L(e)%2 else chr(w%128))con print(w if L(e)%2else chr(w%128))e ho ottenuto un'eccezione di sintassi.
Mac

1
Strano: ho provato su ideone.com e ha funzionato, ma hai ragione, non funziona nell'interprete Python3 attuale (3.4.0 su Ubuntu). Questo post sui suggerimenti chiarisce: un numero seguito da un token alfabetico funziona in generale, ma non per i token che iniziano con eo Ee (dai commenti) non per 0ornessuno dei due.
DLosc

16

Lisp comune, 1180 857 837 836 byte

So che non vincerà, ma mi sono divertito a giocare a golf. Sono riuscito a rimuovere 343 byte, che sono più di due 99 interpreti scritti in CJam.

Inoltre, in modo abbastanza divertente, più provo a comprimerlo, più sono convinto che per Common Lisp, è più breve compilare il codice piuttosto che provare a interpretarlo al volo.

(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)>))))
  • l'analisi lessicale e la generazione del codice sono interlacciate: non memorizzo la rappresentazione interna, ma elaboro direttamente ogni riga.
  • c'è un singolo tagbodyper eseguire 2 loop:

     (... (tagbody % ... (go %) $ ... (go $)) result)
    
  • le variabili locali sono dichiarate in &aux

  • non generare una chiusura, ma direttamente il codice interpretato
  • eccetera.

Ungolf, commentato

(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)>))))

Usiamo input / output standard durante la valutazione, nel senso che usiamo standard reade princfunzioni. Quindi, il codice risultante può essere reso eseguibile dalla riga di comando, come mostrato di seguito.

Gli input non sono completamente ben disinfettati quando si eseguono 99 programmi: si presume che l'utente sappia che tipo di valori sono previsti.

L'unico sovraccarico di runtime possibile può verificarsi quando si salta, poiché è necessario valutare il valore di una variabile e associare tale valore a un'etichetta. Tranne quello, l'interprete deve essere abbastanza efficiente.

Basato sull'intelligente osservazione da Mac che non abbiamo bisogno di dividere e moltiplicare per 9 ogni volta, la versione attuale riesce a non dividere né moltiplicare per 9 durante l'esecuzione.

Esempio

Se lo sostituiamo defmacrocon defun, vediamo il codice generato. Per esempio:

(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

")

Ecco il codice risultante:

(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)))
   >))

Quando eseguito, stampa "G11G10G9G8G7G6G5G4G3G2G1G"

Riga di comando

Possiamo costruire un eseguibile scaricando un core e specificando la toplevelfunzione. Definire un file denominato in boot.lispcui è stato inserito il defmacroe quindi scrivere quanto segue:

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

L'esecuzione sbcl --load boot.lispdà il seguente 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]

Quindi, eseguendo il programma 99 compilato :

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

99 bottiglie

Se sei interessato, ecco il codice compilato per il programma 99 bottiglie scritto nella risposta di Mac : http://pastebin.com/ZXe839CZ (questa è la vecchia versione in cui abbiamo jmped endetichette, una lambda circostante e un'aritmetica più bella).

Ecco un'esecuzione con la nuova versione, per dimostrare che funziona ancora: http://pastebin.com/raw.php?i=h73q58FN


6

TI-84 Basic (Script per calcolatrice), 376 373 377 381 byte

Se funziona su un calcolatore TI-84, sarai in grado di usarlo su un test standardizzato ... quindi è utile;)

Versione minima del sistema operativo: 2,53 MP (MathPrint) a causa della sigma della somma

#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

Le linee guida ASCII di PS non potevano essere seguite esattamente, ma in TI-Basic :c'è una nuova riga. Pertanto, tutte le nuove righe effettive nel codice indicano che :o #all'inizio di ogni riga non sono richiesti. I token di inizio :e #differenziano solo tra commenti e codice.

Dump esadecimale originale (376 byte)

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

Modifica n. 1 - Ottimizzato 3 byte usando l'osservazione di Mac Modifiche n. 2 e n. 3 - Corretti bug individuati da Runer112.


11
Essere facili da usare in situazioni stressanti come i test standardizzati è esattamente ciò per cui ho progettato 99 .
Calvin's Hobbies,

1
Posso suggerire di usare un personaggio diverso, come #, per i commenti? (NB: i commenti nel codice attuale sono implementati come una riga con solo una stringa non
chiusa

8
Hai davvero provato a eseguirlo? Non l'ho fatto, ma solo osservandolo un po 'di più, ho notato quello che sembra essere almeno una mezza dozzina di bug. Ad esempio: le variabili non sono inizializzate con i loro valori, l' Ansinput viene sovrascritto, quindi Ans->Str0alla riga 6 si verificherà un errore, ci sono più istanze in cui l'argomento lunghezza di un sub()comando può essere zero che provoca un errore, Ansalla riga 11 sarà una stringa così Ans-Jsbaglierà ... E ho dato un'occhiata solo alla prima metà del programma.
Runer112

1
@Timtech Questo lascia comunque gli altri problemi. Come ho già detto, c'è la mancanza di I / O dei caratteri, la mancanza di inizializzazione variabile e più istanze in cui un sub()comando può avere lunghezza zero e generare un errore. E una volta sub()risolte le invocazioni, temo che possa rivelare più problemi.
Runer112

1
@Timtech Mi riferivo a questo: "Per impostazione predefinita, ogni variabile è assegnata alla propria rappresentazione numerica. Quindi, a meno che non sia stata riassegnata, il valore della variabile 9è il numero 9 e il valore della variabile 99è il numero 99, e così via." E stringhe di lunghezza 0 possono essere prodotte in modo simile "", ma è una specie di bug che praticamente nessun comando di manipolazione di stringhe può consumare o produrre una stringa vuota, incluso sub().
Runer112,

5

C 426 458 481 497

Modifica Forse sto andando troppo lontano, ma questo funziona con Visual C: rimosso stdio.h, usando int invece di FILE * per fopen e getc

Modifica 2 Riordina fase di esecuzione, più disordine, 32 caratteri salvati

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);
}

Programma console autonomo, nome del programma preso dalla riga di comando e input / output tramite console.

K&R vecchio stile, tipo predefinito int per parametri e parametri globali. Supponendo che EOF sia definito -1 (come in ogni implementazione C di cui sono a conoscenza)

Compila con avvisi con Visual Studio 2010 (progetto Win ++ console C ++, compila come C) Compila su Ideone, ma non può essere eseguito poiché necessita di un file.

Primo passo, il codice sorgente viene letto e analizzato, ogni riga viene memorizzata come una sequenza di numeri interi basata sui numeri di 9s. Se è presente uno spazio vuoto iniziale, il primo numero è negativo. Quindi: 9 BLAH 99 9a9bb9c9( 9 99 9999) diventa -1,2,4 C'è una scorciatoia - non così legale: tutti i codici ASCII inferiori a '' sono considerati newline.

In questo passaggio tutte le variabili utilizzate sono preinizializzate.

La fase di esecuzione segue le specifiche, senza fronzoli, salvo la memorizzazione dei numeri divisi per 9.

Stesso codice più leggibile (spero), aggiunti spazi e nuove righe

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);
}

1
Funziona anche con GCC 4.8.2. Compila come C99!
EMBLEMA

4

Haskell, 550 byte

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

Esempio eseguito con il programma "conto alla rovescia" memorizzato nel file i.99

$ ./99 i.99
G11G10G9G8G7G6G5G4G3G2G1G

Versione non golfata:

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

4

JavaScript (ES6) 340 352

Una funzione con 2 parametri

  • codice del programma come stringa multilinea
  • input come un array

Il terzo parametro facoltativo (predefinito 10k) è il numero massimo di iterazioni - non mi piace un programma che funzioni per sempre

JSFiddle Per testare

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

4

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

Lo script è un misto di q e k, quindi prima di tutto definisco alcune parole chiave q che voglio usare più volte nelle funzioni k. (fondamentalmente #define macro)

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

f legge il file passato nel programma e rimuove i caratteri non necessari

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 prende un elenco / vettore e moltiplica gli indici dispari per -1

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

b è solo una funzione vuota, utilizzata per le linee non operative

p è la funzione di stampa.

Kè una funzione che esamina una variabile. Se la variabile esiste, la restituisce, altrimenti restituisce solo il valore letterale.

//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 è la funzione di assegnazione.

g è la funzione goto.

r accetta una stringa e decide quale operazione deve essere applicata.

E infine, ho semplicemente scorrere l' felenco delle stringhe, con ncome iteratore. La funzione goto si aggiorna nse necessario.


3

Perl, 273 266 255 244 238

Le interruzioni di riga sono state aggiunte per maggiore chiarezza.

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

Nome del programma preso dalla riga di comando:

$ perl 99.pl 99beers.99

Ogni riga del programma viene convertita in codice Perl, ad esempio:

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

Più dettagli

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 $_)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.