package main import ( "fmt" "math" "regexp" "strconv" "strings" ) const ( typeUnknown = iota typeNumber typeBool typeWord typeList ) const ( _name = `([A-Za-z]\w*)` _nameOrInt = "(" + _name + `|\d+)` _index = "(" + _nameOrInt + "|:|" + _nameOrInt + ":|:" + _nameOrInt + "|" + _nameOrInt + ":" + _nameOrInt + ")" ) var nameExp = regexp.MustCompile(`^"` + _name + "$") var nameWithIndexExp = regexp.MustCompile(`^"` + _name + `((\[` + _index + `])*\[` + _index + `\\])?$`) func isNumber(val value) bool { if val.tp == typeNumber { return true } if isWord(val) { val.word = val.word[1:] } _, err := strconv.ParseFloat(val.word, 64) return err == nil } func toNumber(val value) float64 { if val.tp == typeNumber { return val.num } if isWord(val) { val.word = val.word[1:] } num, err := strconv.ParseFloat(val.word, 64) if err != nil { panic(fmt.Errorf("number: %s (%s)", errInvalidNumber, toString(val))) } return num } func toInt(val value, envs []environ) int { if isName(value{word: `"` + val.word}) { newVal, err := getValue(`"`+val.word, envs) if err == nil { val = newVal if val.tp == typeNumber { if val.num != math.Floor(val.num) { panic(fmt.Errorf("int: %s (%f)", errInvalidInteger, val.num)) } return int(val.num) } } } if isWord(val) { val.word = val.word[1:] } num, err := strconv.ParseInt(val.word, 10, 64) if err != nil { panic(fmt.Errorf("int: %s (%s)", errInvalidInteger, toString(val))) } return int(num) } func isBool(val value) bool { if val.tp == typeBool { return true } if val.tp == typeNumber && (val.num == 0 || val.num == 1) { return true } if isWord(val) { val.word = val.word[1:] } return val.word == "false" || val.word == "true" } func toBool(val value) bool { if val.tp == typeBool { return val.b } if val.tp == typeNumber && (val.num == 0 || val.num == 1) { return val.num == 1 } if isWord(val) { val.word = val.word[1:] } if val.word == "false" || val.word == "true" { return val.word == "true" } panic(fmt.Errorf("bool: %s (%s)", errInvalidBool, toString(val))) } func isWord(val value) bool { if val.tp == typeWord { return true } return val.word != "" && val.word[0] == '"' } func parseWord(s string) value { if s != `"` { if s[1] == '[' { s = `"\[` + s[2:] } if s[len(s)-1] == ']' { s = s[:len(s)-1] escape := len(s) - len(strings.TrimRight(s, `\`)) if (escape & 1) == 0 { s += `\` } s += "]" } } return value{tp: typeWord, word: s} } func escapeWord(s string) string { for i := 0; i < len(s); i++ { if s[i] == '\\' { s = s[:i] + s[i+1:] } } return s } func isName(val value) bool { return nameExp.MatchString(val.word) } func toName(val value) string { if !isName(val) { panic(fmt.Errorf("name: %s (%s)", errInvalidName, toString(val))) } return val.word[1:] } func isNameWithIndex(val value) bool { return nameWithIndexExp.MatchString(val.word) } func toNameWithIndex(val value) (string, []string) { if !isNameWithIndex(val) { panic(fmt.Errorf("name: %s (%s)", errInvalidName, toString(val))) } index := strings.Split(val.word, "[") name, cnt := index[0][1:], len(index)-1 for i := 1; i < cnt; i++ { index[i] = index[i][:len(index[i])-1] } index[cnt] = index[cnt][:len(index[cnt])-2] return name, index[1:] } func isList(val value) bool { if val.tp == typeList { return true } return val.word != "" && val.word[0] == '[' } func parseFunc(s string, env environ) value { list := parseList(s) if len(list) != 2 || !isList(value{word: list[0]}) || !isList(value{word: list[1]}) { return value{tp: typeList, list: list} } param := parseList(list[0]) for _, name := range param { if !isName(value{word: `"` + name}) { return value{tp: typeList, list: list} } } local := environ{} for name, val := range env { local[name] = val } return value{tp: typeList, list: list, param: param, body: parseList(list[1]), env: local} } func isEmpty(val value) bool { if val.tp == typeWord { return val.word == `"` } if val.tp == typeList { return len(val.list) == 0 } return val.word == `"` || val.word == "[ ]" } func toValue(val value, env environ) value { if val.tp != typeUnknown { return val } if isNumber(val) { if isWord(val) { return value{tp: typeNumber, word: val.word, num: toNumber(val)} } return value{tp: typeNumber, num: toNumber(val)} } if isBool(val) { if isWord(val) { return value{tp: typeBool, word: val.word, b: toBool(val)} } return value{tp: typeBool, b: toBool(val)} } if isWord(val) { return parseWord(val.word) } if isList(val) { return parseFunc(val.word, env) } panic(fmt.Errorf("value: %s (%s)", errValueExpected, val.word)) } func toString(val value) string { if val.tp == typeNumber { if val.word != "" { return val.word[1:] } return strconv.FormatFloat(val.num, 'g', -1, 64) } if val.tp == typeBool { if val.word != "" { return val.word[1:] } return strconv.FormatBool(val.b) } if val.tp == typeWord { return val.word[1:] } if val.tp == typeList { return makeList(val.list) } panic(fmt.Errorf("string: %s (%s)", errValueExpected, val.word)) } func opIsNumber(val1 value) value { return value{tp: typeBool, b: isNumber(val1)} } func opIsBool(val1 value) value { return value{tp: typeBool, b: isBool(val1)} } func opIsWord(val1 value) value { return value{tp: typeBool, b: isWord(val1)} } func opIsName(val1 value, envs []environ) value { if !isName(val1) { return value{tp: typeBool, b: false} } name := toName(val1) for _, env := range envs { _, ok := env[name] if ok { return value{tp: typeBool, b: true} } } return value{tp: typeBool, b: false} } func opIsList(val1 value) value { return value{tp: typeBool, b: isList(val1)} } func opIsEmpty(val1 value) value { return value{tp: typeBool, b: isEmpty(val1)} }