parse.lua 7.7 KB

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