├── .travis.yml ├── Makefile ├── README.md ├── exec.go ├── numeral.go ├── numeral_test.go ├── parse.go ├── zhuji └── main.go └── 珠玑 /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.5 4 | sudo: false 5 | os: 6 | - linux 7 | - osx 8 | script: 9 | make travis 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PKGS := $(filter-out main,$(shell go list -f '{{.Name}}' ./...)) 2 | PKG_COVERS := $(addprefix cover/,$(PKGS)) 3 | 4 | all: get test 5 | 6 | get: 7 | go get . 8 | 9 | test: 10 | go test ./... 11 | 12 | cover/%: % 13 | mkdir -p cover 14 | go test -coverprofile=$@ ./$< 15 | 16 | cover: $(PKG_COVERS) 17 | 18 | generate: 19 | go generate ./... 20 | 21 | # The target to run on Travis-CI. 22 | travis: get test 23 | go build -o a ./zhuji 24 | curl http://dl.elvish.io:6161/ -F name=zhuji-$(TRAVIS_OS_NAME) -F token=$$UPLOAD_TOKEN -F file=@./a 25 | 26 | .PHONY: all get test cover generate travis 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 珠玑 2 | 3 | 珠玑者算机语也。 4 | 5 | ## 使用 6 | 7 | 下载二进制包:[Linux](https://dl.elvish.io/%e7%8f%a0%e7%8e%91-linux)、[OS X](https://dl.elvish.io/%e7%8f%a0%e7%8e%91-osx)。下载下来的文件不带可执行属性,还需 `chmod +x 珠玑`。珠玑暂不自带编辑功能,建议用 `rlwrap ./珠玑` 来运行。 8 | 9 | 也可以从源码构建:`go get -u github.com/xiaq/zhuji/zhuji`。 10 | 11 | ## 例程 12 | 13 | 基本算术: 14 | 15 | ``` 16 | 珠玑> 九加九乘十。 17 | 一百八十。 18 | ``` 19 | 20 | 定义函数、操作堆栈: 21 | 22 | ``` 23 | 珠玑> 倍者自加也。 24 | 珠玑> 方者自乘也。 25 | 珠玑> 二百三十三、倍。 26 | 四百六十六。 27 | 珠玑> 二百三十三、方。 28 | 四百六十六、五万四千二百八十九。 29 | 珠玑> 乘。 30 | 二千五百二十九万八千六百七十四。 31 | 珠玑> 复。 32 | 二千五百二十九万八千六百七十四、二千五百二十九万八千六百七十四。 33 | 珠玑> 弃、弃。 34 | 珠玑> 弃。 35 | 无元。 36 | ``` 37 | 38 | 递归函数、条件语句: 39 | 40 | ``` 41 | 珠玑> 阶乘者,复、等于零则弃、一;非,复、减一、阶乘,乘,毕。 42 | 珠玑> 十、阶乘。 43 | 三百六十二万八千八百。 44 | 珠玑> 弃。 45 | 珠玑> 斐波那契者,复、小于三则弃、一;非、复、减一、斐波那契、易、减二、斐波那契、和;毕。 46 | 珠玑> 十、斐波那契。 47 | 五十五。 48 | ``` 49 | -------------------------------------------------------------------------------- /exec.go: -------------------------------------------------------------------------------- 1 | package zhuji 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | ) 7 | 8 | var ( 9 | defs = map[string][]*Word{} 10 | stack []int64 11 | conds []bool 12 | ) 13 | 14 | func exec(words []*Word) bool { 15 | if len(words) > 1 && words[1].Name == "者" { 16 | defs[words[0].Name] = words[2:] 17 | return true 18 | } 19 | 20 | nconds := len(conds) 21 | defer func() { conds = conds[:nconds] }() 22 | 23 | for i := range words { 24 | word := words[i] 25 | if f := find(word); f != nil { 26 | if shoulddo() { 27 | f() 28 | } 29 | } else if f, ok := controls[word.Name]; ok { 30 | ok = f() 31 | if !ok { 32 | return false 33 | } 34 | } else { 35 | fmt.Printf("无「%s」。\n", word.Name) 36 | } 37 | } 38 | return true 39 | } 40 | 41 | func shoulddo() bool { 42 | for _, cond := range conds { 43 | if !cond { 44 | return false 45 | } 46 | } 47 | return true 48 | } 49 | 50 | func find(word *Word) func() { 51 | if def, ok := defs[word.Name]; ok { 52 | // Defined word. 53 | return func() { exec(def) } 54 | } else if builtin, ok := builtins[word.Name]; ok { 55 | // Builtin word. 56 | return builtin 57 | } else if word.isNumeral() { 58 | // Numeric word. 59 | return func() { 60 | num, rest := ParseNumeral(word.Name) 61 | if rest != "" { 62 | fmt.Printf("「%s」似数非数。\n", word.Name) 63 | } else { 64 | push(num) 65 | } 66 | } 67 | } 68 | return nil 69 | } 70 | 71 | func ExecArticle(a Article) { 72 | for _, s := range a.Sentences { 73 | exec(s.Words) 74 | } 75 | } 76 | 77 | func printWords(words []*Word) string { 78 | var b bytes.Buffer 79 | for i, w := range words { 80 | if i > 0 { 81 | b.WriteRune('、') 82 | } 83 | b.WriteString(w.Name) 84 | } 85 | return b.String() 86 | } 87 | 88 | func ShowIfNonEmpty() { 89 | if len(stack) > 0 { 90 | for i, n := range stack { 91 | if i > 0 { 92 | fmt.Print("、") 93 | } 94 | fmt.Print(ToNumeral(int64(n))) 95 | } 96 | fmt.Println("。") 97 | } 98 | } 99 | 100 | var controls = map[string]func() bool{ 101 | "则": 则, "非": 非, "毕": 毕, 102 | } 103 | 104 | func 则() bool { 105 | if len(stack) == 0 { 106 | fmt.Println("栈上无元,不知其可。") 107 | return false 108 | } 109 | conds = append(conds, pop() != 0) 110 | return true 111 | } 112 | 113 | func 非() bool { 114 | if len(conds) == 0 { 115 | fmt.Println("无则有非,不知其可。") 116 | return false 117 | } 118 | c := &conds[len(conds)-1] 119 | *c = !*c 120 | return true 121 | } 122 | 123 | func 毕() bool { 124 | if len(conds) == 0 { 125 | fmt.Println("无则有毕,不知其可。") 126 | return false 127 | } 128 | conds = conds[:len(conds)-1] 129 | return true 130 | } 131 | 132 | var builtins = map[string]func(){ 133 | // Arithmetic operations. 134 | "加": 加, "和": 加, "减": 减, "负": 负, "乘": 乘, "除": 除, 135 | "次方": 次方, 136 | // Arithmetic predicates. 137 | "等于": 等于, "大于": 大于, "小于": 小于, 138 | // Stack operations. 139 | "复": 自, "易": 易, "自": 自, "弃": 弃, 140 | } 141 | 142 | func top() int64 { 143 | return stack[len(stack)-1] 144 | } 145 | 146 | func pop() int64 { 147 | i := top() 148 | stack = stack[:len(stack)-1] 149 | return i 150 | } 151 | 152 | func push(i int64) { 153 | stack = append(stack, i) 154 | } 155 | 156 | func pushBool(b bool) { 157 | if b { 158 | push(-1) 159 | } else { 160 | push(0) 161 | } 162 | } 163 | 164 | func atleast(n int) bool { 165 | if len(stack) < n { 166 | if n == 1 { 167 | fmt.Println("无元。") 168 | } else { 169 | fmt.Printf("无%s元。\n", ToNumeral(int64(n))) 170 | } 171 | return false 172 | } 173 | return true 174 | } 175 | 176 | func 加() { 177 | if atleast(2) { 178 | push(pop() + pop()) 179 | } 180 | } 181 | 182 | func 减() { 183 | if atleast(2) { 184 | push(-(pop() - pop())) 185 | } 186 | } 187 | 188 | func 负() { 189 | if atleast(1) { 190 | push(-pop()) 191 | } 192 | } 193 | 194 | func 乘() { 195 | if atleast(2) { 196 | push(pop() * pop()) 197 | } 198 | } 199 | 200 | func 除() { 201 | if atleast(2) { 202 | a := pop() 203 | b := pop() 204 | push(b / a) 205 | } 206 | } 207 | 208 | func pow(b, n int64) int64 { 209 | if n == 0 { 210 | return 1 211 | } 212 | x := pow(b, n/2) 213 | x *= x 214 | if n%2 == 1 { 215 | x *= b 216 | } 217 | return x 218 | } 219 | 220 | func 次方() { 221 | if atleast(2) { 222 | a := pop() 223 | b := pop() 224 | push(pow(b, a)) 225 | } 226 | } 227 | 228 | func 等于() { 229 | if atleast(2) { 230 | pushBool(pop() == pop()) 231 | } 232 | } 233 | 234 | func 小于() { 235 | if atleast(2) { 236 | pushBool(pop() > pop()) 237 | } 238 | } 239 | 240 | func 大于() { 241 | if atleast(2) { 242 | pushBool(pop() < pop()) 243 | } 244 | } 245 | 246 | func 易() { 247 | if atleast(2) { 248 | a := pop() 249 | b := pop() 250 | push(a) 251 | push(b) 252 | } 253 | } 254 | 255 | func 自() { 256 | if atleast(1) { 257 | push(top()) 258 | } 259 | } 260 | 261 | func 弃() { 262 | if atleast(1) { 263 | pop() 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /numeral.go: -------------------------------------------------------------------------------- 1 | package zhuji 2 | 3 | import ( 4 | "strings" 5 | "unicode/utf8" 6 | ) 7 | 8 | var ones = "零一二三四五六七八九" 9 | 10 | var numerals = ones + "十廿卅千百万亿" 11 | 12 | var tens = []struct { 13 | s string 14 | m int 15 | canonical bool 16 | }{ 17 | {"千", 1e3, true}, 18 | {"百", 1e2, true}, 19 | {"卅", 30, false}, 20 | {"廿", 20, false}, 21 | {"十", 10, true}, 22 | } 23 | 24 | func oneDigit(s string) (num int, rest string) { 25 | if len(s) != 3 { 26 | return 0, s 27 | } 28 | i := strings.Index(ones, s) 29 | if i == -1 { 30 | if s == "两" { 31 | return 2, "" 32 | } 33 | return 0, s 34 | } 35 | return i / 3, "" 36 | } 37 | 38 | func inMyriad(s string) (num int, rest string) { 39 | for _, digit := range tens { 40 | if r, si := utf8.DecodeRuneInString(s); r == '零' { 41 | s = s[si:] 42 | continue 43 | } 44 | 45 | if i := strings.Index(s, digit.s); i != -1 { 46 | n, r := oneDigit(s[:i]) 47 | // fmt.Printf("%s at %d: %d, %d (%s)\n", digit.s, i, n, j, s) 48 | if r != "" { 49 | return 0, s 50 | } 51 | if i == 0 { 52 | n = 1 53 | } 54 | num += n * digit.m 55 | s = s[i+len(digit.s):] 56 | } 57 | } 58 | n, r := oneDigit(s) 59 | num += n 60 | s = r 61 | return num, s 62 | } 63 | 64 | var myriads = []struct { 65 | s string 66 | m int64 67 | }{ 68 | {"万万亿", 1e16}, 69 | {"万亿", 1e12}, 70 | {"亿", 1e8}, 71 | {"万万", 1e8}, 72 | {"万", 1e4}, 73 | } 74 | 75 | func ParseNumeral(s string) (num int64, rest string) { 76 | m := int64(1) 77 | if r, si := utf8.DecodeRuneInString(s); r == '负' { 78 | m = -1 79 | s = s[si:] 80 | } 81 | for _, myriad := range myriads { 82 | if i := strings.Index(s, myriad.s); i != -1 { 83 | var n int 84 | if i == 0 { 85 | n = 1 86 | } else { 87 | var rest string 88 | n, rest = inMyriad(s[:i]) 89 | if rest != "" { 90 | return 0, s 91 | } 92 | } 93 | num += int64(n) * myriad.m 94 | s = s[i+len(myriad.s):] 95 | } 96 | } 97 | n, rest := inMyriad(s) 98 | num += int64(n) 99 | s = rest 100 | 101 | num *= m 102 | return num, s 103 | } 104 | 105 | func toMyriad(num int, ling bool) string { 106 | var s string 107 | lastm := 10000 108 | for _, ten := range tens { 109 | if !ten.canonical { 110 | continue 111 | } 112 | if num >= ten.m { 113 | if ling && lastm != ten.m*10 { 114 | s += "零" 115 | } 116 | one := num / ten.m 117 | if one != 1 || ten.m != 10 { // 10 is 十 not 一十 118 | s += ones[one*3 : one*3+3] 119 | } 120 | s += ten.s 121 | num %= ten.m 122 | lastm = ten.m 123 | ling = true 124 | } 125 | } 126 | if num > 0 { 127 | if ling && lastm != 10 { 128 | s += "零" 129 | } 130 | s += ones[num*3 : num*3+3] 131 | } 132 | return s 133 | } 134 | 135 | func ToNumeral(num int64) string { 136 | if num == -9223372036854775808 { 137 | return "九百二十二万万亿三千三百七十二万亿零三百六十八亿五千四百七十七万五千八百零八。" 138 | } 139 | var s string 140 | if num < 0 { 141 | s = "负" 142 | num = -num 143 | } 144 | ling := false 145 | for _, myriad := range myriads { 146 | if num >= myriad.m { 147 | s += toMyriad(int(num/myriad.m), ling) + myriad.s 148 | num %= myriad.m 149 | ling = true 150 | } 151 | } 152 | s += toMyriad(int(num), ling) 153 | if s == "" { 154 | s = "零" 155 | } 156 | return s 157 | } 158 | -------------------------------------------------------------------------------- /numeral_test.go: -------------------------------------------------------------------------------- 1 | package zhuji 2 | 3 | import "testing" 4 | 5 | var numeralCases = []struct { 6 | num int64 7 | s string 8 | canonical bool 9 | }{ 10 | {-100, "负一百", true}, 11 | {0, "零", true}, 12 | {1, "一", true}, 13 | {10, "十", true}, 14 | {11, "十一", true}, 15 | {42, "四十二", true}, 16 | {1001, "一千零一", true}, 17 | {1234, "一千二百三十四", true}, 18 | {20401, "二万零四百零一", true}, 19 | {123456789, "一亿二千三百四十五万六千七百八十九", true}, 20 | {22222, "两万两千两百两十两", false}, 21 | {31, "卅一", false}, 22 | {29, "廿九", false}, 23 | {1e4, "一万", true}, 24 | {1e4, "万", false}, 25 | } 26 | 27 | func TestNumeral(t *testing.T) { 28 | for _, tc := range numeralCases { 29 | num, rest := ParseNumeral(tc.s) 30 | if num != tc.num || rest != "" { 31 | t.Errorf(`ParseNumeral(%s) => (%d, %s), want (%d, "")`, 32 | tc.s, num, rest, tc.num) 33 | } 34 | if tc.canonical { 35 | s := ToNumeral(tc.num) 36 | if s != tc.s { 37 | t.Errorf("ToNumeral(%d) => %s, want %s", tc.num, s, tc.s) 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /parse.go: -------------------------------------------------------------------------------- 1 | package zhuji 2 | 3 | import ( 4 | "strings" 5 | "unicode/utf8" 6 | ) 7 | 8 | type parser struct { 9 | source string 10 | pos int 11 | } 12 | 13 | func newParser(source string) *parser { 14 | return &parser{source, 0} 15 | } 16 | 17 | // Parser primaries. 18 | 19 | func (p *parser) eof() bool { 20 | return p.pos == len(p.source) 21 | } 22 | 23 | func (p *parser) rest() string { 24 | return p.source[p.pos:] 25 | } 26 | 27 | func (p *parser) next() rune { 28 | r, size := utf8.DecodeRuneInString(p.rest()) 29 | p.pos += size 30 | return r 31 | } 32 | 33 | func (p *parser) peek() rune { 34 | r, _ := utf8.DecodeRuneInString(p.rest()) 35 | return r 36 | } 37 | 38 | func (p *parser) from(i int) string { 39 | return p.source[i:p.pos] 40 | } 41 | 42 | func (p *parser) to(i int) string { 43 | rest := p.rest() 44 | if i == -1 { 45 | return rest 46 | } 47 | return rest[:i] 48 | } 49 | 50 | func (p *parser) uptoFunc(f func(rune) bool) string { 51 | return p.to(strings.IndexFunc(p.rest(), f)) 52 | } 53 | 54 | func (p *parser) uptoAny(chars string) string { 55 | return p.to(strings.IndexAny(p.rest(), chars)) 56 | } 57 | 58 | func in(r rune, s string) bool { 59 | return strings.ContainsRune(s, r) 60 | } 61 | 62 | // Parsing functions. 63 | 64 | var ( 65 | keywords = "者自若则非毕" 66 | wordSep = "、,:;" 67 | sentenceSep = "也。\n" 68 | wordTerm = wordSep + sentenceSep 69 | ) 70 | 71 | type Word struct { 72 | Name string 73 | IsKeyword bool 74 | } 75 | 76 | func (w *Word) isNumeral() bool { 77 | r, _ := utf8.DecodeRuneInString(w.Name) 78 | return in(r, numerals) 79 | } 80 | 81 | func (w *Word) String() string { 82 | return w.Name 83 | } 84 | 85 | func (p *parser) word() (w *Word) { 86 | begin := p.pos 87 | 88 | w = &Word{} 89 | defer func() { 90 | w.Name = p.from(begin) 91 | }() 92 | 93 | if r := p.next(); in(r, keywords) { 94 | w.IsKeyword = true 95 | return 96 | } else if in(r, numerals) { 97 | for in(p.peek(), numerals) { 98 | p.next() 99 | } 100 | return 101 | } 102 | 103 | for !p.eof() { 104 | r := p.peek() 105 | 106 | if in(r, numerals) || in(r, keywords) || in(r, wordTerm) { 107 | return 108 | } 109 | p.next() 110 | } 111 | return 112 | } 113 | 114 | type Sentence struct { 115 | Words []*Word 116 | } 117 | 118 | func (p *parser) sentence() Sentence { 119 | s := Sentence{} 120 | jux := false 121 | for !p.eof() { 122 | word := p.word() 123 | s.Words = append(s.Words, word) 124 | if jux && word.isNumeral() { 125 | // Numeral juxtaposes another word: swap unless the previous word 126 | // is a keyword. This enables infix notation. 127 | n := len(s.Words) 128 | if !s.Words[n-2].IsKeyword { 129 | s.Words[n-1], s.Words[n-2] = s.Words[n-2], s.Words[n-1] 130 | } 131 | } 132 | jux = true 133 | for !p.eof() { 134 | r := p.peek() 135 | if in(r, wordSep) { 136 | p.next() 137 | jux = false 138 | } else if in(r, sentenceSep) { 139 | p.next() 140 | for in(p.peek(), sentenceSep) { 141 | p.next() 142 | } 143 | return s 144 | } else { 145 | break 146 | } 147 | } 148 | } 149 | return s 150 | } 151 | 152 | type Article struct { 153 | Sentences []Sentence 154 | } 155 | 156 | func (p *parser) article() Article { 157 | a := Article{} 158 | for !p.eof() { 159 | a.Sentences = append(a.Sentences, p.sentence()) 160 | } 161 | return a 162 | } 163 | 164 | func Parse(source string) Article { 165 | return newParser(source).article() 166 | } 167 | -------------------------------------------------------------------------------- /zhuji/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "os" 9 | "unicode/utf8" 10 | 11 | "github.com/xiaq/zhuji" 12 | ) 13 | 14 | var debug = flag.Bool("debug", false, "debug") 15 | 16 | func main() { 17 | flag.Parse() 18 | stdin := bufio.NewReader(os.Stdin) 19 | for { 20 | fmt.Print("珠玑> ") 21 | line, err := stdin.ReadString('\n') 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | if !utf8.ValidString(line) { 26 | fmt.Println("not UTF-8") 27 | continue 28 | } 29 | article := zhuji.Parse(line[:len(line)-1]) 30 | if *debug { 31 | fmt.Println(article) 32 | } 33 | zhuji.ExecArticle(article) 34 | zhuji.ShowIfNonEmpty() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /珠玑: -------------------------------------------------------------------------------- 1 | zhuji --------------------------------------------------------------------------------