parse.lua 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. local lpeg = require 'lpeg'
  2. local P, R, S, V, C, Ct, Cmt, Cg, Cc, Cf, Cmt = lpeg.P, lpeg.R, lpeg.S, lpeg.V, lpeg.C, lpeg.Ct, lpeg.Cmt, lpeg.Cg, lpeg.Cc, lpeg.Cf, lpeg.Cmt
  3. local line = 1
  4. local lastLinePos = 1
  5. local function pack(...)
  6. return { n = select('#', ...), ... }
  7. end
  8. -- Utility
  9. local space = Cmt(S(' \t\r\n') ^ 0, function(str, pos)
  10. str = str:sub(lastLinePos, pos)
  11. while str:find('\n') do
  12. line = line + 1
  13. lastLinePos = pos
  14. str = str:sub(str:find('\n') + 1)
  15. end
  16. return true
  17. end)
  18. local comma = P(',') ^ 0
  19. local function cName(name)
  20. if #name == 0 then return nil end
  21. return {
  22. kind = 'name',
  23. value = name
  24. }
  25. end
  26. local function cInt(value)
  27. return {
  28. kind = 'int',
  29. value = value
  30. }
  31. end
  32. local function cFloat(value)
  33. return {
  34. kind = 'float',
  35. value = value
  36. }
  37. end
  38. local function cBoolean(value)
  39. return {
  40. kind = 'boolean',
  41. value = value
  42. }
  43. end
  44. local function cString(value)
  45. return {
  46. kind = 'string',
  47. value = value
  48. }
  49. end
  50. local function cEnum(value)
  51. return {
  52. kind = 'enum',
  53. value = value
  54. }
  55. end
  56. local function cList(value)
  57. return {
  58. kind = 'list',
  59. values = value
  60. }
  61. end
  62. local function cObjectField(name, value)
  63. return {
  64. name = name,
  65. value = value
  66. }
  67. end
  68. local function cObject(fields)
  69. return {
  70. kind = 'inputObject',
  71. values = fields
  72. }
  73. end
  74. local function cAlias(name)
  75. return {
  76. kind = 'alias',
  77. name = name
  78. }
  79. end
  80. local function cArgument(name, value)
  81. return {
  82. kind = 'argument',
  83. name = name,
  84. value = value
  85. }
  86. end
  87. local function cField(...)
  88. local tokens = pack(...)
  89. local field = { kind = 'field' }
  90. for i = 1, #tokens do
  91. local key = tokens[i].kind
  92. if not key then
  93. if tokens[i][1].kind == 'argument' then
  94. key = 'arguments'
  95. elseif tokens[i][1].kind == 'directive' then
  96. key = 'directives'
  97. end
  98. end
  99. field[key] = tokens[i]
  100. end
  101. return field
  102. end
  103. local function cSelectionSet(selections)
  104. return {
  105. kind = 'selectionSet',
  106. selections = selections
  107. }
  108. end
  109. local function cFragmentSpread(name)
  110. return {
  111. kind = 'fragmentSpread',
  112. name = name
  113. }
  114. end
  115. local function cOperation(...)
  116. local args = pack(...)
  117. if args[1].kind == 'selectionSet' then
  118. return {
  119. kind = 'operation',
  120. operation = 'query',
  121. selectionSet = args[1]
  122. }
  123. else
  124. local result = {
  125. kind = 'operation',
  126. operation = args[1]
  127. }
  128. for i = 2, #args do
  129. local key = args[i].kind
  130. if not key then
  131. if args[i][1].kind == 'variableDefinition' then
  132. key = 'variableDefinitions'
  133. elseif args[i][1].kind == 'directive' then
  134. key = 'directives'
  135. end
  136. end
  137. result[key] = args[i]
  138. end
  139. return result
  140. end
  141. end
  142. local function cDocument(definitions)
  143. return {
  144. kind = 'document',
  145. definitions = definitions
  146. }
  147. end
  148. local function cFragmentDefinition(name, typeCondition, selectionSet)
  149. return {
  150. kind = 'fragmentDefinition',
  151. name = name,
  152. typeCondition = typeCondition,
  153. selectionSet = selectionSet
  154. }
  155. end
  156. local function cNamedType(name)
  157. return {
  158. kind = 'namedType',
  159. name = name
  160. }
  161. end
  162. local function cListType(type)
  163. return {
  164. kind = 'listType',
  165. type = type
  166. }
  167. end
  168. local function cNonNullType(type)
  169. return {
  170. kind = 'nonNullType',
  171. type = type
  172. }
  173. end
  174. local function cInlineFragment(...)
  175. local args = pack(...)
  176. if #args == 2 then
  177. return {
  178. kind = 'inlineFragment',
  179. typeCondition = args[1],
  180. selectionSet = args[2]
  181. }
  182. elseif #args == 1 then
  183. return {
  184. kind = 'inlineFragment',
  185. selectionSet = args[1]
  186. }
  187. end
  188. end
  189. local function cVariable(name)
  190. return {
  191. kind = 'variable',
  192. name = name
  193. }
  194. end
  195. local function cVariableDefinition(variable, type, defaultValue)
  196. return {
  197. kind = 'variableDefinition',
  198. variable = variable,
  199. type = type,
  200. defaultValue = defaultValue
  201. }
  202. end
  203. local function cDirective(name, arguments)
  204. return {
  205. kind = 'directive',
  206. name = name,
  207. arguments = arguments
  208. }
  209. end
  210. -- "Terminals"
  211. local rawName = R('az', 'AZ') * (P('_') + R('09') + R('az', 'AZ')) ^ 0
  212. local name = rawName / cName
  213. local alias = space * name * P(':') * space / cAlias
  214. local integerPart = P('-') ^ -1 * (P('0') + R('19') * R('09') ^ 0)
  215. local intValue = integerPart / cInt
  216. local fractionalPart = P('.') * R('09') ^ 1
  217. local exponentialPart = S('Ee') * S('+-') ^ -1 * R('09') ^ 1
  218. local floatValue = integerPart * (fractionalPart + exponentialPart + (fractionalPart * exponentialPart)) / cFloat
  219. local booleanValue = (P('true') + P('false')) / cBoolean
  220. local stringValue = P('"') * C((P('\\"') + 1 - S('"\n')) ^ 0) * P('"') / cString
  221. local enumValue = (rawName - 'true' - 'false' - 'null') / cEnum
  222. local fragmentName = (rawName - 'on') / cName
  223. local fragmentSpread = space * P('...') * fragmentName / cFragmentSpread
  224. local operationType = C(P('query') + P('mutation'))
  225. local variable = space * P('$') * name / cVariable
  226. -- Nonterminals
  227. local graphQL = P {
  228. 'document',
  229. document = space * Ct((V('definition') * comma * space) ^ 0) / cDocument * -1,
  230. definition = V('operation') + V('fragmentDefinition'),
  231. operation = (operationType * space * name ^ -1 * V('variableDefinitions') ^ -1 * V('directives') ^ -1 * V('selectionSet') + V('selectionSet')) / cOperation,
  232. fragmentDefinition = P('fragment') * space * fragmentName * space * V('typeCondition') * space * V('selectionSet') / cFragmentDefinition,
  233. inlineFragment = P('...') * space * V('typeCondition') ^ -1 * V('selectionSet') / cInlineFragment,
  234. selectionSet = space * P('{') * space * Ct(V('selection') ^ 0) * space * P('}') / cSelectionSet,
  235. selection = space * (V('field') + fragmentSpread + V('inlineFragment')),
  236. field = space * alias ^ -1 * name * V('arguments') ^ -1 * V('directives') ^ -1 * V('selectionSet') ^ -1 * comma / cField,
  237. argument = space * name * P(':') * V('value') * comma / cArgument,
  238. arguments = P('(') * Ct(V('argument') ^ 1) * P(')'),
  239. value = space * (variable + V('objectValue') + V('listValue') + enumValue + stringValue + booleanValue + floatValue + intValue),
  240. listValue = P('[') * Ct((V('value') * comma) ^ 0) * P(']') / cList,
  241. objectFieldValue = space * C(rawName) * space * P(':') * space * V('value') * comma / cObjectField,
  242. objectValue = P('{') * space * Ct(V('objectFieldValue') ^ 0) * space * P('}') / cObject,
  243. type = V('nonNullType') + V('listType') + V('namedType'),
  244. namedType = name / cNamedType,
  245. listType = P('[') * space * V('type') * space * P(']') / cListType,
  246. nonNullType = (V('namedType') + V('listType')) * P('!') / cNonNullType,
  247. typeCondition = P('on') * space * V('namedType'),
  248. variableDefinition = space * variable * space * P(':') * space * V('type') * (space * P('=') * V('value')) ^ -1 * comma * space / cVariableDefinition,
  249. variableDefinitions = P('(') * Ct(V('variableDefinition') ^ 1) * P(')'),
  250. directive = P('@') * name * V('arguments') ^ -1 / cDirective,
  251. directives = space * Ct((V('directive') * comma * space) ^ 1) * space
  252. }
  253. return function(str)
  254. assert(type(str) == 'string', 'parser expects a string')
  255. str = (str .. '\n'):gsub('(.-\n)', function(line)
  256. local index = 1
  257. while line:find('#', index) do
  258. local pos = line:find('#', index) - 1
  259. local chunk = line:sub(1, pos)
  260. local _, quotes = chunk:gsub('([^\\]")', '')
  261. if quotes % 2 == 0 then
  262. return chunk .. '\n'
  263. else
  264. index = pos + 2
  265. end
  266. end
  267. return line
  268. end)
  269. local match = graphQL:match(str)
  270. if not match then
  271. error('Syntax error near line ' .. line, 2)
  272. end
  273. return match
  274. end