@@ -3,6 +3,10 @@ const _ = require('lodash');
33const { safeParseJson, outdentString } = require ( './utils' ) ;
44const parseExample = require ( './example/bruToJson' ) ;
55
6+ // this is done to avoid breaking existing pairlist mapping so
7+ // the key is hidden and not added into the json automatically
8+ const ANNOTATIONS_KEY = Symbol ( 'annotations' ) ;
9+
610/**
711 * A Bru file is made up of blocks.
812 * There are three types of blocks
@@ -55,10 +59,27 @@ const grammar = ohm.grammar(`Bru {
5559 multilinetextblock = multilinetextblockdelimiter (~multilinetextblockdelimiter any)* multilinetextblockdelimiter st* contenttypeannotation?
5660 contenttypeannotation = "@contentType(" (~")" any)* ")"
5761
62+ // Annotation support (decorators on pairs)
63+ annotationname = annotationchar+
64+ annotationchar = ~("(" | ")" | " " | "\\t" | "\\r" | "\\n" | ":") any
65+ annotationsinglequotedargchar = ~"'" any
66+ annotationsinglequotedarg = "'" annotationsinglequotedargchar* "'"
67+ annotationdoublequotedargchar = ~"\\"" any
68+ annotationdoublequotedarg = "\\"" annotationdoublequotedargchar* "\\""
69+ annotationunquotedargchar = ~")" any
70+ annotationunquotedarg = annotationunquotedargchar*
71+ annotationargvalue = annotationsinglequotedarg | annotationdoublequotedarg | annotationunquotedarg
72+ annotationmultilinetextblock = multilinetextblockdelimiter (~multilinetextblockdelimiter any)* multilinetextblockdelimiter
73+ annotationargscontents = annotationmultilinetextblock | annotationargvalue
74+ annotationargs = "(" annotationargscontents ")"
75+ annotation = "@" annotationname annotationargs?
76+ annotationentry = st* annotation ~":" st* nl
77+ pairannotations = annotationentry*
78+
5879 // Dictionary Blocks
59- dictionary = st* "{" pairlist? tagend
80+ dictionary = st* "{" st* pairlist? tagend
6081 pairlist = optionalnl* pair (~tagend stnl* pair)* (~tagend space)*
61- pair = st* (quoted_key | key) st* ":" st* value st*
82+ pair = st* pairannotations st* (quoted_key | key) st* ":" st* value st*
6283 disable_char = "~"
6384 quote_char = "\\""
6485 esc_char = "\\\\"
@@ -72,7 +93,7 @@ const grammar = ohm.grammar(`Bru {
7293 // Dictionary for Assert Block
7394 assertdictionary = st* "{" assertpairlist? tagend
7495 assertpairlist = optionalnl* assertpair (~tagend stnl* assertpair)* (~tagend space)*
75- assertpair = st* assertkey st* ":" st* value st*
96+ assertpair = st* pairannotations st* assertkey st* ":" st* value st*
7697 assertkey = ~tagend assertkeychar*
7798 assertkeychar = ~(tagend | nl | ":") any
7899
@@ -168,12 +189,12 @@ const mapPairListToKeyValPairs = (pairList = [], parseEnabled = true) => {
168189 return _ . map ( pairList [ 0 ] , ( pair ) => {
169190 let name = _ . keys ( pair ) [ 0 ] ;
170191 let value = pair [ name ] ;
192+ const rawAnnotations = pair [ ANNOTATIONS_KEY ] ;
171193
172194 if ( ! parseEnabled ) {
173- return {
174- name,
175- value
176- } ;
195+ const result = { name, value } ;
196+ if ( rawAnnotations && rawAnnotations . length ) result . annotations = rawAnnotations ;
197+ return result ;
177198 }
178199
179200 let enabled = true ;
@@ -182,11 +203,9 @@ const mapPairListToKeyValPairs = (pairList = [], parseEnabled = true) => {
182203 enabled = false ;
183204 }
184205
185- return {
186- name,
187- value,
188- enabled
189- } ;
206+ const result = { name, value, enabled } ;
207+ if ( rawAnnotations && rawAnnotations . length ) result . annotations = rawAnnotations ;
208+ return result ;
190209 } ) ;
191210} ;
192211
@@ -197,18 +216,16 @@ const mapRequestParams = (pairList = [], type) => {
197216 return _ . map ( pairList [ 0 ] , ( pair ) => {
198217 let name = _ . keys ( pair ) [ 0 ] ;
199218 let value = pair [ name ] ;
219+ const rawAnnotations = pair [ ANNOTATIONS_KEY ] ;
200220 let enabled = true ;
201221 if ( name && name . length && name . charAt ( 0 ) === '~' ) {
202222 name = name . slice ( 1 ) ;
203223 enabled = false ;
204224 }
205225
206- return {
207- name,
208- value,
209- enabled,
210- type
211- } ;
226+ const result = { name, value, enabled, type } ;
227+ if ( rawAnnotations && rawAnnotations . length ) result . annotations = rawAnnotations ;
228+ return result ;
212229 } ) ;
213230} ;
214231
@@ -346,19 +363,67 @@ const sem = grammar.createSemantics().addAttribute('ast', {
346363 { }
347364 ) ;
348365 } ,
349- dictionary ( _1 , _2 , pairlist , _3 ) {
366+ dictionary ( _1 , _2 , _3 , pairlist , _4 ) {
350367 return pairlist . ast ;
351368 } ,
352369 pairlist ( _1 , pair , _2 , rest , _3 ) {
353370 return [ pair . ast , ...rest . ast ] ;
354371 } ,
355- pair ( _1 , key , _2 , _3 , _4 , value , _5 ) {
372+ pairannotations ( entries ) {
373+ return entries . ast ;
374+ } ,
375+ annotationentry ( _1 , annotation , _2 , _3 ) {
376+ return annotation . ast ;
377+ } ,
378+ annotation ( _at , name , argsIter ) {
379+ const annotObj = { name : name . ast } ;
380+ const argsArr = argsIter . ast ;
381+ if ( argsArr . length > 0 ) {
382+ annotObj . value = argsArr [ 0 ] ;
383+ }
384+ return annotObj ;
385+ } ,
386+ annotationname ( chars ) {
387+ return chars . sourceString ;
388+ } ,
389+ annotationsinglequotedarg ( _open , chars , _close ) {
390+ return chars . sourceString ;
391+ } ,
392+ annotationdoublequotedarg ( _open , chars , _close ) {
393+ return chars . sourceString ;
394+ } ,
395+ annotationunquotedarg ( chars ) {
396+ return chars . sourceString ;
397+ } ,
398+ annotationargvalue ( alt ) {
399+ return alt . ast ;
400+ } ,
401+ annotationmultilinetextblock ( _1 , content , _2 ) {
402+ const lines = content . sourceString . split ( '\n' ) ;
403+ // NOTE: the number 4 is taken from the `multilinetextblock` implementation
404+ let minIndent = 4 ;
405+ const dedented = lines . map ( ( line ) => ( line . trim ( ) === '' ? '' : line . substring ( minIndent ) ) ) ;
406+ if ( dedented . length > 0 && dedented [ 0 ] === '' ) dedented . shift ( ) ;
407+ if ( dedented . length > 0 && dedented [ dedented . length - 1 ] === '' ) dedented . pop ( ) ;
408+ return dedented . join ( '\n' ) ;
409+ } ,
410+ annotationargscontents ( alt ) {
411+ return alt . ast ;
412+ } ,
413+ annotationargs ( _open , value , _close ) {
414+ return value . ast ;
415+ } ,
416+ pair ( _1 , annotations , _keyindent , key , _2 , _3 , _4 , value , _5 ) {
356417 let res = { } ;
357418 if ( Array . isArray ( value . ast ) ) {
358419 res [ key . ast ] = value . ast ;
359- return res ;
420+ } else {
421+ res [ key . ast ] = value . ast ? value . ast . trim ( ) : '' ;
422+ }
423+ const annotationList = annotations . ast ;
424+ if ( annotationList && annotationList . length > 0 ) {
425+ res [ ANNOTATIONS_KEY ] = annotationList ;
360426 }
361- res [ key . ast ] = value . ast ? value . ast . trim ( ) : '' ;
362427 return res ;
363428 } ,
364429 esc_quote_char ( _1 , quote ) {
@@ -378,9 +443,13 @@ const sem = grammar.createSemantics().addAttribute('ast', {
378443 assertpairlist ( _1 , pair , _2 , rest , _3 ) {
379444 return [ pair . ast , ...rest . ast ] ;
380445 } ,
381- assertpair ( _1 , key , _2 , _3 , _4 , value , _5 ) {
446+ assertpair ( _1 , annotations , _2 , key , _3 , _4 , _5 , value , _6 ) {
382447 let res = { } ;
383448 res [ key . ast ] = value . ast ? value . ast . trim ( ) : '' ;
449+ const annotationList = annotations . ast ;
450+ if ( annotationList && annotationList . length > 0 ) {
451+ res [ ANNOTATIONS_KEY ] = annotationList ;
452+ }
384453 return res ;
385454 } ,
386455 assertkey ( chars ) {
0 commit comments