├── README.md ├── examples └── hello.fumo └── fumolang.py /README.md: -------------------------------------------------------------------------------- 1 | # fumolang 2 | 3 | Fumolang is a low-level, compiled, non-portable, esoteric programming langauge 4 | based on the popular 5 | [FumoFumo Touhou plush series](https://en-dic.pixiv.net/a/FumoFumo) 6 | by [Gift](https://www.gift-gift.jp). 7 | 8 | ## Language Spec 9 | 10 | Fumolang directly compiles to binary and only has two keywords that directly 11 | translate into one bit in the compiled output: 12 | 13 | * `Fumo` - 0 14 | * `FumoFumo` - 1 15 | 16 | The language is case-insensitive, and any variation of these two keywords is 17 | accepted. Each word must be seperated by whitespace, and any other non-whitespace 18 | token will be treated as comments. 19 | 20 | This means source code is not portable and must be rewritten for every target 21 | platform 22 | 23 | ## Examples 24 | 25 | A example source code for linux-amd64 can be found in the `examples/` 26 | directory. 27 | 28 | ## Compilation 29 | 30 | ``` 31 | TODO(james7132): Document 32 | ``` 33 | 34 | ## Decompilation 35 | 36 | ``` 37 | TODO(james7132): Document 38 | ``` 39 | 40 | ## FAQ 41 | 42 | ### Why do this? 43 | 44 | Fumos. 45 | 46 | ### Compiling the decompilation output results in different results, that's a bug! 47 | 48 | Who cares? Fumo. 49 | -------------------------------------------------------------------------------- /fumolang.py: -------------------------------------------------------------------------------- 1 | #!/user/sbin/python 2 | 3 | VALUES = ['Fumo', 'FumoFumo'] 4 | 5 | 6 | class BitWriter: 7 | 8 | def __init__(self): 9 | self._value = 0 10 | self._bit = 7 11 | self.bytes = [] 12 | 13 | @property 14 | def flushable(self): 15 | return self._value != 0 and self._bit != 7 16 | 17 | def write(self, value): 18 | self._bit -= 1 19 | if self._bit < 0: 20 | self.flush() 21 | if value: 22 | self._value = self._value | (1 << self._bit) 23 | 24 | def flush(self): 25 | self.bytes.append(self._value) 26 | self._value = 0 27 | self._bit = 7 28 | 29 | def to_bytes(self): 30 | return bytes(self.bytes) 31 | 32 | 33 | def bytes2fumo(binary): 34 | for byte in binary: 35 | yield VALUES[(byte >> 7) & 1] 36 | yield VALUES[(byte >> 6) & 1] 37 | yield VALUES[(byte >> 5) & 1] 38 | yield VALUES[(byte >> 4) & 1] 39 | yield VALUES[(byte >> 3) & 1] 40 | yield VALUES[(byte >> 2) & 1] 41 | yield VALUES[(byte >> 1) & 1] 42 | yield VALUES[byte & 1] 43 | 44 | 45 | def fumo2bytes(writer, line): 46 | words = [word.casefold() for word in line.split()] 47 | for word in words: 48 | if word == "fumo": 49 | writer.write(0) 50 | elif word == "": 51 | writer.write(1) 52 | 53 | 54 | def compile(src, dst): 55 | writer = BitWriter() 56 | with open(src, 'r+') as src_file: 57 | with open(dst, 'wb+') as dst_file: 58 | while True: 59 | line = src_file.readline() 60 | if not line: 61 | break 62 | fumo2bytes(writer, line) 63 | dst_file.write(writer.to_bytes()) 64 | if writer.flushable: 65 | writer.flush() 66 | dst_file.write(writer.to_bytes()) 67 | 68 | 69 | def decompile(src, dst): 70 | with open(src, 'rb+') as src_file: 71 | with open(dst, 'w+') as dst_file: 72 | while True: 73 | block = src_file.read(1024 ** 2) # 1MB 74 | if not block: 75 | break 76 | out = ' '.join(bytes2fumo(block)) 77 | dst_file.write(out) 78 | 79 | 80 | decompile('hello', 'hello.fumo') 81 | --------------------------------------------------------------------------------