31
\$\begingroup\$

The goal of this code golf is to create an interpreter for the programming language HQ9+
There are 4 commands in this programming language:

  • H - prints "Hello, world!"
  • Q - prints the source code of the program
  • 9 - prints the lyrics of the song "99 bottles of beer"
  • + - increments the accumulator

The rules:

  1. Because the accumulator isn't defined, you can ignore the command +
  2. Your program should prompt for input (this input will be the source code) or the source code is read from a file
  3. You are not allowed to put the lyrics of "99 bottles of beer" in a file, and read the text from the file
  4. You are not allowed to use a compression algorithm such as GZip or BZip to compress the song text: you have to create your own compression algorithm. This doesn't have to be a complex algorithm, but try to compress the text as much as possible (remember: this is a code-golf, the code with the fewest bytes wins)
  5. If the source code contains a char that isn't H, Q, 9 or +, then output "Source code contains invalid characters" before running any commands! This means that, if the source code is H1 for example, then don't output Hello, world! before you output Source code contains invalid characters. No, immediately output Source code contains invalid characters
  6. If your interpreter accepts both uppercase and lowercase characters, then you get -8 to your character count

Good luck!

\$\endgroup\$
9
  • 5
    \$\begingroup\$ Rule 4 doesn't make sense at the "why would he prohibit that?!" level; rule 5 doesn't make sense at the "what does he mean?!" level. \$\endgroup\$ Commented Oct 22, 2013 at 18:44
  • 5
    \$\begingroup\$ How does rule 5 not make sense? \$\endgroup\$
    – Mike C
    Commented Oct 22, 2013 at 19:37
  • 16
    \$\begingroup\$ Rule 5 does not make sense because it breaks the HQ9+ spec. \$\endgroup\$
    – boothby
    Commented Oct 23, 2013 at 2:27
  • 5
    \$\begingroup\$ An interpreter in HQ9+ should automatically win. \$\endgroup\$ Commented Jan 3, 2015 at 22:51
  • 6
    \$\begingroup\$ do I get extra points if it accepts uppercase +? \$\endgroup\$
    – 12Me21
    Commented Feb 11, 2017 at 16:49

18 Answers 18

16
\$\begingroup\$

APL (Dyalog) (326 - 8 = 318)

Paste it in an editor window and call H. Input is taken from the keyboard.

H
⎕←'Source code contains invalid characters'/⍨~∧/'HhQq9+'∊⍨V←⍞
B←' of beer'
W←' on the wall'
L←⎕TC[2]
Z←{k←' bottle','s'/⍨⍵≠1⋄⍵<0:'99',k⋄⍵=0:'No',k⋄k,⍨⍕⍵}
{⍵∊'Hh':⎕←'Hello, world!'
⍵∊'Qq':⎕←V
⍵∊'9':{⎕←G,K,(G←Z⍵),B,L,(⊃'Go to the store and buy some more' 'Take one down and pass it around'[1+×⍵]),L,(Z⍵-1),K←B,W,L}¨1-⍨⌽⍳100}¨V
\$\endgroup\$
7
\$\begingroup\$

C, 599 587 487 481 467 characters

I'm sure this can be beaten. I'm using C after all. There's a reason you don't see a lot of winning golf entries written in C. This is now 467 characters thanks to criminal abuse of #define.

Specify the HQ9+ source file as a command line argument.

EDIT: Now accepts the source from stdin, not a file. Start the program, start typing the code, CTRLC when done to run it.

It compiles at least in MinGW/GCC with: gcc -o hq9+.exe hq9+.c

Should work in MSVC, but I didn't feel like creating a whole solution just for this. :)

#define B "bottles of beer"
#define C case
#define P printf
#define R break
a,i,j,s;
main(){
char p[9999],c;
for(;;) {
    switch(c=getch()){
    C 'H':C 'Q':C '9':C '+': C 3: R;
    default:P("Source code contains invalid characters\n");
    }
    if (c==3) R;
    p[s++]=c;
}
for(i=0;i<s;i++){
    c = p[i];
    switch(c){
    C 'H':
        P("Hello world!");
        R;
    C 'Q':
        for(j=0;j<s;j++)putchar(p[j]);
        R;
    C '9':
        j=99;
        while(j){
            P("%d "B" on the wall,\n%d "B".\nTake one down, pass it around,\n%d "B".\n",j,j,j-1);
            j--;
        }
        R;
    C '+':
        a++;
    }
}
}

Or:

