Guest User

Untitled

a guest
Nov 16th, 2018
109
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.38 KB | None | 0 0
  1. package locale
  2.  
  3. import (
  4. "bufio"
  5. "fmt"
  6. "io"
  7. "regexp"
  8. "strconv"
  9. "strings"
  10. "unicode/utf8"
  11. )
  12.  
  13. // TODO: Is this correct? Does it handle malformed input?
  14. var wordRegex = regexp.MustCompile(`[^\s"']+|"([^"]*)"|'([^']*)`)
  15.  
  16. // Parse transforms a text-definition of a locale into a Def
  17. func Parse(in io.Reader) (def Def, err error) {
  18. s := bufio.NewScanner(in)
  19.  
  20. commentChar := '#'
  21. escapeChar := '\\'
  22. lineNum := 0
  23. curCategory := ""
  24.  
  25. // Catch any errors
  26. defer func() {
  27. if r := recover(); r != nil {
  28. def = Def{}
  29. err = fmt.Errorf("Line %d: %s", lineNum, r)
  30. }
  31. }()
  32.  
  33. // A closure to simplify error handling
  34. die := func(format string, args ...interface{}) {
  35. panic(fmt.Errorf("line %d: %s", lineNum, fmt.Sprintf(format, args)))
  36. }
  37.  
  38. // More closures to make assertions and parse input
  39. requireCategory := func(category, keyword string) {
  40. if category != curCategory {
  41. die("keyword %s is only applicable in category %s (current: '%s')", keyword, category, curCategory)
  42. }
  43. }
  44. requireString := func(val string) string {
  45. // TODO: Bug with escaped quote characters.
  46. if val[0] != '"' || val[len(val)-1] != '"' {
  47. die("Value '%s' needs to be a double-quoted string", val)
  48. }
  49. return val[1 : len(val)-1]
  50. }
  51. requireInt := func(val string) int {
  52. n, err := strconv.Atoi(val)
  53. if err != nil {
  54. die("%s is not an integer", val)
  55. }
  56. return n
  57. }
  58. requireChar := func(val string) string {
  59. if val[0] != '<' || val[len(val)-1] != '>' {
  60. die("character %s must be surrounded by angle brackets", val)
  61. }
  62. return val[1 : len(val)-1]
  63. }
  64. requireCharList := func(val string) []string {
  65. var out []string
  66. for _, s := range strings.Split(val, ";") {
  67. out = append(out, requireChar(s))
  68. }
  69. return out
  70. }
  71. requireCharPair := func(pair string) (first, second string) {
  72. if pair[0] != '(' || pair[len(pair)-1] != ')' {
  73. die("character pair '%s' not in parentheses", pair)
  74. }
  75. parts := strings.Split(pair[1:len(pair)-1], ",")
  76. if len(parts) != 2 {
  77. die("Need 2 elements in pair '%s'", pair)
  78. }
  79. return requireChar(parts[0]), requireChar(parts[1])
  80. }
  81.  
  82. for s.Scan() {
  83. lineNum++
  84. line := s.Text()
  85. // Discard comment lines
  86. firstRune, _ := utf8.DecodeRuneInString(line)
  87. if firstRune == commentChar {
  88. continue
  89. }
  90.  
  91. // Join newlines if they end with the escapeChar
  92. // TODO: This is very inefficient
  93. for {
  94. var lastRune rune
  95. for _, r := range line {
  96. lastRune = r
  97. }
  98. if lastRune != escapeChar {
  99. break
  100. }
  101. if lastRune == escapeChar {
  102. if !s.Scan() {
  103. die("Line continuation at end of file")
  104. return Def{}, fmt.Errorf("line continuation on line %d was not followed by another line", lineNum)
  105. }
  106. line += strings.TrimSpace(s.Text())
  107. lineNum++
  108. }
  109. }
  110.  
  111. // TODO: Find more robust way to split
  112. words := wordRegex.FindAllString(line, -1)
  113. if len(words) == 0 {
  114. continue
  115. }
  116.  
  117. if len(words) == 1 {
  118. // Only category declarations can have only 1 word
  119. switch words[0] {
  120. case "LC_CTYPE", "LC_COLLATE", "LC_MONETARY", "LC_NUMERIC", "LC_TIME", "LC_MESSAGES":
  121. curCategory = words[0]
  122. default:
  123. die("Unrecognized category name %s", words[0])
  124. }
  125. continue
  126. }
  127.  
  128. // No line can have more than 2 words
  129. if len(words) > 2 {
  130. return Def{}, fmt.Errorf("line %d contains too many words: %s", lineNum, words)
  131. }
  132.  
  133. // Logic!
  134. keyword, val := words[0], words[1]
  135. switch keyword {
  136. // Header blocks
  137. case "comment_char":
  138. if curCategory != "" {
  139. return Def{}, fmt.Errorf("line %d: unexpected %s during category %s", lineNum, keyword, curCategory)
  140. }
  141. commentChar, _ = utf8.DecodeRuneInString(words[1])
  142. case "escape_char":
  143. if curCategory != "" {
  144. return Def{}, fmt.Errorf("line %d: unexpected %s during category %s", lineNum, keyword, curCategory)
  145. }
  146. escapeChar, _ = utf8.DecodeRuneInString(words[1])
  147. // End block
  148. case "END":
  149. if val != curCategory {
  150. return Def{}, fmt.Errorf("line %d: tried to end category %s during %s", lineNum, val, curCategory)
  151. }
  152. curCategory = ""
  153. // Ctype values
  154. case "charclass":
  155. requireCategory("LC_CTYPE", keyword)
  156. for _, cc := range strings.Split(val, ";") {
  157. if _, in := def.ctype.other[cc]; !in {
  158. def.ctype.other[cc] = make([]string, 0)
  159. }
  160. }
  161. case "upper":
  162. requireCategory("LC_CTYPE", keyword)
  163. def.ctype.upper = requireCharList(val)
  164. case "lower":
  165. requireCategory("LC_CTYPE", keyword)
  166. def.ctype.lower = requireCharList(val)
  167. case "alpha":
  168. requireCategory("LC_CTYPE", keyword)
  169. def.ctype.alpha = requireCharList(val)
  170. case "digit":
  171. requireCategory("LC_CTYPE", keyword)
  172. def.ctype.digit = requireCharList(val)
  173. case "alnum":
  174. requireCategory("LC_CTYPE", keyword)
  175. def.ctype.alnum = requireCharList(val)
  176. case "space":
  177. requireCategory("LC_CTYPE", keyword)
  178. def.ctype.space = requireCharList(val)
  179. case "cntrl":
  180. requireCategory("LC_CTYPE", keyword)
  181. def.ctype.cntrl = requireCharList(val)
  182. case "punct":
  183. requireCategory("LC_CTYPE", keyword)
  184. def.ctype.punct = requireCharList(val)
  185. case "graph":
  186. requireCategory("LC_CTYPE", keyword)
  187. def.ctype.graph = requireCharList(val)
  188. case "print":
  189. requireCategory("LC_CTYPE", keyword)
  190. def.ctype.print = requireCharList(val)
  191. case "xdigit":
  192. requireCategory("LC_CTYPE", keyword)
  193. def.ctype.xdigit = requireCharList(val)
  194. case "blank":
  195. requireCategory("LC_CTYPE", keyword)
  196. def.ctype.blank = requireCharList(val)
  197. case "toupper":
  198. for _, pair := range strings.Split(val, ";") {
  199. lower, upper := requireCharPair(pair)
  200. def.ctype.toupper[lower] = upper
  201. }
  202. case "tolower":
  203. for _, pair := range strings.Split(val, ";") {
  204. upper, lower := requireCharPair(pair)
  205. def.ctype.tolower[upper] = lower
  206. }
  207.  
  208. // Monetary values
  209. case "int_curr_symbol":
  210. requireCategory("LC_MONETARY", keyword)
  211. def.monetary.intCurrSymbol = requireString(val)
  212. case "currency_symbol":
  213. requireCategory("LC_MONETARY", keyword)
  214. def.monetary.currencySymbol = requireString(val)
  215. case "mon_decimal_point":
  216. requireCategory("LC_MONETARY", keyword)
  217. def.monetary.monDecimalPoint = requireString(val)
  218. case "mon_thousands_sep":
  219. requireCategory("LC_MONETARY", keyword)
  220. def.monetary.monThousandsSep = requireString(val)
  221. case "mon_grouping":
  222. requireCategory("LC_MONETARY", keyword)
  223. for _, s := range strings.Split(val, ";") {
  224. def.monetary.monGrouping = append(def.monetary.monGrouping, requireInt(s))
  225. }
  226. case "positive_sign":
  227. requireCategory("LC_MONETARY", keyword)
  228. def.monetary.positiveSign = val
  229. case "negative_sign":
  230. requireCategory("LC_MONETARY", keyword)
  231. def.monetary.negativeSign = val
  232. case "int_frac_digits":
  233. requireCategory("LC_MONETARY", keyword)
  234. def.monetary.intFracDigits = requireInt(val)
  235. case "frac_digits":
  236. requireCategory("LC_MONETARY", keyword)
  237. def.monetary.fracDigits = requireInt(val)
  238. case "p_cs_precedes":
  239. requireCategory("LC_MONETARY", keyword)
  240. def.monetary.pCsPrecedes = requireInt(val)
  241. case "p_sep_by_space":
  242. requireCategory("LC_MONETARY", keyword)
  243. def.monetary.pSepBySpace = requireInt(val)
  244. case "n_cs_precedes":
  245. requireCategory("LC_MONETARY", keyword)
  246. def.monetary.nCsPrecedes = requireInt(val)
  247. case "n_sep_by_space":
  248. requireCategory("LC_MONETARY", keyword)
  249. def.monetary.nSepBySpace = requireInt(val)
  250. case "p_sign_posn":
  251. requireCategory("LC_MONETARY", keyword)
  252. def.monetary.pSignPosn = requireInt(val)
  253. case "n_sign_posn":
  254. requireCategory("LC_MONETARY", keyword)
  255. def.monetary.nSignPosn = requireInt(val)
  256. case "int_p_cs_precedes":
  257. requireCategory("LC_MONETARY", keyword)
  258. def.monetary.intPcsPrecedes = requireInt(val)
  259. case "int_p_sep_by_space":
  260. requireCategory("LC_MONETARY", keyword)
  261. def.monetary.intPsepBySpace = requireInt(val)
  262. case "int_n_cs_precedes":
  263. requireCategory("LC_MONETARY", keyword)
  264. def.monetary.intNcsPrecedes = requireInt(val)
  265. case "int_n_sep_by_space":
  266. requireCategory("LC_MONETARY", keyword)
  267. def.monetary.intNsepBySpace = requireInt(val)
  268. case "int_p_sign_posn":
  269. requireCategory("LC_MONETARY", keyword)
  270. def.monetary.intPsignPosn = requireInt(val)
  271. case "int_n_sign_posn":
  272. requireCategory("LC_MONETARY", keyword)
  273. def.monetary.intNsignPosn = requireInt(val)
  274. default:
  275. // Handle more complex values
  276. switch {
  277. case curCategory == "LC_CTYPE" && def.ctype.other[keyword] != nil:
  278. def.ctype.other[keyword] = append(def.ctype.other[keyword], val)
  279.  
  280. default:
  281. die("misc error: %s", line)
  282. }
  283. }
  284.  
  285. }
  286.  
  287. if curCategory != "" {
  288. die("Category %s never ended", curCategory)
  289. }
  290.  
  291. return def, nil
  292. }
Add Comment
Please, Sign In to add comment