Skip to content

Commit eae075b

Browse files
authored
Merge pull request #182 from facchinm/problematic_multiline
Solve multiline functions generation
2 parents 4a09bad + bcd2800 commit eae075b

File tree

7 files changed

+425
-54
lines changed

7 files changed

+425
-54
lines changed

Diff for: src/arduino.cc/builder/ctags/ctags_has_issues.go

+310
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
/*
2+
* This file is part of Arduino Builder.
3+
*
4+
* Arduino Builder is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation; either version 2 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program; if not, write to the Free Software
16+
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17+
*
18+
* As a special exception, you may use this file as part of a free software
19+
* library without restriction. Specifically, if other files instantiate
20+
* templates or use macros or inline functions from this file, or you compile
21+
* this file and link it with other files to produce an executable, this
22+
* file does not by itself cause the resulting executable to be covered by
23+
* the GNU General Public License. This exception does not however
24+
* invalidate any other reasons why the executable file might be covered by
25+
* the GNU General Public License.
26+
*
27+
* Copyright 2015 Arduino LLC (http://www.arduino.cc/)
28+
*/
29+
30+
package ctags
31+
32+
import (
33+
"bufio"
34+
"os"
35+
"strings"
36+
37+
"arduino.cc/builder/types"
38+
)
39+
40+
func (p *CTagsParser) FixCLinkageTagsDeclarations(tags []*types.CTag) {
41+
42+
linesMap := p.FindCLinkageLines(tags)
43+
for i, _ := range tags {
44+
45+
if sliceContainsInt(linesMap[tags[i].Filename], tags[i].Line) &&
46+
!strings.Contains(tags[i].PrototypeModifiers, EXTERN) {
47+
tags[i].PrototypeModifiers = tags[i].PrototypeModifiers + " " + EXTERN
48+
}
49+
}
50+
}
51+
52+
func sliceContainsInt(s []int, e int) bool {
53+
for _, a := range s {
54+
if a == e {
55+
return true
56+
}
57+
}
58+
return false
59+
}
60+
61+
func (p *CTagsParser) prototypeAndCodeDontMatch(tag *types.CTag) bool {
62+
if tag.SkipMe {
63+
return true
64+
}
65+
66+
code := removeSpacesAndTabs(tag.Code)
67+
68+
if strings.Index(code, ")") == -1 {
69+
// Add to code non-whitespace non-comments tokens until we find a closing round bracket
70+
file, err := os.Open(tag.Filename)
71+
if err == nil {
72+
defer file.Close()
73+
74+
scanner := bufio.NewScanner(file)
75+
line := 1
76+
77+
// skip lines until we get to the start of this tag
78+
for scanner.Scan() && line < tag.Line {
79+
line++
80+
}
81+
82+
// read up to 10 lines in search of a closing paren
83+
multilinecomment := false
84+
temp := ""
85+
86+
code, multilinecomment = removeComments(scanner.Text(), multilinecomment)
87+
for scanner.Scan() && line < (tag.Line+10) && strings.Index(temp, ")") == -1 {
88+
temp, multilinecomment = removeComments(scanner.Text(), multilinecomment)
89+
code += temp
90+
}
91+
}
92+
}
93+
94+
code = removeSpacesAndTabs(code)
95+
96+
prototype := removeSpacesAndTabs(tag.Prototype)
97+
prototype = removeTralingSemicolon(prototype)
98+
99+
// Prototype matches exactly with the code?
100+
ret := strings.Index(code, prototype)
101+
102+
if ret == -1 {
103+
// If the definition is multiline ctags uses the function name as line number
104+
// Try to match functions in the form
105+
// void
106+
// foo() {}
107+
108+
// Add to code n non-whitespace non-comments tokens before the code line
109+
110+
code = removeEverythingAfterClosingRoundBracket(code)
111+
// Get how many characters are "missing"
112+
n := strings.Index(prototype, code)
113+
line := 0
114+
// Add these characters to "code" string
115+
code, line = getFunctionProtoWithNPreviousCharacters(tag, code, n)
116+
// Check again for perfect matching
117+
ret = strings.Index(code, prototype)
118+
if ret != -1 {
119+
tag.Line = line
120+
}
121+
}
122+
123+
return ret == -1
124+
}
125+
126+
func findTemplateMultiline(tag *types.CTag) string {
127+
code, _ := getFunctionProtoUntilTemplateToken(tag, tag.Code)
128+
return removeEverythingAfterClosingRoundBracket(code)
129+
}
130+
131+
func removeEverythingAfterClosingRoundBracket(s string) string {
132+
n := strings.Index(s, ")")
133+
return s[0 : n+1]
134+
}
135+
136+
func getFunctionProtoUntilTemplateToken(tag *types.CTag, code string) (string, int) {
137+
138+
/* FIXME I'm ugly */
139+
line := 0
140+
141+
file, err := os.Open(tag.Filename)
142+
if err == nil {
143+
defer file.Close()
144+
145+
scanner := bufio.NewScanner(file)
146+
multilinecomment := false
147+
var textBuffer []string
148+
149+
// buffer lines until we get to the start of this tag
150+
for scanner.Scan() && line < (tag.Line-1) {
151+
line++
152+
text := scanner.Text()
153+
textBuffer = append(textBuffer, text)
154+
}
155+
156+
for line > 0 && !strings.Contains(code, TEMPLATE) {
157+
158+
line = line - 1
159+
text := textBuffer[line]
160+
161+
text, multilinecomment = removeComments(text, multilinecomment)
162+
163+
code = text + code
164+
}
165+
}
166+
return code, line
167+
}
168+
169+
func getFunctionProtoWithNPreviousCharacters(tag *types.CTag, code string, n int) (string, int) {
170+
171+
/* FIXME I'm ugly */
172+
expectedPrototypeLen := len(code) + n
173+
line := 0
174+
175+
file, err := os.Open(tag.Filename)
176+
if err == nil {
177+
defer file.Close()
178+
179+
scanner := bufio.NewScanner(file)
180+
multilinecomment := false
181+
var textBuffer []string
182+
183+
// buffer lines until we get to the start of this tag
184+
for scanner.Scan() && line < (tag.Line-1) {
185+
line++
186+
text := scanner.Text()
187+
textBuffer = append(textBuffer, text)
188+
}
189+
190+
for line > 0 && len(code) < expectedPrototypeLen {
191+
192+
line = line - 1
193+
text := textBuffer[line]
194+
195+
text, multilinecomment = removeComments(text, multilinecomment)
196+
197+
code = text + code
198+
code = removeSpacesAndTabs(code)
199+
}
200+
}
201+
return code, line
202+
}
203+
204+
func removeComments(text string, multilinecomment bool) (string, bool) {
205+
// Remove C++ style comments
206+
if strings.Index(text, "//") != -1 {
207+
text = text[0:strings.Index(text, "//")]
208+
}
209+
210+
// Remove C style comments
211+
if strings.Index(text, "*/") != -1 {
212+
if strings.Index(text, "/*") != -1 {
213+
// C style comments on the same line
214+
text = text[0:strings.Index(text, "/*")] + text[strings.Index(text, "*/")+1:len(text)-1]
215+
} else {
216+
text = text[strings.Index(text, "*/")+1 : len(text)-1]
217+
multilinecomment = true
218+
}
219+
}
220+
221+
if multilinecomment {
222+
if strings.Index(text, "/*") != -1 {
223+
text = text[0:strings.Index(text, "/*")]
224+
multilinecomment = false
225+
} else {
226+
text = ""
227+
}
228+
}
229+
return text, multilinecomment
230+
}
231+
232+
/* This function scans the source files searching for "extern C" context
233+
* It save the line numbers in a map filename -> {lines...}
234+
*/
235+
func (p *CTagsParser) FindCLinkageLines(tags []*types.CTag) map[string][]int {
236+
237+
lines := make(map[string][]int)
238+
239+
for _, tag := range tags {
240+
241+
if lines[tag.Filename] != nil {
242+
break
243+
}
244+
245+
file, err := os.Open(tag.Filename)
246+
if err == nil {
247+
defer file.Close()
248+
249+
lines[tag.Filename] = append(lines[tag.Filename], -1)
250+
251+
scanner := bufio.NewScanner(file)
252+
253+
// we can't remove the comments otherwise the line number will be wrong
254+
// there are three cases:
255+
// 1 - extern "C" void foo()
256+
// 2 - extern "C" {
257+
// void foo();
258+
// void bar();
259+
// }
260+
// 3 - extern "C"
261+
// {
262+
// void foo();
263+
// void bar();
264+
// }
265+
// case 1 and 2 can be simply recognized with string matching and indent level count
266+
// case 3 needs specia attention: if the line ONLY contains `extern "C"` string, don't bail out on indent level = 0
267+
268+
inScope := false
269+
enteringScope := false
270+
indentLevels := 0
271+
line := 0
272+
273+
externCDecl := removeSpacesAndTabs(EXTERN)
274+
275+
for scanner.Scan() {
276+
line++
277+
str := removeSpacesAndTabs(scanner.Text())
278+
279+
if len(str) == 0 {
280+
continue
281+
}
282+
283+
// check if we are on the first non empty line after externCDecl in case 3
284+
if enteringScope == true {
285+
enteringScope = false
286+
}
287+
288+
// check if the line contains externCDecl
289+
if strings.Contains(str, externCDecl) {
290+
inScope = true
291+
if len(str) == len(externCDecl) {
292+
// case 3
293+
enteringScope = true
294+
}
295+
}
296+
if inScope == true {
297+
lines[tag.Filename] = append(lines[tag.Filename], line)
298+
}
299+
indentLevels += strings.Count(str, "{") - strings.Count(str, "}")
300+
301+
// Bail out if indentLevel is zero and we are not in case 3
302+
if indentLevels == 0 && enteringScope == false {
303+
inScope = false
304+
}
305+
}
306+
}
307+
308+
}
309+
return lines
310+
}

0 commit comments

Comments
 (0)