#define B "bottles of beer"
#define C case
#define P printf
#define R break
a,i,j,s;main(){char p[9999],c;for(;;){switch(c=getch()){C 'H':C 'Q':C '9':C '+': C 3: R;default:P("Source code contains invalid characters\n");}if (c==3) R;p[s++]=c;}for(i=0;i<s;i++){c = p[i];switch(c){C 'H':P("Hello world!");R;C 'Q':for(j=0;j<s;j++)putchar(p[j]);R;C '9':j=99;while(j){P("%d "B" on the wall,\n%d "B".\nTake one down, pass it around,\n%d "B".\n",j,j,j-1);j--;}R;C '+':a++;}}}
\$\endgroup\$
3
  • \$\begingroup\$ Got it down to 487 characters by using stdin instead of a file for input. \$\endgroup\$
    – Mike C
    Commented Oct 22, 2013 at 19:36
  • \$\begingroup\$ Make that 464 by abusing #define. \$\endgroup\$
    – Mike C
    Commented Oct 22, 2013 at 19:47
  • \$\begingroup\$ You could get rid of spaces, like the ones after C and a couple before R;. Could also shave off 8 chars with p[s++]=0; after the input loop to simplify printing p. \$\endgroup\$ Commented Oct 23, 2013 at 5:34
6
\$\begingroup\$

Mathematica, 349 346 341 chars

