1 /*
2 *
3 * Copyright (c) 2017, Red Hat, Inc.
4 * Copyright (c) 2017, Masatake YAMATO
5 *
6 * Author: Masatake YAMATO <yamato@redhat.com>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
21 * USA.
22 *
23 * Inspired by the following commit but written from scratch:
24 * ==========================================================
25 *
26 * commit 76dbed4de88875d8c8409dfd65da4f94f901c94a
27 * Author: Ram Singla <ram.singla@gmail.com>
28 * Date: Tue Jan 18 13:24:46 2011 +0800
29 *
30 * RSpec Code added. Courtesy: mortice
31 *
32 * ==========================================================
33 *
34 * Reference:
35 * - https://rubydoc.info/gems/rspec-core/frames
36 */
37
38 /*
39 * INCLUDE FILES
40 */
41 #include "general.h" /* must always come first */
42
43 #include "entry.h"
44 #include "kind.h"
45 #include "numarray.h"
46 #include "parse.h"
47 #include "subparser.h"
48
49 #include "ruby.h"
50
51 #include <string.h>
52
53 /*
54 * DATA STRUCTURES
55 */
56 struct sRSpecSubparser {
57 rubySubparser ruby;
58 int scope;
59 };
60
61 typedef enum {
62 K_DESCRIBE,
63 K_CONTEXT,
64 K_IT,
65 } rspecKind;
66
67 static kindDefinition RSpecKinds [] = {
68 { true, 'd', "describe", "describes" },
69 { true, 'c', "context", "contexts" },
70 { true, 'i', "it", "things described with \"it\"" },
71 };
72
73 /*
74 * FUNCTIONS
75 */
makeSimpleRSpecTag(vString * vstr,int kindIndex,rubySubparser * subparser)76 static int makeSimpleRSpecTag (vString *vstr, int kindIndex, rubySubparser *subparser)
77 {
78 int r = makeSimpleTag (vstr, kindIndex);
79 tagEntryInfo *e = getEntryInCorkQueue (r);
80 if (e)
81 {
82 struct sRSpecSubparser *rspec = (struct sRSpecSubparser *)subparser;
83 e->extensionFields.scopeIndex = rspec->scope;
84 }
85 return r;
86 }
87
readRest(const unsigned char ** cp)88 static vString *readRest (const unsigned char **cp)
89 {
90 vString *vstr = NULL;
91 unsigned char b;
92
93 switch (**cp)
94 {
95 case '\'':
96 case '"':
97 b = **cp;
98 ++*cp;
99 vstr = vStringNew ();
100 if (!rubyParseString (cp, b, vstr))
101 {
102 vStringDelete (vstr);
103 vstr = NULL;
104 }
105 break;
106 case ':':
107 /* symbol, Should this be part of the Ruby parser side? */
108 break;
109 default:
110 vstr = vStringNew ();
111 if (!rubyParseModuleName (cp, vstr))
112 {
113 vStringDelete (vstr);
114 vstr = NULL;
115 }
116 else if (strcmp (vStringValue (vstr), "do") == 0)
117 {
118 vStringDelete (vstr);
119 vstr = NULL;
120 *cp += -2; /* push back "do" */
121 }
122
123 break;
124 }
125
126 if (vstr)
127 {
128 rubySkipWhitespace (cp);
129 if (**cp == ',')
130 {
131 ++*cp;
132 rubySkipWhitespace (cp);
133 vString *vstr_rest = readRest (cp);
134 if (vstr_rest)
135 {
136 vStringPut (vstr, ' ');
137 vStringCat (vstr, vstr_rest);
138 vStringDelete (vstr_rest);
139 }
140 }
141 }
142
143 return vstr;
144 }
145
146 struct caseType {
147 const char *keyword;
148 rspecKind kind;
149 };
150
lineNotify(rubySubparser * s,const unsigned char ** cp)151 static int lineNotify (rubySubparser *s, const unsigned char **cp)
152 {
153 struct caseType caseTypes [] = {
154 { "describe", K_DESCRIBE },
155 { "RSpec.describe", K_DESCRIBE },
156 { "context", K_CONTEXT },
157 { "it", K_IT },
158 };
159
160 for (int i = 0; i < ARRAY_SIZE(caseTypes); i++)
161 {
162 if (rubyCanMatchKeywordWithAssign(cp, caseTypes[i].keyword))
163 {
164 vString *vstr = NULL;
165 rubySkipWhitespace (cp);
166 vstr = readRest (cp);
167 if (vstr)
168 {
169 int r = makeSimpleRSpecTag (vstr, caseTypes[i].kind, s);
170 vStringDelete (vstr);
171 return r;
172 }
173 }
174 }
175
176 return CORK_NIL;
177 }
178
enterBlockNotify(rubySubparser * s,int corkIndex)179 static void enterBlockNotify (rubySubparser *s, int corkIndex)
180 {
181 struct sRSpecSubparser *rspec = (struct sRSpecSubparser *)s;
182 rspec->scope = corkIndex;
183 }
184
leaveBlockNotify(rubySubparser * s,int corkIndex)185 static void leaveBlockNotify (rubySubparser *s, int corkIndex)
186 {
187 tagEntryInfo *e = getEntryInCorkQueue (corkIndex);
188 if (e)
189 {
190 struct sRSpecSubparser *rspec = (struct sRSpecSubparser *)s;
191 rspec->scope = e->extensionFields.scopeIndex;
192 }
193 }
194
findRSpecTags(void)195 static void findRSpecTags (void)
196 {
197 scheduleRunningBaseparser (RUN_DEFAULT_SUBPARSERS);
198 }
199
inputStart(subparser * s)200 static void inputStart (subparser *s)
201 {
202 struct sRSpecSubparser *rspec = (struct sRSpecSubparser *)s;
203 rspec->scope = CORK_NIL;
204 }
205
RSpecParser(void)206 extern parserDefinition* RSpecParser (void)
207 {
208 static struct sRSpecSubparser rspecSubparser = {
209 .ruby = {
210 .subparser = {
211 .direction = SUBPARSER_BASE_RUNS_SUB,
212 .inputStart = inputStart,
213 },
214 .lineNotify = lineNotify,
215 .enterBlockNotify = enterBlockNotify,
216 .leaveBlockNotify = leaveBlockNotify,
217 },
218 .scope = CORK_NIL,
219 };
220
221 static parserDependency dependencies [] = {
222 [0] = { DEPTYPE_SUBPARSER, "Ruby", &rspecSubparser },
223 };
224
225 parserDefinition* const def = parserNew ("RSpec");
226
227 def->dependencies = dependencies;
228 def->dependencyCount = ARRAY_SIZE(dependencies);
229 def->kindTable = RSpecKinds;
230 def->kindCount = ARRAY_SIZE (RSpecKinds);
231 def->parser = findRSpecTags;
232 def->useCork = CORK_QUEUE;
233
234 return def;
235 }
236