diff --git a/2014/endoh1/.gitignore b/2014/endoh1/.gitignore index 216deeb88..7ba4a572a 100644 --- a/2014/endoh1/.gitignore +++ b/2014/endoh1/.gitignore @@ -1,3 +1,5 @@ main main.c prog +treacle +treacle.c diff --git a/2014/endoh1/Makefile b/2014/endoh1/Makefile index 33f3a0c45..40561a75a 100644 --- a/2014/endoh1/Makefile +++ b/2014/endoh1/Makefile @@ -1,161 +1,262 @@ #!/usr/bin/env make # -# 2014 makefile +# IOCCC 2014 winning entry - endoh1 + +################################################################################ # -# This work by Landon Curt Noll, Simon Cooper, and Leonid A. Broukhis -# is licensed under: +# Regarding individual entries: +# +# IOCCC winning entry code may not work on your system. Even with standards, +# conditions and build environments change, especially when it comes to more +# extreme code such you might find in an IOCCC winner. Moreover, what was +# allowed and liked in one IOCCC contest may no longer be liked/allowed in +# later IOCCC years. +# +# Corrections on winning author and fixing typos in remarks are VERY WELCOME. +# +# GitHub pull requests are welcome: +# +# If you have a patch that might make running an entry work in modern Unix-like +# environments, please create a GitHub pull request for the IOCCC judges to +# consider. +# +# Suggestions and corrections regarding Makefiles, winning author information, +# fixing typos in remarks, and code patches are welcome via opening a GitHub +# issue or better yet, by creating a GitHub pull request for the IOCCC judges +# to consider. To form a GitHub issue or GitHub pull, please visit: +# +# https://github.com/ioccc-src/winner +# +################################################################################ + +# This work by Landon Curt Noll and Leonid A. Broukhis is licensed under: # # Creative Commons Attribution-ShareAlike 3.0 Unported License. # # See: http://creativecommons.org/licenses/by-sa/3.0/ -################ -# tool locations -################ +############################# +# shell used by this Makefile +############################# # SHELL= bash -CP= cp -CPP= cpp -GUNZIP= gunzip -LD= ld -MAKE= make -RM= rm -SED= sed -TAR= tar -TRUE= true + +####################### +# common tool locations +####################### +# +include ../../var.mk # Set X11_LIBDIR to the directory where the X11 library resides # -X11_LIBDIR= /usr/X11R6/lib +#X11_LIBDIR= /usr/X11R6/lib +#X11_LIBDIR= /usr/X11/lib +X11_LIBDIR= /opt/X11/lib # Set X11_INCLUDEDIR to the directory where the X11 include files reside # -X11_INCDIR= /usr/X11R6/include +#X11_INCDIR= /usr/X11R6/include +#X11_INCDIR= /usr/X11/include +X11_INCDIR= /opt/X11/include -# Compiler warnings +# Common C compiler warnings to silence # -#CWARN= -CWARN= -Wall -#CWARN= -Wall -pedantic +#CSILENCE= +CSILENCE= -Wno-empty-body -# compiler standard +# Common C compiler warning flags +# +#CWARN= ${CSILENCE} +#CWARN= -Wall ${CSILENCE} +#CWARN= -Wall -Wextra ${CSILENCE} +CWARN= -Wall -Wextra -pedantic ${CSILENCE} + +# Compiler standard # #CSTD= #CSTD= -ansi -CSTD= -std=c11 +CSTD= -std=gnu11 -# compiler bit architecture +# Compiler bit architecture # # Some entries require 32-bitness: # ARCH= -m32 # -# Some entries require 64-bitess: +# Some entries require 64-bitness: # ARCH= -m64 # # By default we assume nothing: # ARCH= -# optimization +# Defines that are needed to compile +# +CDEFINE= +#CDEFINE= -DIOCCC + +# Include files that are needed to compile +# +CINCLUDE= +#CINCLUDE= -include stdlib.h +#CINCLUDE= -include stdio.h +#CINCLUDE= -include stdlib.h -include stdio.h +#CINCLUDE= -I ${X11_INCDIR} -I ${X11_INCDIR}/X11 +#CINCLUDE= `${SDL2_CONFIG} --cflags` + +# Optimization # # Most compiles will safely use -O2. Some can use only -O1 or -O. # A few compilers have broken optimizers or this entry make break # under those buggy optimizers and thus you may not want anything. +# Reasonable compilers will allow for -O3. # #OPT= #OPT= -O #OPT= -O1 -OPT= -O2 -#OPT= -O3 +#OPT= -O2 +OPT= -O3 + +# Default flags for ANSI C compilation +# +CFLAGS= ${CSTD} ${CWARN} ${ARCH} ${CDEFINE} ${CINCLUDE} ${OPT} # Libraries needed to build # LIBS= +#LIBS= -lm +#LIBS= -L ${X11_LIBDIR} -lX11 +#LIBS= `${SDL2_CONFIG} --libs` -# default flags for ANSI C compilation -# -CFLAGS= ${CWARN} ${CSTD} ${ARCH} ${OPT} -Wno-empty-body -funsigned-char - -# ANSI compiler -# -# Set CC to the name of your ANSI compiler. -# -# Some entries seem to need gcc. If you have gcc, set -# both CC and MAY_NEED_GCC to gcc. -# -# If you do not have gcc, set CC to the name of your ANSI compiler, and -# set MAY_NEED_GCC to either ${CC} (and hope for the best) or to just : -# to disable such programs. +# C compiler to use # CC= cc #CC=clang -MAY_NEED_GCC= gcc +#CC=gcc + +# Compiler add-ons or replacements for clang only +# +ifeq ($(CC),clang) +# +CSILENCE+= -Wno-comma -Wno-missing-variable-declarations \ + -Wno-poison-system-directories -Wno-shadow +# +CWARN+= -Weverything +# +endif + +# Specific add-ons or replacements for gcc only +# +ifeq ($(CC),gcc) +# +#CSILENCE+= +# +#CWARN+= +# +endif ############################## # Special flags for this entry ############################## # -ENTRY= prog main -DATA= +ENTRY= endoh1 +PROG= prog +# +OBJ= ${PROG}.o +DATA= Rakefile alpha.rb assemble.rb ast-def.rb gen-prog.rb gen-table.rb link.rb \ + machine.rb main.c parse.rb prog-tmpl.c read_me.md register.rb \ + serialize.rb src.rb tool.rb vm.c +TARGET= ${PROG} main treacle +# ALT_OBJ= -ALT_ENTRY= +ALT_TARGET= ################# # build the entry ################# # -all: ${ENTRY} ${DATA} +all: data ${TARGET} @${TRUE} +.PHONY: all alt data everything clean clobber install \ + love haste waste maker easter_egg sandwich \ + supernova deep_magic magic charon pluto + +${PROG}: ${PROG}.c + ${CC} ${CFLAGS} $< -o $@ ${LIBS} + main: main.c ${CC} ${CFLAGS} $< -o $@ ${LIBS} main.c: prog - ./prog > $@ - -prog: prog.c - ${CC} ${CFLAGS} $< -o $@ ${LIBS} + ${RM} -f $@ + ./${PROG} > $@ # alternative executable # -alt: ${ALT_ENTRY} +alt: data ${ALT_TARGET} @${TRUE} +treacle.c: ${PROG} + ${RM} -f $@ + ./${PROG} < ${PROG}.c > $@ + +treacle: treacle.c + ${CC} ${CFLAGS} $< -o $@ ${LIBS} + # data files # data: ${DATA} @${TRUE} +# both all and alt +# +everything: all alt + ############### # utility rules ############### # -everything: all alt - clean: - ${RM} -f ${ENTRY}.o ${ALT_OBJ} main.o + ${RM} -f ${OBJ} ${ALT_OBJ} + ${RM} -f main.o + @-if [ -f indent.c ]; then \ + echo ${RM} -f indent.c; \ + ${RM} -f indent.c; \ + fi clobber: clean - ${RM} -f ${ENTRY} ${ALT_ENTRY} main.c main - -nuke: clobber - @${TRUE} - -dist_clean: nuke - @${TRUE} + ${RM} -f ${TARGET} ${ALT_TARGET} + @-if [ -e sandwich ]; then \ + ${RM} -f sandwich; \ + echo 'ate sandwich'; \ + fi install: - @echo "Surely we are joking Dr. Science!" + @echo "Dr. Spock says that is not logical!" @${TRUE} -# backwards compatibility +# you may be able to misunderstand the source by reading indent.c # -build: all - @${TRUE} +indent.c: ${PROG}.c + @if which "${GINDENT}" >/dev/null 2>&1; then \ + echo ${RM} -f $@; \ + ${RM} -f $@; \ + echo "${GINDENT} < $< > $@"; \ + ${GINDENT} < $< > $@; \ + elif which "${INDENT}" >/dev/null 2>&1; then \ + echo ${RM} -f $@; \ + ${RM} -f $@; \ + echo "${INDENT} < $< > $@"; \ + ${INDENT} < $< > $@; \ + else \ + echo "no intent tool found, ident $< yourself, sorry"; \ + echo "exit 1"; \ + exit 1; \ + fi ################## @@ -174,35 +275,69 @@ waste: @echo 'haste' @${TRUE} +maker: + @echo 'Participate in a maker faire' + @${TRUE} + easter_egg: - @echo you expected to sometimes mis-understand this $${RANDOM} magic - @echo chongo '' "/\\oo/\\" - @echo Readers shall not be disallowed from failong to be unable to not partly misunderstand this partocular final echo. + @echo "you expected to often misunderstand this $${RANDOM} magic" + @echo "chongo '' /\\oo/\\" + @echo "Eggy" + +sandwich: easter_egg + @if [ `id -u` -eq 0 ]; then \ + echo 'Okay.'; \ + echo "$${RANDOM}`date +%s`$${RANDOM}" > $@; \ + else \ + echo 'What? Make it yourself.'; \ + exit 1; \ + fi # Understand the history of the Homebrew Computer Club # as well as the West Coast Computer Faire and # you might be confused different. :-) # -supernova: nuke +supernova: sandwich @-if [ -r .code_anal ]; then \ - ${RM} -f .code_anal_v4; \ + ${RM} -f .code_anal_v6; \ else \ - echo "We are not expected to understand that"; \ + echo "planet deniers, like some members of the IAU, are so cute when they try to defend their logic"; \ fi + @echo "A $@ helps ${MAKE} the elements that help form planets" @${TRUE} -deep_magic: +deep_magic: supernova @-if [ -r .code_anal ]; then \ - ccode_analysis --deep_magic 2ed3582ec5c094a093fea2a6bcb9f19439892873c9cc45ed1ceaafe08b7d52e1b2c7cd524be3b0b2 --FNV1a_hash 512-bit "${ENTRY}"; \ + ccode_analysis --deep_magic 7889066cdbdf09b0408021754586e895be641cc9be29e7eafd3ca65359541f17 \ + --FNV1a_hash_512_bit -v2 "${PROG}" "${PROG}.c"; \ else \ - echo "You are holding it wrong!"; \ + echo "Wrong! Do it again!"; \ + sleep 2; \ + echo "Wrong! Do it again!"; \ + sleep 2; \ + echo "Wrong! Do it again!"; \ fi @${TRUE} magic: deep_magic @-if [ -r .code_anal ]; then \ - ccode_analysis --mode 391581 --level 216193 --FNV1a_hash 512-bit "${ENTRY}"; \ + ccode_analysis --level 20003 --mode 5717175 --FNV1a_hash_512_bit -v2 "${PROG}" "${PROG}.c"; \ else \ - echo "These aren't the primes you're looking for Pauline!"; \ + echo "If you don't eat yer meat, you can't have any pudding!"; \ + echo "How can you have any pudding if you don't eat yer meat?!"; \ fi @${TRUE} + +# The IOCCC resident astronomer states +# +charon: magic + @echo "$@ is a dwarf planet" + @echo "dwarf is a type of planet" + @echo "therefore IAU, $@ is a planet" + +pluto: charon + ${MAKE} charon + @echo "$@ is a dwarf planet" + @echo "dwarf is a type of planet" + @echo "therefore, $@ is a planet" + @echo "having lots of planets is good feature because supernovae can help ${MAKE} a lot of them" diff --git a/2014/endoh1/README.md b/2014/endoh1/README.md index 4ec9c1c4e..917a1360a 100644 --- a/2014/endoh1/README.md +++ b/2014/endoh1/README.md @@ -1,41 +1,47 @@ # Most square (YODA award) -Yusuke Endoh - - + Yusuke Endoh + + +# To build: -## Judges' comments: -### To build: - - make +```sh +make +``` ### To run: - ./prog > foo.c +```sh +./prog > foo.c +``` ### Try: - ./prog > main.c +```sh +./prog > main.c - make main - ./main Hello +make main +./main Hello +``` -### Selected Judges Remarks: +## Judges' comments: Big terminals with tiny fonts have their uses in certain cases. One of them is: - ./prog < prog.c > treacle.c - make treacle - # or instead of make: cc -Wno-empty-body -funsigned-char treacle.c -o treacle - ./treacle +```sh +./prog < prog.c > treacle.c +make treacle +./treacle +``` When using a smart phone to read the output, you may need to step back to see the bigger picture because that picture is worth more than 1000 words. ## Author's comments: + ### Remarks You may want to use a large display with a very small-font terminal. @@ -44,11 +50,10 @@ You may want to use a large display with a very small-font terminal. Decompress `src.zip` if you need explanation. --------------------------------------------------------------------------------- - diff --git a/2014/endoh1/Rakefile b/2014/endoh1/Rakefile new file mode 100644 index 000000000..c6c696918 --- /dev/null +++ b/2014/endoh1/Rakefile @@ -0,0 +1,42 @@ +file "ast.txt" => ["src.rb", "parse.rb", "ast-def.rb"] do |t| + ruby "parse.rb", t.prerequisites[0], t.name +end + +file "seq.txt" => ["ast.txt", "serialize.rb"] do |t| + ruby "serialize.rb", t.prerequisites[0], t.name +end + +file "alpha.txt" => ["seq.txt", "alpha.rb"] do |t| + ruby "alpha.rb", t.prerequisites[0], t.name +end + +file "reg.txt" => ["alpha.txt", "register.rb"] do |t| + ruby "register.rb", t.prerequisites[0], t.name +end + +file "raw.txt" => ["reg.txt", "assemble.rb", "machine.rb"] do |t| + ruby "assemble.rb", t.prerequisites[0], t.name +end + +file "main.c" => ["raw.txt", "link.rb", "machine.rb", "vm.c"] do |t| + ruby "link.rb", t.prerequisites[0], t.name +end + +file "prog.c" => ["main.c", "gen-prog.rb", "prog-tmpl.c"] do |t| + ruby "gen-prog.rb", t.prerequisites[0], t.name +end + +file "prog" => "prog.c" do |t| + sh "gcc", "-Wall", "-W", "-Wextra", "-o", t.name, t.prerequisites[0], "-g" +end + +task :test => :default do + sh "./prog > m.c && gcc -Wall -W -Wextra -o m m.c" + sh "./m ABCDEabcde0123456789 | ruby qr-png.rb && zbarimg -q t.png" + sh "./m | ruby qr-png.rb && zbarimg -q t.png > t.c" + sh "echo -n QR-Code: > t2.c && cat m.c >> t2.c && diff t.c t2.c && echo && echo OK && echo" + sh "cat prog.c | ./iocccsize -i; wc -c prog.c" + sh "cat m.c | ./iocccsize -i; wc -c m.c" +end + +task :default => "prog" diff --git a/2014/endoh1/alpha.rb b/2014/endoh1/alpha.rb new file mode 100644 index 000000000..90596512f --- /dev/null +++ b/2014/endoh1/alpha.rb @@ -0,0 +1,141 @@ +# alpha conversion (variable renaming) + +require_relative "ast-def" + +class ID + @@num = 0 + + def initialize(name) + @unified = nil + @name = "#{ name }:#{ @@num }" + @@num += 1 + end + + attr_reader :name, :unified + + def inspect + if @unified + @unified.inspect + else + "#<#{ self.class }:#{ @name }>" + end + end + + def unify(other) + if @unified + @unified.unify(other) + elsif other.term != self + @unified = other + end + end + + def term + @unified ? @unified.term : self + end + + def ==(other) + term.name == other.term.name + end + + alias eql? == + + def hash + if @unified + @unified.hash + else + @name + end + end +end + +def merge(vars1, vars2) + vars1.each do |key, var| + if vars2[key] && vars2[key] != var + var.unify(vars2[key]) + end + end + vars1.merge(vars2) +end + +def check_opt_inc_dec(seq) + seq.each_cons(4) do |insn1, insn2, insn3, insn4| + next unless insn1.is_a?(IRead) + next unless insn2 == IPush[1] + next unless insn3 == IBinary[:+] || insn3 == IBinary[:-] + next unless insn4 == IWrite[insn1.var] + insn1.tmp.unify(insn4.tmp) + end + + seq.each do |insn| + case insn + when IBranch, ILoop + check_opt_inc_dec(insn.s1) + check_opt_inc_dec(insn.s2) + end + end +end + +def alpha(seq, vars) + nvars = vars.dup + seq.reverse_each do |insn| + case insn + when IRead + if insn.var + if insn.tmp + nvars[insn.var] ||= insn.tmp + nvars[insn.var].unify(insn.tmp) + else + insn.tmp = nvars[insn.var] ||= ID.new(insn.var) + end + end + when IWrite + if insn.var + if insn.tmp + nvars[insn.var] ||= insn.tmp + nvars[insn.var].unify(insn.tmp) + else + insn.tmp = nvars[insn.var] + end + nvars.delete(insn.var) + end + when IExit + nvars.clear + when IBranch + nvars1 = alpha(insn.s1, nvars) + nvars2 = alpha(insn.s2, nvars) + nvars = merge(nvars1, nvars2) + when ILoop + begin + nvars_back = nvars.dup + nvars1 = alpha(insn.s1, nvars) + nvars2 = alpha(insn.s1, alpha(insn.s2, nvars)) + nvars = merge(nvars1, nvars2) + merge(nvars, nvars_back) + end until nvars.keys == nvars_back.keys + end + end + nvars +end + +def rename(seq) + seq.each do |insn| + case insn + when IRead, IWrite + insn.tmp = ID.new(insn.var) if insn.var == "dummy" + insn.var.replace insn.tmp.term.name if insn.var + when IBranch, ILoop + rename(insn.s1) + rename(insn.s2) + end + end +end + +if ARGV.size != 2 + puts "usage: #$0 " +else + seq = eval(File.read(ARGV[0])) + alpha(seq, {}) + check_opt_inc_dec(seq) + rename(seq) + File.write(ARGV[1], seq.pretty_inspect) +end diff --git a/2014/endoh1/assemble.rb b/2014/endoh1/assemble.rb new file mode 100644 index 000000000..0dd2471cc --- /dev/null +++ b/2014/endoh1/assemble.rb @@ -0,0 +1,81 @@ +# dump machine code + +require_relative "ast-def" +require_relative "machine" + +def dump_seq(seq) + seq.each_cons(2) do |insn1, insn2| + if insn1.is_a?(IPush) && insn2.is_a?(IPush) + p insn1, insn2 + raise "consecutive push" + end + end + + seq.map do |insn| + case insn + when IRead then (REGISTER_READ + insn.var.ord - ?a.ord).chr if insn.var + when IWrite then (REGISTER_WRITE + insn.var.ord - ?a.ord).chr if insn.var + when IInc then MNEMONIC[:Inc] + (REGISTER_READ + insn.var.ord - ?a.ord).chr + when IDec then MNEMONIC[:Dec] + (REGISTER_READ + insn.var.ord - ?a.ord).chr + when IPush then encode_number(insn.num) + when IBranch + MNEMONIC[:BranchS] + dump_seq(insn.s1) + + MNEMONIC[:BranchM] + dump_seq(insn.s2) + + MNEMONIC[:BranchE] + when ILoop + MNEMONIC[:LoopS] + dump_seq(insn.s1) + + MNEMONIC[:LoopM] + dump_seq(insn.s2) + + MNEMONIC[:LoopE] + when IBinary then MNEMONIC[insn.op] + when ITest then MNEMONIC[:Test] + when IARef then MNEMONIC[:ARef] + when IOut then MNEMONIC[:Out] + when IExit then MNEMONIC[:Exit] + when IASet then MNEMONIC[:ASet] + end + end.join +end + +def check_opt_inc_dec(seq, i) + return unless seq[i].is_a?(IRead) + return unless seq[i + 1] == IPush[1] + return unless seq[i + 2] == IBinary[:+] || seq[i + 2] == IBinary[:-] + return unless seq[i + 3] == IWrite[seq[i].var] + insn = (seq[i + 2].op == :+ ? IInc : IDec)[seq[i].var] + flag = seq[i + 4].is_a?(IRead) && seq[i + 4].var == seq[i].var + [insn, seq[i].var, flag] +end + +def opt_inc_dec(seq) + while true + modified = false + (seq.size - 3).times do |i| + new_insn, var, flag = check_opt_inc_dec(seq, i) + if new_insn + if flag + seq[i, 5] = [new_insn] + else + seq[i, 4] = [new_insn, IWrite[var]] + end + modified = true + end + end + break if !modified + end + + seq.each do |insn| + case insn + when IBranch, ILoop + opt_inc_dec(insn.s1) + opt_inc_dec(insn.s2) + end + end +end + +if ARGV.size != 2 + puts "usage: #$0 " +else + seq = eval(File.read(ARGV[0])) + opt_inc_dec(seq) + File.write(ARGV[1], dump_seq(seq)) +end diff --git a/2014/endoh1/ast-def.rb b/2014/endoh1/ast-def.rb new file mode 100644 index 000000000..0f9d11413 --- /dev/null +++ b/2014/endoh1/ast-def.rb @@ -0,0 +1,89 @@ +require "pp" + +class Node + def ==(other) + self.class == other.class && + self.class::Keys.all? do |key| + instance_variable_get(:"@#{ key }") == other.send(key) + end + end + + attr_accessor :tmp + + def self.create(*keys) + klass = Class.new(Node) + klass.const_set(:Keys, keys) + klass.class_eval do + keys.each do |key| + attr_reader key + end + end + klass + end + + def self.[](*values) + new(*values) + end + + def initialize(*values) + self.class::Keys.zip(values) do |key, value| + instance_variable_set(:"@#{ key }", value) + end + end + + def inspect + "#{ self.class }[#{ + self.class::Keys.map do |key| + instance_variable_get(:"@#{ key }").inspect + end.join(", ") + }]" + end + + def pretty_print(q) + q.group(2, "#{ self.class }[", "]") do + q.seplist( + self.class::Keys.map do |key| + instance_variable_get(:"@#{ key }") + end + ) do |v| + q.pp v + end + end + end +end + +PRead = Node.create(:var) +PImm = Node.create(:num) +PNop = Node.create + +PBinary = Node.create(:op, :e1, :e2) +PIfThenElse = Node.create(:cond, :e1, :e2) + +PRShift = Node.create(:val, :shift) +PARef = Node.create(:e1, :e2) # e1[e2] +PSRef = Node.create(:e) +PIRef = Node.create(:e) + +PSequence = Node.create(:seq, :e) + +PASet = Node.create(:e1, :e2, :e3) # e1[e2] = e3 +PAssign = Node.create(:var, :exp) +PLoop = Node.create(:cond, :body) +PExit = Node.create(:e) +POut = Node.create(:e) +PTest = Node.create(:e) + +IRead = Node.create(:var) +IWrite = Node.create(:var) +IInc = Node.create(:var) +IDec = Node.create(:var) +INop = Node.create +IPush = Node.create(:num) +IBinary = Node.create(:op) +IARef = Node.create +IASet = Node.create +IExit = Node.create +IOut = Node.create +ITest = Node.create +IBranch = Node.create(:s1, :s2) +ILoop = Node.create(:s1, :s2) diff --git a/2014/endoh1/gen-prog.rb b/2014/endoh1/gen-prog.rb new file mode 100644 index 000000000..79b61025b --- /dev/null +++ b/2014/endoh1/gen-prog.rb @@ -0,0 +1,58 @@ +tmpl = < 92 - 34 + if $1 == "\0" + (len + 34).chr + s[offset, len] + else + (len + 92).chr + ($1.ord + 36).chr + end + end +end + +if ARGV.size != 2 + puts "usage: #$0 " +else + Code = File.read(ARGV[0]) + + vm_src = File.read("vm.c").lines.drop(1).join.gsub(/^\s*/, "").gsub("\n", "").chomp.gsub(/\s*\/\*.*?\*\//, "") + quine_data = compress("#include@" + vm_src.gsub('"CODE"', "Q").gsub('"', """'")) + + # back patch of absolute address + Code.gsub!(encode_number(39999)) { encode_number((Code ).size + 39901 - 1) } + Code.gsub!(encode_number(97531)) { encode_number((Code + Table ).size + 39901 - 1) } + Code.gsub!(encode_number(50000)) { encode_number((Code + Table + quine_data).size + 39901 + 1) } + + File.write(ARGV[1], "#include\n" + vm_src.sub("CODE") { Code + Table + quine_data } + "\n") +end diff --git a/2014/endoh1/machine.rb b/2014/endoh1/machine.rb new file mode 100644 index 000000000..f4d1b9fb6 --- /dev/null +++ b/2014/endoh1/machine.rb @@ -0,0 +1,57 @@ +# 33 : not used +# 34 : NG +# 35..55 : read +# 56..76 : write +# 77..85 : binary op +# 86..91 : not used +# 92 : NG +# 93..98 : control (0..5) +# 99..104 : other insns (6..12) +# 105..126 : num (13..32) + +MNEMONIC = { + :Nop => "", + + :+ => 77.chr, + :- => 78.chr, + :* => 79.chr, + :"&&" => 79.chr, + :^ => 80.chr, + :> => 81.chr, + :/ => 82.chr, + :% => 83.chr, + :">>" => 84.chr, # 6 + :"==" => 85.chr, + + :BranchS => 93.chr, # 0 -> BranchM + :LoopM => 94.chr, # 1 -> LoopE + :BranchM => 95.chr, # 2 -> BranchE + :LoopE => 96.chr, # 3 -> LoopS (back) + :BranchE => 97.chr, # 4 + :LoopS => 98.chr, # 5 + + :ARef => 99.chr, # 6 + :ASet => 100.chr, # 7 + :Out => 101.chr, # 8 + :Exit => 102.chr, # 9 + :Inc => 103.chr, # 10 + :Dec => 104.chr, # 11 + + :Test => 105.chr, +} + +REGISTER_READ = 35 +REGISTER_WRITE = 56 +IMM = 106 + +def encode_number(n) + if n >= 0 + tt = n.to_s(126 - IMM + 1).tr("0-9a-z", [*IMM.chr..126.chr].join) + raise if tt[0] == 126.chr + tt[0, 1] = tt[0].tr([*IMM.chr..125.chr].join, [*(IMM + 1).chr..126.chr].join) + else + raise if n != -1 + tt = IMM.chr + end + tt +end diff --git a/2014/endoh1/parse.rb b/2014/endoh1/parse.rb new file mode 100644 index 000000000..fe09f8716 --- /dev/null +++ b/2014/endoh1/parse.rb @@ -0,0 +1,87 @@ +# parse pseudo Ruby code and generate AST + +require_relative "ast-def" +require "ripper" + +Subroutines = {} + +def expr(e) + case e[0] + when :binary then PBinary[e[2], expr(e[1]), expr(e[3])] + when :var_ref, :vcall + name = e[1][1] + case name + when "nop" then PNop[] + when "minus1" then PImm[-1] + when "Zero" then PImm[0] + else PRead[name] + end + when :aref then PARef[expr(e[1]), expr(e[2][1][0])] + when :assign then PASet[expr(e[1][1]), expr(e[1][2][1][0]), expr(e[2])] + when :@int then PImm[e[1].to_i] + when :paren + PSequence[statements(e[1][0..-2]), expr(e[1][-1])] + when :ifop + PIfThenElse[expr(e[1]), expr(e[2]), expr(e[3])] + when :method_add_arg + case e[1][1][1] + when "inc", "dec" + r = e[2][1][1][0][1][1] + PSequence[ + [PAssign[r, PBinary[e[1][1][1] == "inc" ? :+ : :-, PRead[r], PImm[1]]]], + PRead[r] + ] + else + pp e + raise e[1][1][1] + end + when Array then expr(e[0]) + else + pp e + raise + end +end + +def statement(e) + case e[0] + when :assign then PAssign[e[1][1][1], expr(e[2])] + when :method_add_block + if e[1][1][1][1] == "loop" + PLoop[expr(e[1][2][1][1]), statements(e[2][2])] + else + pp e + raise + end + when :command + case e[1][1] + when "call" then Subroutines[e[2][1][0][1][1]] + when "exit" then PExit[expr(e[2][1][0])] + when "out" then POut[expr(e[2][1][0])] + when "test" then PTest[expr(e[2][1][0])] + else + pp e + exit + end + when :void_stmt then PNop[] + else + pp e + raise e[0].to_s + end +end + +def statements(s) + s.map {|e| statement(e) } +end + +def program(e) + e[1].each do |e| + Subroutines[e[1][1][1][1]] = statements(e[2][2]) + end +end + +if ARGV.size != 2 + puts "usage: #$0 " +else + program(Ripper.sexp(File.read(ARGV[0]))) + File.write(ARGV[1], Subroutines["main"].pretty_inspect) +end diff --git a/2014/endoh1/prog-tmpl.c b/2014/endoh1/prog-tmpl.c new file mode 100644 index 000000000..c856f0aec --- /dev/null +++ b/2014/endoh1/prog-tmpl.c @@ -0,0 +1,11 @@ +char*s="CODE",*p,b,d[9338],*q,x,*r=d; +int main(){ + ;for(p=q=5000+0+d;*s;s++)if(*s>32)*p++=*s-89?*s:32; + for(p=INDX+q;(b=*p++);){ + for(d[17]=10;x=*p++,b<92&&344*23;r++)*r=r[36-x]; + } + puts(d); + return(0); +}/*IOCCC2014IOCCC2014IOCCC2014IOCCC2014IOCCC*/ diff --git a/2014/endoh1/read_me.md b/2014/endoh1/read_me.md new file mode 100644 index 000000000..4a9858257 --- /dev/null +++ b/2014/endoh1/read_me.md @@ -0,0 +1,149 @@ +# Q(uine)R code generator + +## What is difficult? + +As you may know, QR code (specification: ISO/IEC 18004) is quite complex. +It requires a bit stream, Reed-Solomon codes, BCH codes, +data relocation, big (and irregular!) numerical tables, +human-oriented pattern arrangement (difficult to implement by a small program), +etc. + +On the other hand, a quine has a big restriction: +it needs to have two copies of the program! + +Do you think that it is possible to write QR code encoder in C within *1kB*? + + +## How did I achieve that? + +It is known that we can mitigate the quine restriction by `eval`. +For example, this is a quine written in Ruby: + + eval s="print 'eval s='; p s" + +Note that `print` appears just once in the program. + +To emulate this approach in C, I adopted the following structure. + + char s[]="
int main() { }"; + int main() { ; } + +* `` : An own VM implementation (written in C) that executes the main code +* `
` : An object code of quine and QR code encoder (written in the assembly language of the VM) + +`
` (about 1100 bytes) appears once +and `` (about 450 bytes) appears twice. +So the whole code is about 1100 + 450 * 2 = 2000 bytes. Hooray! + + +## What sort of VM is that? + +The VM is a kind of stack machine which makes the object code compact. +The instruction set is carefully selected +to minimize the sizes of both `
` and ``. + +* 21 integer registers access + * read, write, inc-and-read, dec-and-read +* Binary operations + * add, sub, mul, div, mod, xor, less than, equal, rshift +* Stack operation + * push (an immediate value) +* Heap access + * load, store +* If-Then-Else +* While-Loop +* Misc. + * putchar, exit + +The instructions have variable length, +but many of them are represented as one printable character. +(See `machine.rb` and `assemble.rb` in detail.) +The mapping between instructions and characters is also carefully designed +to minimize ``, +which makes the resulted program non-human-readable. + + +## How did I create the main code? + +It is too tedious to write the machine language directly. +So I wrote the source code in Ruby syntax and created a Ruby-to-VM compiler +that involves alpha renaming and automatic register allocation +only for this purpose. + +* `src.rb`: the source code of QR code encoder written in Ruby syntax. +* `vm.c`: the source code of the VM. + +The compiler tool chain is the following. + + src.rb interp.c + | | + | [parse.rb] | + V | + ast.txt | + | | + | [serialize.rb] | + V | + seq.txt | + | | + | [alpha.rb] | + V | + alpha.txt | + | | + | [register.rb] | + V | + reg.txt | + | | + | [assemble.rb] | + V | + raw.txt | + | | + +---------+---------+ + | + | [link.rb] + V + main.c + | + | [gen-prog.c] + V + prog.c + +* `parse.rb` converts Ruby to AST. +* `serialize.rb` converts AST to intermediate instruction sequence. +* `alpha.rb` applies "alpha-renaming" to the instruction sequence. +* `register.rb` applies "register allocation". +* `assemble.rb` converts the sequence to the final stack-machine instructions. +* `interp.c` is an implementation of the VM. +* `link.rb` generates `main.c` by linking the instructions and interpreter source. +* `gen-prog.c` generates `prog.c` based on `main.c` + +You can run the process by `rake` command. `ruby-minisat` gem is needed. + + $ gem install ruby-minisat + $ rake + +## Tools + +I checked that the following QR code readers can read the output of my program. + + * Linux: [ZBar bar code reader][1] + * Android: [Barcode Scanner][2] + +[1]: http://zbar.sourceforge.net/ +[2]: https://play.google.com/store/apps/details?id=com.google.zxing.client.android] + +You can use `tool.rb` to convert the output to a png file. + + $ ./main Hello | ruby tool.rb > hello.png + + $ zbarimg -q hello.png + QR-Code:Hello + + $ ruby tool.rb prog.c + $ ruby tool.rb prog.c | ruby tool.rb + +## Limitations + +* QR code error correction level is fixed to L (Low). +* It supports only byte mode (not numeric, alphanumeric, kanji mode...) +* The mask pattern is fixed to 4. +* The mask is not applied to the remained bits. diff --git a/2014/endoh1/register.rb b/2014/endoh1/register.rb new file mode 100644 index 000000000..c3949d952 --- /dev/null +++ b/2014/endoh1/register.rb @@ -0,0 +1,83 @@ +# register allocation (variable renaming again!) + +require_relative "ast-def" +require "minisat" + +Conflicts = {} + +def liveness(seq, vars) + nvars = vars.dup + + seq.reverse_each do |insn| + case insn + when IRead, IWrite + nvars[insn.var] = true if insn.var + when IExit + nvars.clear + when IBranch + nvars1 = liveness(insn.s1, nvars) + nvars2 = liveness(insn.s2, nvars) + nvars = nvars1.merge(nvars2) + when ILoop + begin + nvars_back = nvars + nvars1 = liveness(insn.s1, nvars) + nvars2 = liveness(insn.s1, liveness(insn.s2, nvars)) + nvars = nvars1.merge(nvars2) + end until nvars.keys == nvars_back.keys + end + + nvars.keys.sort.combination(2) do |v1, v2| + Conflicts[[v1, v2].sort] = true + end + + nvars.delete(insn.var) if insn.is_a?(IWrite) && insn.var + end + + nvars +end + +def graph_coloring(liveness, k) + solver = MiniSat::Solver.new + + h = {} + + liveness.keys.flatten.uniq.each do |var| + vs = (0...k).map { solver.new_var } + solver << vs + vs.combination(2) {|v1, v2| solver << [-v1, -v2] } + h[var] = vs + end + + liveness.each_key do |var1, var2| + h[var1].zip(h[var2]) {|v1, v2| solver << [-v1, -v2] } + end + + solver.solve + + h.each do |var, vs| + h[var] = (vs.find_index {|v| solver[v] } + ?a.ord).chr + end + h +end + +def rename(seq, h) + seq.each do |insn| + case insn + when IRead, IWrite + insn.var.replace h[insn.var] if insn.var + when IBranch, ILoop + rename(insn.s1, h) + rename(insn.s2, h) + end + end +end + +if ARGV.size != 2 + puts "usage: #$0 " +else + seq = eval(File.read(ARGV[0])) + liveness(seq, {}) + rename(seq, graph_coloring(Conflicts, 21)) + File.write(ARGV[1], seq.pretty_inspect) +end diff --git a/2014/endoh1/serialize.rb b/2014/endoh1/serialize.rb new file mode 100644 index 000000000..f9ef9e7ad --- /dev/null +++ b/2014/endoh1/serialize.rb @@ -0,0 +1,122 @@ +# translate AST into stack-based instruction sequence + +require_relative "ast-def" + +class PRead + def emit + [IRead[var]] + end +end + +class PImm + def emit + [IPush[num]] + end +end + +class PNop + def emit + [INop[]] + end +end + +class PBinary + def emit + if op == :< + e2.emit + e1.emit + [IBinary[:>]] + else + e1.emit + e2.emit + [IBinary[op]] + end + end +end + +class PIfThenElse + def emit + s1 = e1.emit + s2 = e2.emit + + # merge the common tails if any + tails = [] + s1.reverse.zip(s2.reverse) do |i1, i2| + break if i1 != i2 + s1.pop + s2.pop + tails.unshift i1 + end + + cond.emit + [IBranch[s1, s2]] + tails + end +end + +class PARef + def emit + if e1 == PImm[0] && e2.is_a?(PBinary) && e2.op == :+ + e2.emit[0..-2] + [IARef[]] + elsif e2 == PImm[0] && e1.is_a?(PBinary) && e1.op == :+ + e1.emit[0..-2] + [IARef[]] + else + e1.emit + e2.emit + [IARef[]] + end + end +end + +class PASet + def emit + if e1 == PImm[0] && e2.is_a?(PBinary) && e2.op == :+ + e3.emit + e2.emit[0..-2] + [IASet[]] + elsif e2 == PImm[0] && e1.is_a?(PBinary) && e1.op == :+ + e3.emit + e1.emit[0..-2] + [IASet[]] + else + e3.emit + e2.emit + e1.emit + [IASet[]] + end + end +end + +class PAssign + def emit + exp.emit + [IWrite[var != ?_ ? var : nil]] + end +end + +class PSequence + def emit + seq.flat_map {|e| e.emit } + e.emit + end +end + +class PLoop + def emit + [ILoop[cond.emit, body.flat_map {|e| e.emit }]] + end +end + +class PTest + def emit + e.emit + [ITest[]] + end +end + +class PExit + def emit + e.emit + [IExit[]] + end +end + +class POut + def emit + e.emit + [IOut[]] + end +end + +class Array + def emit + flat_map {|e| e.emit } + end +end + +if ARGV.size != 2 + puts "usage: #$0 " +else + ast = eval(File.read(ARGV[0])) + File.write(ARGV[1], ast.flat_map {|e| e.emit }.pretty_inspect) +end diff --git a/2014/endoh1/src.rb b/2014/endoh1/src.rb new file mode 100644 index 000000000..35accbace --- /dev/null +++ b/2014/endoh1/src.rb @@ -0,0 +1,280 @@ +# magical calculation methods! + +# calculate the total code words for the version given +total_codewords { + n = Version / 7 * 5 + 5 + TotalCodes = + Size_1 * Size_1 / 8 - 24 - + (Version > 6 ? n * n / 8 + n : (Version > 1) * 4) +} + +# calculate the error correction code words for the version given +error_correction_codewords { + ECCodes = TotalCodes * 54 / 265 - Zero[39999 + Version] + 43 +} + +# calculate the degree of the generator polynomial +generator_polynomial_degree { + Degree = + Version < 6 ? ECCodes : + Version == 10 ? 18: + (n = 16 + loop(ECCodes / 2 % dec(n)) { } + n * 2) +} + +other_constants { + N = ECCodes / Degree + M = N - TotalCodes % N + K = (TotalCodes - ECCodes) / N + 1 + S = Degree + K + D = K * N - M +} + + +# GF(2^8) +gen_table { + i = 511 + j = 1 + loop(i) { + j = ((Forward[Backward[j] = dec(i)] = j) ^ (285 * (j % 2))) / 2 + } +} + +# calculate the generator polynomial +setup_generator_polynomial { + i = Degree + 1 + loop(dec(i)) { + j = Degree + 1 + x = 0 + loop(j > i) { + y = Coefs[dec(j)] + dummy = Coefs[j] = Backward[x ^ Forward[Degree - i + y]] + x = Forward[y] + } + } +} + +# paint fixed cells +setup_fixed_patterns { + # three big markers + i = Size + loop(i) { + i = i - 1 + j = Size + loop(j) { + k = i - j * Size + dummy = + Zero[j * Size + i] = + Zero[minus1 - k] = + Zero[Size * Size + k] = + (i < 7 && dec(j) < 7 && (((i - 6) * i + 19 + (j - 6) * j) / 5) ^ 2 < 3) - + ((i > 7) + (j > 7) > 0) + } + } + + # alignment markers + x = Version / 7 + 2 + n = Version - 32 ? (Version + x / 2) * 2 / (x - 1) * 2 : 26 + i = x * x + loop(Version > 1 && dec(i) > 1) { + j = 25 + loop(i - x && j) { + y = Size_1 - 8 + n + dummy = Zero[ + ((y - i / x * n) % (y - 4) + j / 5) * Size + + ((y - i % x * n) % (y - 4) + j % 5) + ] = (33084991 >> dec(j)) % 2 + } + } + + # BCH for version info + n = Version * 4096 + k = n + j = 64 + loop(j) { + k = k ^ ((7973 * j) * (k + 1 > 4096 * j)) + j = j / 2 + } + # (n + k) is the code + + # version info and timing + i = 18 * (Version > 6) + loop(i + Size - 16) { + dummy = + Zero[x * Size + y] = + Zero[y * Size + x] = + dec(i) < 0 ? + (x = Size - 8 + i ; y = 6 ; (0 - i) % 2) : + (x = Size - 11 + i % 3; y = i / 3; ((n + k) >> i) % 2) + } + + # format info + i = 15 + loop(i) { + # BCH for format info (4 + 8) (mask = 4, error correction level = L) + # 26159 is the code + n = dec(i) > 7 ? Size_1 - 14 + i : i + (i > 5) + dummy = + Zero[Size * (Size - 8) + 8] = + Zero[n * Size + 8] = + Zero[9 * Size_1 - n + 8 + (i / 3 == 2)] = + (26159 >> i) % 2 + } +} + +# data encoding +setup_data { + n = minus1 + buff = 64 + z = 17 + j = 0 + loop(inc(n) < D) { + x = Input[j] + _ = x ? + (y = V9 < 0 ? (j = j + 1; x) : (V9 = V9 - 1; V9 ? InputLen % 256 : (InputLen >> 8)) + emit = buff + y / 16 + buff = (y % 16) * 16 + nop) : + (emit = buff + z = z ^ 253 + buff = z + nop) + dummy = EncodedData[n] = emit + } +} + +# error correction +encode { + x = D + i = N + loop(i) { + y = D + S * i + n = K - (dec(i) < M) + j = n + Degree + loop(j) { + dummy = EncodedData[dec(y)] = dec(j) < n ? EncodedData[dec(x)] : 0 + } + loop(j < n * Degree) { + t = j % Degree + 1 + idx = EncodedData + y + j / Degree + dummy = idx[t] = idx[t] ^ ((idx[0] > 0) * Forward[Coefs[t] + Backward[idx[0]]]) + j = j + 1 + } + } +} + +# make the pattern by placing the data +place { + x = Size_1 + y = Size_1 + j = minus1 + loop(inc(j) < S) { + i = M * (j == K - 1) + loop(i < N) { + t = 8 + loop(t) { + xx = x + (x > 5) + _ = Zero[y * Size + xx] < 0 ? + (dummy = Zero[y * Size + xx] = + ((EncodedData[j + (j < K ? i * K - (i < M ? i : M) : D + i * S)] >> dec(t)) ^ + (y / 2 + xx / 3)) % 2 == 0; + nop) : nop + x = x - + (x % 2 ? 1 : + (x / 2 % 2 ? + (y ? (y = y - 1; minus1) : 1) : + (y - Size_1 ? (y = y + 1; minus1) : 1))) + } + i = i + 1 + } + } +} + +# print the pattern to stdout +output { + k = Size + 4 + y = Size_1 - k + loop(inc(y) < k) { + x = Size_1 - k + loop(inc(x) < k) { + out 27; out 91; out 52 + out 55 - + (x > minus1 && x < Size && y > minus1 && y < Size) * + (x + y * Size < 0 ? 0 : (Zero[x + y * Size] > 0)) * 7 + out 109; out 32; out 32 + } + out 27; out 91; out 48; out 109; out 10 + } +} + +# generate the code itself by decompression +decompress { + i = 97531 # a placeholder value that will be replaced by assemble.rb + InputLen = minus1 + # decompress + loop((b = Zero[inc(i)]; b)) { + _ = b < 92 ? + (loop(dec(b) > 33) { + x = Zero[inc(i)] + _ = x - 81 ? nop : + (k = 39900 # quine + x = Input[inc(InputLen)] = 34 + loop((z = Zero[inc(k)]; z)) { + dummy = Input[inc(InputLen)] = z + } + nop + ) + dummy = Input[inc(InputLen)] = x + } + nop) : + (i = i + 1 + loop(dec(b) > 91) { + dummy = Input[InputLen] = Input[inc(InputLen) - i[0] + 36] + } + nop) + } + InputLen = InputLen + 1 + dummy = 17[Input] = 10 # newline after "#include" +} + +main { + # memory layout + # 0...31329: Pattern data + # 32000...32512: Forward translation of GF(2^8) + # 32512...32768: Backward translation of GF(2^8) + # 32800...33000: Coefficients of generator polynomial + # 33000...37000: EncodedData + # 40000... : Code + nul + Input + Forward = 32000 + Backward = 32512 + Coefs = 32800 + EncodedData = 33000 + Input = 50000 # a placeholder + + loop(Input[InputLen]) { InputLen = InputLen + 1 } + _ = InputLen ? nop : (call decompress; nop) + + Version = 0 + loop(Version < 40) { + Size_1 = 4 * inc(Version) + 16 + Size = Size_1 + 1 + call total_codewords + call error_correction_codewords + call generator_polynomial_degree + call other_constants + V9 = Version > 9 + _ = D - 1 - V9 > InputLen ? ( + call gen_table + call setup_generator_polynomial + call setup_fixed_patterns + call setup_data + call encode + call place + call output + exit 0 + nop + ) : nop + } + exit 1 +} diff --git a/2014/endoh1/src.zip b/2014/endoh1/src.zip deleted file mode 100644 index d197a9030..000000000 Binary files a/2014/endoh1/src.zip and /dev/null differ diff --git a/2014/endoh1/tool.rb b/2014/endoh1/tool.rb new file mode 100644 index 000000000..26d46ccaa --- /dev/null +++ b/2014/endoh1/tool.rb @@ -0,0 +1,49 @@ +require "zlib" + +lines = [*$<] + +if lines.join.gsub(/\e\[\d*m/, "").chars.uniq.sort == ["\n", " "] + + matrix = lines.map do |line| + row = [] + cell = nil + line.chomp.scan(/\G(?:\e\[(\d+)m|..)/) do + if $1 + cell = $1 == "40" ? :black : :white + else + row << cell + end + end + row + end + + size = matrix.size + if size != matrix.first.size + raise "not square: #{ matrix.first.size } x #{ matrix.size }" + end + $stderr.puts "Version: #{ (size - 25) / 4 }" + + scale = 400 / size + data = (0 ... scale * size).flat_map do |y| + [0] + (0 ... scale * size).flat_map do |x| + matrix[y / scale][x / scale] == :black ? [0, 0, 0] : [255, 255, 255] + end + end + + print "\x89PNG\r\n\x1a\n" + [ + ["IHDR", [scale * size, scale * size, 8, 2, 0, 0, 0].pack("NNCCCCC")], + ["IDAT", Zlib::Deflate.deflate(data.pack("C*"))], + ["IEND", ""], + ].each do |type, data| + print [data.bytesize, type, data, Zlib.crc32(type + data)].pack("NA4A*N") + end + +else + + width = lines.map {|line| line.chomp.size + 4 * 4 }.max + lines.each do |line| + puts line.chomp.ljust(width).gsub(/../) { "\e[#{ $& == " " ? 47 : 40 }m " } + end + +end diff --git a/2014/endoh1/vm.c b/2014/endoh1/vm.c new file mode 100644 index 000000000..eae7e969a --- /dev/null +++ b/2014/endoh1/vm.c @@ -0,0 +1,29 @@ +#include +int H[99999],*s,*c,d,t; +int main(int n,char*v[]){ + for(s=c=H+39999;(*++s="CODE"[d++]);); + for(;n>1&&(*++s=v[1][t++]);); + /*for(;n=getchar(),n>0;)*++s=n;*/ + for(s=H;d=*c++%93,d-9;){ + int v=*s,g[]={n+v,v-n,n*v,v^n,n>n,v==n}; + for(t=0;d<4&&d>=2*!!n&&(c-=d/3*2,v=3+*c++,t||v!=98+d);) + t+=v++/6-16?0:v/2%3-1; + for(t=d-14;t>0&&d<34&&105<*c;)t=t*21+*c++-106; + n= + /*d==12?fprintf(stderr,"test:%d\n",n),*s--:*/ + d>76?s--,g[d-77]: /* operation */ + d>55?H[d-21]=n,*s--: /* reg write */ + d>9? + *++s=n, + 12>d?H[*c++]+=21-d*2: /* inc / dec */ + d<34?t:H[d] /* imm push / reg read */ + : + d>5? + s--, + 7d?*s--:n; /* control */ + } + return n; +}