h = If[StringMatchQ[#, ("H" | "Q" | "9" | "+") ...], 
   b = If[# > 0, ToString@#, "No"] <> " bottle" <> 
      If[# == 1, "", "s"] <> " of beer" &; 
   w = b@# <> " on the wall" &; 
   Print /@ 
    StringCases[#, {"H" -> "Hello, world!", "Q" -> #, 
      "9" -> "" <> 
        Table[w@n <> ",\n" <> b@n <> 
          ".\ntake one down, pass it around,\n" <> w[n - 1] <> 
          If[n == 1, ".", ".\n\n"], {n, 99, 1, -1}]}];, 
   "Source code contains invalid characters"] &
\$\endgroup\$
2
  • \$\begingroup\$ Very impressive but there appear to be some minor gliches.'h["Q"] outputs "Q". h["9"] works, but h[9] outputs the whole program (I don't understand how.) \$\endgroup\$
    – DavidC
    Commented Oct 23, 2013 at 15:18
  • \$\begingroup\$ @DavidCarraher It outputs code due to how StringMatchQ and If fails: If[StringMatchQ[3, "a"], x, y] \$\endgroup\$
    – ssch
    Commented Oct 23, 2013 at 18:01
3
\$\begingroup\$

J - 444 bytes

I liked the number, so stopped golfing. Here 'ya go, single expression function!

f=:'Source code contains invalid characters'"_`('Hello, world!'"_`[`((((s=:(<:@[s],L,(v,LF,'Take one down and pass it around, '"_,b@<:,' of beer on the wall.'"_)@[,''"_)`(],(L=:LF,LF),(v=:1&b,' of beer on the wall, '"_,b,' of beer.'"_)@[)@.([<1:))''"_),LF,'Go to the store and buy some more, '"_,(b=:({&'Nn'@([=0:),'o more'"_)`(":@])@.(]>0:),{.&' bottles'@(8:-=&1@])),' of beer on the wall.'"_)@(99"_))`]@.('HQ9+'&i.@])"0 1 0])@.(*./@e.&'HQ9+')

Examples:

   hq9 '9QHHQ+'
99 bottles of beer on the wall, 99 bottles of beer.
Take one down and pass it around, 98 bottles of beer on the wall.

98 bottles of beer on the wall, 98 bottles of beer.
Take one down and pass it around, 97 bottles of beer on the wall.

...

3 bottles of beer on the wall, 3 bottles of beer.
Take one down and pass it around, 2 bottles of beer on the wall.

2 bottles of beer on the wall, 2 bottles of beer.
Take one down and pass it around, 1 bottle of beer on the wall.

1 bottle of beer on the wall, 1 bottle of beer.
Take one down and pass it around, no more bottles of beer on the wall.

No more bottles of beer on the wall, no more bottles of beer.
Go to the store and buy some more, 99 bottles of beer on the wall.
9QHHQ+
Hello, world!
Hello, world!
9QHHQ+
+
   hq9 '9QHHaQ'
Source code contains invalid characters
\$\endgroup\$
2
  • \$\begingroup\$ And no, I won't write an explanation for this. Too long. \$\endgroup\$
    – seequ
    Commented Jun 20, 2014 at 12:38
  • \$\begingroup\$ Wait, why is this downvoted? \$\endgroup\$
    – seequ
    Commented Mar 20, 2015 at 18:41
3
\$\begingroup\$

Fortran 528 470 481

It requires compiling with -fpp flag (+3 to score)1 to use preprocessing directives (which saves way more than 3 chars, so I'm totally okay with that). It is also case insensitive, so there's -8 :D. Saved 5 chars by not preprocessing the endif as that is used once anyway.

Requires the file to have a .F90 extension (makes sense to call it hq9+.F90) so that the compiler forces preprocessing. The code is case-sensitive; making it case-insensitive adds something like 16 characters, so it's not really worth it to save 8 characters. My previous answer did not account for the changing plurals in bottles for 9; this version corrects it (and sadly adds a lot more characters).

#define P print*,
#define W " of beer on the wall"
#define N print'(x,i0,a,a)',
#define e enddo
#define S case
#define J len(trim(c))
character(len=99)::c,b=" bottles";read*,c;do i=1,J;if(all(c(i:i)/=["H","Q",'9',"+"])) then;P"Source code contains invalid characters";exit;endif;e;do i=1,J;select S(c(i:i));S("H");P"Hello, world!";S("Q");P c;S("9");l=8;do k=99,1,-1;N k,b(1:l),W;N k,b(1:l)," of beer";P "Take one down, pass it around";if(k==2)l=l-1;if(k==1)exit;N k-1,b(1:l),W;P"";e;P"No more",trim(b),W;S default;endselect;e;end

Looks a lot better ungolfed & non-preprocessed (probably because you can see what's going on):

program hq9
   character(len=99)::c,b=" bottles"
   read*,c
   do i=1,len(trim(c))
! change the below to ["H","h","Q","q","9","+"] to be case-insensitive
      if(all(c(i:i)/=["H","Q","9","+"]))then
         print*,"Source code contains invalid characters"
         exit
      endif
   enddo
   do i=1,len(trim(c))
      select case(c(i:i))
        case("H")                ! change to case("H","h") for case-insensitive
           print*,"Hello, world!"
        case("Q")                ! change to case("Q","q") for case-insensitive
           print*, c
        case("9")
           l=8
           do k=99,1,-1
              print'(x,i0,a,a)', k,b(1:l)," of beer on the wall"
              print'(x,i0,a)', k,b(1:l)," of beer"
              print*,"Take one down, pass it around"
              if(k==2) l=l-1
              if(k==1) exit
              print'(x,i0,a)', k-1,b(1:l)," of beer on the wall"
              print*,""
           enddo
           print*,"No more",trim(b)," of beer on the wall"
        case default
           ! do nothing
      endselect
   enddo
end program hq9
\$\endgroup\$
1
  • \$\begingroup\$ I think it's acceptable to require a specific extension to let the compiler know what it's compiling. An analog might be requiring a C++ file to be named .cc to avoid needing the -lstdc++ flag. \$\endgroup\$
    – primo
    Commented Jun 19, 2014 at 15:26
3
\$\begingroup\$

Perl, 325 - 8 = 317

sub p{print@_}$_=<>;$a=' on the wall';$b=' bottle';$e=' of beer';$c='Take one down, pass it around';if(!/^[hqHQ9+]+$/){p"Source code contains invalid characters";exit}$k=$_;for(/./g){/H|h/&&p"Hello, World!";if(/9/){$i=99;$d=$b."s$e";while($i>0){p"$i$d$a
$i$d
$c
";$q=--$i==1?'':'s';$d="$b$q$e";$i||=No;p"$i$d$a
"}}/Q|q/&&p$k}

Expanded:

sub p{print@_}
$_=<>;
$a=' on the wall';
$b=' bottle';
$e=' of beer';
$c='Take one down, pass it around';
if(!/^[hqHQ9+]+$/){
    p"Source code contains invalid characters";
    exit
}
$k=$_;
for(/./g){
    /H|h/&&p"Hello, World!";
    if(/9/){
        $i=99;
        $d=$b."s$e";
        while($i>0){
            p"$i$d$a
$i$d
$c
";
            $q=--$i==1?'':'s';
            $d="$b$q$e";
            $i||=No;
            p"$i$d$a
"
        }
    }
    /Q|q/&&p$k
}
\$\endgroup\$
3
\$\begingroup\$

Python 2, 452 453 443 bytes

q=raw_input()
if set(q)-set('HQ9+'):print'Source code contains invalid characters'
b=' bottles of beer'
b=[b,b.replace('s','')]
w=[a+' on the wall'for a in b]
t='Take one down, pass it around,\n'
c={'H':'Hello, world!','Q':q,'9':''.join(`i`+w[i<2]+',\n'+`i`+b[i<2]+'.\n'+t+(`i`if i>1 else'No')+w[0]+'.\n'for i in range(1,100)[::-1])+'No'+w[0]+',\nNo'+b[0]+'.\n'+'Go to the store, buy some more,\n99'+w[0]+'.'}
for d in q:
 if d in c:print c[d]

Try it online!

Bleh. It was already bleh, but then I discovered a bug that cost me a byte to fix. Bleh.

Saved some characters by not exiting on invalid input like I thought you were supposed to.

Explained version coming shortly. I gave up. I barely even understand how this monstrosity works any more. If there's demand I'll give it another go but until then it's not happening.

\$\endgroup\$
5
  • \$\begingroup\$ You don't exit on the invalid character, just display the message, so you can save 7 chars there. \$\endgroup\$
    – Kyle Kanos
    Commented Jun 19, 2014 at 16:48
  • \$\begingroup\$ @KyleKanos Oh, I misunderstood that. Thanks! \$\endgroup\$ Commented Jun 19, 2014 at 16:57
  • \$\begingroup\$ Your lyrics aren't right- currently it says: 99 bottles of beer on the wall, 99 bottles of beer. Take one down, pass it around, *99* bottles of beer on the wall. It's supposed to say: 99 bottles of beer on the wall, 99 bottles of beer. Take one down, pass it around, *98* bottles of beer on the wall. \$\endgroup\$
    – Oliver Ni
    Commented Jan 4, 2015 at 17:28
  • \$\begingroup\$ It also gives an error when I enter +. You're supposed to ignore +, not give an error \$\endgroup\$
    – Oliver Ni
    Commented Jan 4, 2015 at 17:31
  • \$\begingroup\$ Also there's not supposed to be anything between outputs. Like if I enter HQHH it should say Hello World!HQHHHelloWorld!Hello World! \$\endgroup\$
    – Oliver Ni
    Commented Jan 4, 2015 at 17:33
2
\$\begingroup\$

Ruby, 364 360 - 8 = 352

Still has lots of room for improvement. 99 bottles code stolen from here.

p=gets.upcase
if p=~/[^HQ9+
]/
puts"Source code contains invalid characters"else
p.each_char{|x|case x
when ?H
puts"Hello, world!"
when ?Q
puts p
when ?9
def c;"#{$n} bottle#{'s'if$n>1} of beer on the wall"end
($n=99).times{puts"#{c}, #{c[0..-13]}.
#{$n<2?"Go to the store and buy some more":"Take one down and pass it around"}, #{$n=($n-2)%99+1;c}.

"}end}end
\$\endgroup\$
6
  • \$\begingroup\$ There's supposed to be No more bottles of beer on the wall after the 1 bottle section. then it's Go to the store... \$\endgroup\$
    – Oliver Ni
    Commented Jan 4, 2015 at 17:44
  • \$\begingroup\$ 1 bottle of beer on the wall, 1 bottle of beer on the wall, take one down, pass it around, no more bottles of beer on the wall. No more bottles of beer on the wall, no more bottles of beer, go to the store, buy some more, 99 bottles of beer on the wall. \$\endgroup\$
    – Oliver Ni
    Commented Jan 4, 2015 at 17:46
  • \$\begingroup\$ @Oliver The question does not specify the exact lyrics of the song. In no place does it require the "no more" section to be included. \$\endgroup\$
    – Doorknob
    Commented Jan 4, 2015 at 17:46
  • \$\begingroup\$ Ok, but also, there's not supposed to be a newline between outputs. \$\endgroup\$
    – Oliver Ni
    Commented Jan 4, 2015 at 17:47
  • \$\begingroup\$ @Oliver Where does the challenge say that? \$\endgroup\$
    – Doorknob
    Commented Jan 4, 2015 at 17:47
2
\$\begingroup\$

Haskell, 298

main=interact g
g s|all(`elem`"HQ9+")s=s>>=(%s)|0<1="Source code contains invalid characters"
'H'%_="hello World!"
'Q'%s=s
'9'%_=drop 32$d 99
_%_=""
k=" bottles of beer on the wall"
d 0="No more"++k++"."
d n|a<-shows n k=[a,".\n\n",a,",\n",take 18a,".\ntake one down, pass it around\n",d$n-1]>>=id

this is quite straightforward. % returns a command's output (given the source cod for use on Q). d returns the 99 bottles song with a junk line at the start for golfing reasons. everything is wrapped in an interact (you might want to use g instead of main when testing)

\$\endgroup\$
6
  • \$\begingroup\$ How should I run it? I tried pasting it in WinGHCi, but I get <interactive>:8:5: parse error on input ‘=’. \$\endgroup\$
    – ProgramFOX
    Commented Jan 2, 2015 at 8:47
  • \$\begingroup\$ @ProgramFOX i have no idea... Ill check when i can \$\endgroup\$ Commented Jan 2, 2015 at 9:20
  • \$\begingroup\$ @ProgramFOX sorry it took long :-). you're not using GHCi properly. in GHCi you can't irectly input definitions as it expects expressions. you can either use a let to insert definitions (this breaks when inserting multiple definitions not separated by ;) or run the code from a file. \$\endgroup\$ Commented Jan 4, 2015 at 20:13
  • \$\begingroup\$ I see. I still fail at trying to run it though; when I load the file and call main, it says that the source code contains invalid characters when I try to execute H. When I try to run g instead of main, it immediately gives an error. \$\endgroup\$
    – ProgramFOX
    Commented Jan 5, 2015 at 16:46
  • \$\begingroup\$ @ProgramFOX main doesn't work because the newline appended is not a legal command. That's why i recommended using g instead. As for g, does putStrLn $ g "H" not work properly? \$\endgroup\$ Commented Jan 5, 2015 at 17:15
2
\$\begingroup\$

Python 2, 340 - 8 = 332

s,o=raw_input(),''
for z in s:
 if z in'Hh':o+='Hello World!'
 elif z in'Qq':o+=s
 elif'9'==z:
  i=298
  while~-i:print i/3or 99,'bottle'+'s of beer on the wall.\n'[2<i<6:9+i%3*12]+'..\nGToa kteo  otnhee  dsotwonr,e ,p absusy  isto maer omuonrde,,'[(i>3)+i%3*68::2];i-=1
 elif'+'!=z:o='Source code contains invalid characters';break
print o
\$\endgroup\$
0
1
\$\begingroup\$

Java, 546 bytes

This is my first code golf submission. I am sure we could do more with it. It reads the input as the command line argument. Beer code "borrowed" from "99 Bottles of Beer" java answer (creative commons)

class a{public static void main(String[] a){if(a[0].matches("^[HQ9\\Q+\\E]+$")){for(char c:a[0].toCharArray()){if(c=='H')p("Hello, world!");if(c=='Q')p(a[0]);if(c=='9')b();}}else{System.out.println("Source code contains invalid characters");}}static void p(String s){System.out.println(s);}static void b(){String b=" of beer",c=" on the wall",n=".\n",s;for(int i=100;i-->1;){s=" bottle"+(i>1?"s":"");p(i+s+b+c+", "+i+s+b+n+(i<2?"Go to the store and buy some more, 99":"Take one down and pass it around, "+(i-1))+" bottle"+(i!=2?"s":"")+b+c+n);}}}

Let me know if command line args is not acceptable. This was a lot of fun!

\$\endgroup\$
1
  • \$\begingroup\$ CLA are fine. Nice answer. \$\endgroup\$
    – Riker
    Commented Mar 14, 2016 at 0:07
1
\$\begingroup\$

Lua 443 - 8 = 435 464 - 8 = 456

I managed to save 21 characters by using multiple if-ends instead of if-elseif-end. I also had some extra white spaces floating around after a few ).

p=print o=" of beer"t=" on the wall"B=" bottle"b=" bottles"l=io.read("*l");g=l:lower()if g:match"[^hq9+]"then p("Source code contains invalid characters")end for i=1,#g do s=g:sub(i,i)if s=='h'then p("Hello, world")end if s=='q'then p(l)end if s=='9'then n=99 repeat p(n..b..o..t..", "..n..b..o)n=n-1 p("Take one down, pass it around, "..n..b..o..t..".")p()until n==1 p("1"..B..o..t..", 1"..B..o)p("No more"..b..o..t..", no more"..b..o)end end

I'm fairly happy with this one, even though it's not much shorter than my Fortran answer. The 99 bottles of beer code has been modified from this answer by Alessandro. Ungolfed, we have

-- reuse stuff
p=print
o=" of beer"
t=" on the wall"
B=" bottle"
b=" bottles"
-- read the line & then lowercase it for case insensitivity
l=io.read("*l");g=l:lower()
if g:match"[^hq9+]" then -- horray for basic regex
   p("Source code contains invalid characters")
end
for i=1,#g do
   s=g:sub(i,i)               -- take substring
   if s=='h' then p("Hello, world") end
   if s=='q' then p(l) end
   if s=='9' then
      n=99
      repeat
         p(n..b..o..t..", "..n..b..o)
         n=n-1
         p("Take one down, pass it around, "..n..b..o..t..".")
         p()
      until n==1
      p("1"..B..o..t..", 1"..B..o)
      p("No more"..b..o..t..", no more"..b..o)
   end
end
\$\endgroup\$
4
  • \$\begingroup\$ The code to lowercase the input costs 12 characters, so the bonus is not worth it. \$\endgroup\$
    – user344
    Commented Jan 3, 2015 at 19:21
  • \$\begingroup\$ You don't need to lowercase the input, you can just do something like if s in'Hh' \$\endgroup\$
    – Oliver Ni
    Commented Jul 30, 2015 at 4:40
  • \$\begingroup\$ It saves 8 chars \$\endgroup\$
    – Oliver Ni
    Commented Jul 30, 2015 at 4:41
  • \$\begingroup\$ @MCParadox: Er, that's Python you're thinking of, Lua's in is for for-loop iterators and not comparisons; you'll get an error if you stick that in there. There are three choices here: (1) use match at each line, (2) user s=='h'or s=='H' at each line, (3) do it the way I did it. Clearly (3) is shorter than (1) and (2). \$\endgroup\$
    – Kyle Kanos
    Commented Aug 3, 2015 at 14:49
1
\$\begingroup\$

Julia, 362

s = chomp(readline(STDIN))
l=""
z=" of beer"
q,r,w,t=" bottles$z"," bottle$z"," on the wall.\n","take one down, pass it around,\n"
for j=99:-1:2
b="$j$q"
l*="$b$w$b.\n$t"
end
l*="1$r$(w)1$r.\n$(t)No$q$w"
p=println
all(c->c in "HQ9+", s)||p("Source code contains invalid characters")
for c in s
    c=='Q'&&p(s)
    c=='H'&&p("Hello, world!")
    c=='9'&&p(l)
end
\$\endgroup\$
1
\$\begingroup\$

Tcl, 515

set d [read stdin]
if {![regexp {^[hq9\+HQ]*$} $d]} {puts "Source code contains invalid characters"}
lmap c [split $d {}] {set b { bottles of beer}
switch -- $c H {puts "Hello, world"} Q {puts $d} 9 {for {set i 99} {$i>2} {incr i -1} {puts "$i$b on the wall,
$i$b.
Take one down, pass it around,
[expr $i-1]$b on the wall.
"}
puts "2$b on the wall,
2$b.
Take one down, pass it around,
1 bottle of beer on the wall.

1 bottle of beer on the wall,
1 bottle of beer.
Take one down, pass it around,
No$b on the wall."}}

Just a bit golfed, still smaller than C and the correct 99 Bottles of beer end verse.

\$\endgroup\$
2
  • \$\begingroup\$ Okay, now that one's legit. Good job, you beat me. I should stop using C for golf. \$\endgroup\$
    – Mike C
    Commented Oct 22, 2013 at 19:00
  • \$\begingroup\$ You can remove the first line and embed it at the last parameter of regexp. You can get my golfing of 99 beers on codegolf.stackexchange.com/a/109818/29325 \$\endgroup\$
    – sergiol
    Commented Oct 28, 2017 at 23:34
1
\$\begingroup\$

JavaScript (ES6), 385

s=>{n=99,b=' bottle',j=' of beer',d=' on the wall',e='Take one down, pass it around',k='Go to the store, buy some more',l='No',o='s',f=', ';for(i=s.split(m=v='');~n;)v+=[n||l,b,n-1?o:m,j,d,f,n||l,b,n-1?o:m,j,f,n?e:k,f,--n<1?99:n,b,n-1?o:m,j,d,'! '].join(m);return s.match(/[^HQ9\+]/,r='')?'Source code contains invalid characters':[...s].map(c=>({H:'Hello World!',9:v,Q:s})[c]).join``}

Didn't opt for the case-insensitive, would have cost too many characters. Not even close to some of the other entries, but was fun nonetheless!

JavaScript, 344

I made a version with a less complete version of the song:

(function(i){if(s.match(/[^HQ9\+]/)){m='Source code contains invalid characters'}else{n=99,b=' bottles of beer ',d='on the wall',e='take one down, pass it around',f=', ';for(;n;)v+=[n,b,d,f,n,b,f,e,f,--n||'no more',b,d,'! '].join(m);h={H:'Hello World!',Q:arguments.callee+m,9:v};for(;c=i[n++];)m+=h[c]||''}alert(m)})((s=prompt()).split(m=v=''))

but after seeing the other entries (and looking at the actual lyrics, who knew!), I thought it was a bit of a cop out!

\$\endgroup\$
5
  • \$\begingroup\$ It should be Go to the store, buy some more, *99* bottles of beer on the wall, not *No more* bottles of beer on the wall \$\endgroup\$
    – Oliver Ni
    Commented Jan 4, 2015 at 17:35
  • \$\begingroup\$ If you buy some more, how can there still be no more? \$\endgroup\$
    – Oliver Ni
    Commented Jan 4, 2015 at 17:36
  • \$\begingroup\$ And also, Q doesn't work for me. \$\endgroup\$
    – Oliver Ni
    Commented Jan 4, 2015 at 17:39
  • \$\begingroup\$ @Oliver didn't notice you comment on this back then... I totally misunderstood when the Q was supposed to do that the time and provided the source code of the function instead of the input... That was originally my reason for doing it in JS, that part would be easy, however I now know that isn't what's required at all! \$\endgroup\$ Commented Nov 15, 2016 at 17:03
  • 1
    \$\begingroup\$ @Oliver updated the lyrics! \$\endgroup\$ Commented Nov 15, 2016 at 17:23
1
\$\begingroup\$

Excel VBA, 489 Bytes

Unindented:

Sub h(s)
For c=1 To Len(s)
Select Case Mid(s,c,1)
Case "H"
Debug.? "Hello,World!"
Case "Q"
Set v=ActiveWorkbook.VBProject.VBComponents("M").CodeModule:For n=1 To v.countoflines:Debug.? v.Lines(n,1):Next
Case "+"
a=a+1
Case "9"
s=" Bottles of Beer":o=" Bottle of Beer":w=" on the wall":t=". Take 1 down pass it around,":p=",":d=".":For n=99 To 3 Step -1:Debug.? n;s;w;p;n;s;t;n-1;s;w;d:Next:Debug.? 2;s;w;p;2;s;t;1;o;w;d:Debug.? 1;o;w;p;1;o;t;"No";s;w;d
End Select
Next
End Sub

(indented for readability)

Sub h(s)
For c=1 To Len(s)
Select Case Mid(s,c,1)
    Case "H"
        Debug.? "Hello,World!"
    Case "Q"
        Set v=ActiveWorkbook.VBProject.VBComponents("M").CodeModule
        For n=1 To v.countoflines
            Debug.? v.Lines(n,1)
        Next
    Case "+"
        a=a+1
    Case "9"
        s=" Bottles of Beer"
        o=" Bottle of Beer"
        w=" on the wall"
        t=". Take 1 down pass it around,"
        p=","
        d="."
        For n=99 To 3 Step -1
            Debug.? n;s;w;p;n;s;t;n-1;s;w;d
        Next
        Debug.? 2;s;w;p;2;s;t;1;o;w;d
        Debug.? 1;o;w;p;1;o;t;"No";s;w;d
End Select
Next
End Sub

Rename default Module1 to M
call with h "+++++++++Your Code"
This will also work on other office applications, changing ActiveWorkbook to the appropriate document type

\$\endgroup\$
4
  • \$\begingroup\$ note that excel will add spaces, and expand abbreviations, so within excel, the code is 531 chars \$\endgroup\$
    – SeanC
    Commented Oct 23, 2013 at 19:51
  • \$\begingroup\$ Thats 636 Characters \$\endgroup\$
    – Oliver Ni
    Commented Jan 4, 2015 at 17:03
  • \$\begingroup\$ Remove all the whitespace from my indentation, @oliver \$\endgroup\$
    – SeanC
    Commented Jan 4, 2015 at 18:39
  • \$\begingroup\$ You can loose 3 Bytes by converting all instances of For n=1 To to For n=1To (removing whitespace before To) and 4 bytes by converting all instances of Debug.? 2; to Debug.?2; (removing whitespace after ? keyword) \$\endgroup\$ Commented May 31, 2017 at 16:46
1
\$\begingroup\$

C, 562 bytes

char*c="%1d %3$s of %4$s on the %5$s, %1d %3$s of %4$s.\n\0Take one down and pass it around, %2d %3$s of %4$s on the %5$s.\n\0Go to the store and buy some more, %1d %3$s of %4$s on the %5$s.\n";main(int a,char**b){int x=0;for(int i=0;i<strlen(b[1]);i++){if(b[1][i]=='+')x++;else if(b[1][i]=='9'){int k=99;while(1){printf(&c[0],k,k,k==1?"bottle":"bottles","beer","wall");if(k!=1){printf(&c[49],k,k,"bottles","beer","wall");k--;}else{k=99;printf(&c[114],k,k,"bottles","beer","wall");break;}}}else printf("%s",b[1][i]=='H'?"Hello, world!":(b[1][i]=='Q'?b[1]:""));}}

As a full program. First argument is the HQ9+ program. With an actual accumulator. I challanged myself not to use define statements.

Ungolfed version:

char* c = "%1d %3$s of %4$s on the %5$s, %1d %3$s of %4$s.\n\0Take one down and pass it around, %2d %3$s of %4$s on the %5$s.\n\0Go to the store and buy some more, %1d %3$s of %4$s on the %5$s.\n";
main (int a, char** b) {
    int x = 0;
    for (int i = 0; i < strlen(b[1]); i++) {
        if (b[1][i] == '+')
            x++;
        else if (b[1][i] == '9') {
            int k = 99;
            while (1) {
                printf(&c[0], k, k, k == 1 ? "bottle" : "bottles", "beer", "wall");
                if (k != 1) {
                    printf(&c[49], k, k, "bottles", "beer", "wall");
                    k--;
                } else {
                    k=99;
                    printf(&c[114], k, k, "bottles", "beer", "wall");
                    break;
                }
            }
        } else
            printf("%s",b[1][i] == 'H' ? "Hello, world!" : (b[1][i] == 'Q' ? b[1] : ""));
    }
}
\$\endgroup\$
1
1
\$\begingroup\$

GNU C, 429 423 bytes

Compiles and runs correctly on GCC-8.3, without any compiler flags. No other configuration was tested.

It considers a file to be a stream of text up to the first '\n' that it encounters (this just makes testing easier, can be fixed by replacing the 10 in (c=getchar())!=10 with a -1). However, keep in mind that newlines will not be considered valid.

char*b="bottles of beer\0on the wall\0Take one down, pass it around",s[4096];n;Q(){puts(s);}W(){puts("Hello, world!");}B(){for(n=100;--n;){printf("%d %s %s,\n%d %s.\n%s,\n%d %s %s.\n\n",n,b,b+16,n,b,b+28,n-1,b,b+16);}}N(){}l=0,c,i,(*t[])()={[43]=N,[57]=B,[72]=W,[81]=Q};main(){while((c=getchar())!=10){if(c!=43&&c!=57&&c!=72&&c!=81){exit(puts("Source code contains invalid characters"));}s[l++]=c;}for(;i<l;++i)t[s[i]]();}

Degolfed

// we stick all the strings in a single string, null separated.
// We will later index into the right indices to print the right pieces.
char*b="bottles of beer\0on the wall\0Take one down, pass it around";

// Here we store the source code. I chose 4kiB, but this was rather arbitrary.
char s[4096];

// n is the counter for the B function
n;

Q() { // Q for quine
    // puts is shorter than printf and it prints a newline for free
    puts(s);
}

W() { // W for world
    puts("Hello, world!");
}

B() { // B for bottles
    // n is the current count
    for (n = 100; --n;) {
        printf("%d %s %s,\n"    // n bottles of beer on the wall,
               "%d %s.\n"       // n bottles of beer.
               "%s,\n"          // Take one down, pass it around,
               "%d %s %s.\n\n", // n-1 bottles of beer on the wall

               n, b, b + 16, // Index into the right positions
               n, b,         // in the string.
               b + 28, n - 1, b, b + 16);
    }
}

N() {} // N for nothing, we need it for the table

// l is the length of source code
// c is the currently read character
// i is an index
l = 0, c, i;

// This array of function pointers is a bit like a jump table.
// This table tells us which function to call depending on the current char.
// It is essentially a hand-rolled switch statement, but shorter.
// NOTE: the [index]=value syntax is a GNU extension
int(*t[])()={
    [43/* ASCII + */] = N, // do nothing
    [57/* ASCII 9 */] = B, // print 99 bottles
    [72/* ASCII H */] = W, // print hello world
    [81/* ASCII Q */] = Q  // print source
};

main() {
    // consume input until we hit \n. this
    while ((c = getchar()) != 10) {
        // if we find a character not in "HQ9+", we log and exit
        if (c != 43 && c != 57 && c != 72 && c != 81) {
            exit(puts("Source code contains invalid characters"));
        }
        // populate the source code
        s[l++] = c;
    }

    // go over the source code (at this point validated)
    // and call the function that is set in the table
    // We can skip initializing i because it is a static, and statics
    // get implicitly initialized to 0. this saves 3 bytes
    for (; i < l; ++i)
        t[s[i]]();
}

I will admit I cheated a bit with the 99 bottles.

\$\endgroup\$
1

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.