Skip to content

Commit 4e2a1d6

Browse files
blackfischBoGuu
authored andcommitted
put SQF validators local
1 parent 11c56d0 commit 4e2a1d6

3 files changed

Lines changed: 354 additions & 6 deletions

File tree

.travis.yml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
language: python
22
python:
33
- '3.4'
4-
before_script:
5-
# Credits to ACE3 for these validators
6-
- wget https://www.dropbox.com/s/wzj5js0kc2xlanc/sqf_validator.py
7-
- wget https://www.dropbox.com/s/jza43yuxswvx2hz/config_style_checker.py
84
script:
9-
- python3 sqf_validator.py
10-
- python3 config_style_checker.py
5+
# Credits to ACE3 for these validators
6+
- python3 tools/sqf_validator.py
7+
- python3 tools/config_style_checker.py

tools/config_style_checker.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
#!/usr/bin/env python3
2+
3+
#credits to ACE3: https://github.com/acemod/ACE3/blob/master/tools/sqf_validator.py
4+
5+
import fnmatch
6+
import os
7+
import re
8+
import ntpath
9+
import sys
10+
import argparse
11+
12+
def check_config_style(filepath):
13+
bad_count_file = 0
14+
def pushClosing(t):
15+
closingStack.append(closing.expr)
16+
closing << Literal( closingFor[t[0]] )
17+
18+
def popClosing():
19+
closing << closingStack.pop()
20+
21+
with open(filepath, 'r', encoding='utf-8', errors='ignore') as file:
22+
content = file.read()
23+
24+
# Store all brackets we find in this file, so we can validate everything on the end
25+
brackets_list = []
26+
27+
# To check if we are in a comment block
28+
isInCommentBlock = False
29+
checkIfInComment = False
30+
# Used in case we are in a line comment (//)
31+
ignoreTillEndOfLine = False
32+
# Used in case we are in a comment block (/* */). This is true if we detect a * inside a comment block.
33+
# If the next character is a /, it means we end our comment block.
34+
checkIfNextIsClosingBlock = False
35+
36+
# We ignore everything inside a string
37+
isInString = False
38+
# Used to store the starting type of a string, so we can match that to the end of a string
39+
inStringType = '';
40+
41+
lastIsCurlyBrace = False
42+
checkForSemiColumn = False
43+
44+
# Extra information so we know what line we find errors at
45+
lineNumber = 1
46+
47+
indexOfCharacter = 0
48+
# Parse all characters in the content of this file to search for potential errors
49+
for c in content:
50+
if (lastIsCurlyBrace):
51+
lastIsCurlyBrace = False
52+
if c == '\n': # Keeping track of our line numbers
53+
lineNumber += 1 # so we can print accurate line number information when we detect a possible error
54+
if (isInString): # while we are in a string, we can ignore everything else, except the end of the string
55+
if (c == inStringType):
56+
isInString = False
57+
# if we are not in a comment block, we will check if we are at the start of one or count the () {} and []
58+
elif (isInCommentBlock == False):
59+
60+
# This means we have encountered a /, so we are now checking if this is an inline comment or a comment block
61+
if (checkIfInComment):
62+
checkIfInComment = False
63+
if c == '*': # if the next character after / is a *, we are at the start of a comment block
64+
isInCommentBlock = True
65+
elif (c == '/'): # Otherwise, will check if we are in an line comment
66+
ignoreTillEndOfLine = True # and an line comment is a / followed by another / (//) We won't care about anything that comes after it
67+
68+
if (isInCommentBlock == False):
69+
if (ignoreTillEndOfLine): # we are in a line comment, just continue going through the characters until we find an end of line
70+
if (c == '\n'):
71+
ignoreTillEndOfLine = False
72+
else: # validate brackets
73+
if (c == '"' or c == "'"):
74+
isInString = True
75+
inStringType = c
76+
elif (c == '/'):
77+
checkIfInComment = True
78+
elif (c == '('):
79+
brackets_list.append('(')
80+
elif (c == ')'):
81+
if (len(brackets_list) > 0 and brackets_list[-1] in ['{', '[']):
82+
print("ERROR: Possible missing round bracket ')' detected at {0} Line number: {1}".format(filepath,lineNumber))
83+
bad_count_file += 1
84+
brackets_list.append(')')
85+
elif (c == '['):
86+
brackets_list.append('[')
87+
elif (c == ']'):
88+
if (len(brackets_list) > 0 and brackets_list[-1] in ['{', '(']):
89+
print("ERROR: Possible missing square bracket ']' detected at {0} Line number: {1}".format(filepath,lineNumber))
90+
bad_count_file += 1
91+
brackets_list.append(']')
92+
elif (c == '{'):
93+
brackets_list.append('{')
94+
elif (c == '}'):
95+
lastIsCurlyBrace = True
96+
if (len(brackets_list) > 0 and brackets_list[-1] in ['(', '[']):
97+
print("ERROR: Possible missing curly brace '}}' detected at {0} Line number: {1}".format(filepath,lineNumber))
98+
bad_count_file += 1
99+
brackets_list.append('}')
100+
elif (c== '\t'):
101+
print("ERROR: Tab detected at {0} Line number: {1}".format(filepath,lineNumber))
102+
bad_count_file += 1
103+
104+
else: # Look for the end of our comment block
105+
if (c == '*'):
106+
checkIfNextIsClosingBlock = True;
107+
elif (checkIfNextIsClosingBlock):
108+
if (c == '/'):
109+
isInCommentBlock = False
110+
elif (c != '*'):
111+
checkIfNextIsClosingBlock = False
112+
indexOfCharacter += 1
113+
114+
if brackets_list.count('[') != brackets_list.count(']'):
115+
print("ERROR: A possible missing square bracket [ or ] in file {0} [ = {1} ] = {2}".format(filepath,brackets_list.count('['),brackets_list.count(']')))
116+
bad_count_file += 1
117+
if brackets_list.count('(') != brackets_list.count(')'):
118+
print("ERROR: A possible missing round bracket ( or ) in file {0} ( = {1} ) = {2}".format(filepath,brackets_list.count('('),brackets_list.count(')')))
119+
bad_count_file += 1
120+
if brackets_list.count('{') != brackets_list.count('}'):
121+
print("ERROR: A possible missing curly brace {{ or }} in file {0} {{ = {1} }} = {2}".format(filepath,brackets_list.count('{'),brackets_list.count('}')))
122+
bad_count_file += 1
123+
return bad_count_file
124+
125+
def main():
126+
127+
print("Validating Config Style")
128+
129+
sqf_list = []
130+
bad_count = 0
131+
132+
parser = argparse.ArgumentParser()
133+
parser.add_argument('-m','--module', help='only search specified module addon folder', required=False, default="")
134+
args = parser.parse_args()
135+
136+
# Allow running from root directory as well as from inside the tools directory
137+
rootDir = "../addons"
138+
if (os.path.exists("addons")):
139+
rootDir = "addons"
140+
141+
for root, dirnames, filenames in os.walk(rootDir + '/' + args.module):
142+
for filename in fnmatch.filter(filenames, '*.cpp'):
143+
sqf_list.append(os.path.join(root, filename))
144+
for filename in fnmatch.filter(filenames, '*.hpp'):
145+
sqf_list.append(os.path.join(root, filename))
146+
147+
for filename in sqf_list:
148+
bad_count = bad_count + check_config_style(filename)
149+
150+
print("------\nChecked {0} files\nErrors detected: {1}".format(len(sqf_list), bad_count))
151+
if (bad_count == 0):
152+
print("Config validation PASSED")
153+
else:
154+
print("Config validation FAILED")
155+
156+
return bad_count
157+
158+
if __name__ == "__main__":
159+
sys.exit(main())

tools/sqf_validator.py

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
#!/usr/bin/env python3
2+
3+
#credits to ACE3: https://github.com/acemod/ACE3/blob/master/tools/sqf_validator.py
4+
5+
import fnmatch
6+
import os
7+
import re
8+
import ntpath
9+
import sys
10+
import argparse
11+
12+
def validKeyWordAfterCode(content, index):
13+
keyWords = ["for", "do", "count", "each", "forEach", "else", "and", "not", "isEqualTo", "in", "call", "spawn", "execVM", "catch", "param", "select", "apply", "findIf", "remoteExec"];
14+
for word in keyWords:
15+
try:
16+
subWord = content.index(word, index, index+len(word))
17+
return True;
18+
except:
19+
pass
20+
return False
21+
22+
def check_sqf_syntax(filepath):
23+
bad_count_file = 0
24+
def pushClosing(t):
25+
closingStack.append(closing.expr)
26+
closing << Literal( closingFor[t[0]] )
27+
28+
def popClosing():
29+
closing << closingStack.pop()
30+
31+
with open(filepath, 'r', encoding='utf-8', errors='ignore') as file:
32+
content = file.read()
33+
34+
# Store all brackets we find in this file, so we can validate everything on the end
35+
brackets_list = []
36+
37+
# To check if we are in a comment block
38+
isInCommentBlock = False
39+
checkIfInComment = False
40+
# Used in case we are in a line comment (//)
41+
ignoreTillEndOfLine = False
42+
# Used in case we are in a comment block (/* */). This is true if we detect a * inside a comment block.
43+
# If the next character is a /, it means we end our comment block.
44+
checkIfNextIsClosingBlock = False
45+
46+
# We ignore everything inside a string
47+
isInString = False
48+
# Used to store the starting type of a string, so we can match that to the end of a string
49+
inStringType = '';
50+
51+
lastIsCurlyBrace = False
52+
checkForSemicolon = False
53+
onlyWhitespace = True
54+
55+
# Extra information so we know what line we find errors at
56+
lineNumber = 1
57+
58+
indexOfCharacter = 0
59+
# Parse all characters in the content of this file to search for potential errors
60+
for c in content:
61+
if (lastIsCurlyBrace):
62+
lastIsCurlyBrace = False
63+
# Test generates false positives with binary commands that take CODE as 2nd arg (e.g. findIf)
64+
checkForSemicolon = not re.search('findIf', content, re.IGNORECASE)
65+
66+
if c == '\n': # Keeping track of our line numbers
67+
onlyWhitespace = True # reset so we can see if # is for a preprocessor command
68+
lineNumber += 1 # so we can print accurate line number information when we detect a possible error
69+
if (isInString): # while we are in a string, we can ignore everything else, except the end of the string
70+
if (c == inStringType):
71+
isInString = False
72+
# if we are not in a comment block, we will check if we are at the start of one or count the () {} and []
73+
elif (isInCommentBlock == False):
74+
75+
# This means we have encountered a /, so we are now checking if this is an inline comment or a comment block
76+
if (checkIfInComment):
77+
checkIfInComment = False
78+
if c == '*': # if the next character after / is a *, we are at the start of a comment block
79+
isInCommentBlock = True
80+
elif (c == '/'): # Otherwise, will check if we are in an line comment
81+
ignoreTillEndOfLine = True # and an line comment is a / followed by another / (//) We won't care about anything that comes after it
82+
83+
if (isInCommentBlock == False):
84+
if (ignoreTillEndOfLine): # we are in a line comment, just continue going through the characters until we find an end of line
85+
if (c == '\n'):
86+
ignoreTillEndOfLine = False
87+
else: # validate brackets
88+
if (c == '"' or c == "'"):
89+
isInString = True
90+
inStringType = c
91+
elif (c == '#' and onlyWhitespace):
92+
ignoreTillEndOfLine = True
93+
elif (c == '/'):
94+
checkIfInComment = True
95+
elif (c == '('):
96+
brackets_list.append('(')
97+
elif (c == ')'):
98+
if (brackets_list[-1] in ['{', '[']):
99+
print("ERROR: Possible missing round bracket ')' detected at {0} Line number: {1}".format(filepath,lineNumber))
100+
bad_count_file += 1
101+
brackets_list.append(')')
102+
elif (c == '['):
103+
brackets_list.append('[')
104+
elif (c == ']'):
105+
if (brackets_list[-1] in ['{', '(']):
106+
print("ERROR: Possible missing square bracket ']' detected at {0} Line number: {1}".format(filepath,lineNumber))
107+
bad_count_file += 1
108+
brackets_list.append(']')
109+
elif (c == '{'):
110+
brackets_list.append('{')
111+
elif (c == '}'):
112+
lastIsCurlyBrace = True
113+
if (brackets_list[-1] in ['(', '[']):
114+
print("ERROR: Possible missing curly brace '}}' detected at {0} Line number: {1}".format(filepath,lineNumber))
115+
bad_count_file += 1
116+
brackets_list.append('}')
117+
elif (c== '\t'):
118+
print("ERROR: Tab detected at {0} Line number: {1}".format(filepath,lineNumber))
119+
bad_count_file += 1
120+
121+
if (c not in [' ', '\t', '\n']):
122+
onlyWhitespace = False
123+
124+
if (checkForSemicolon):
125+
if (c not in [' ', '\t', '\n', '/']): # keep reading until no white space or comments
126+
checkForSemicolon = False
127+
if (c not in [']', ')', '}', ';', ',', '&', '!', '|', '='] and not validKeyWordAfterCode(content, indexOfCharacter)): # , 'f', 'd', 'c', 'e', 'a', 'n', 'i']):
128+
print("ERROR: Possible missing semicolon ';' detected at {0} Line number: {1}".format(filepath,lineNumber))
129+
bad_count_file += 1
130+
131+
else: # Look for the end of our comment block
132+
if (c == '*'):
133+
checkIfNextIsClosingBlock = True;
134+
elif (checkIfNextIsClosingBlock):
135+
if (c == '/'):
136+
isInCommentBlock = False
137+
elif (c != '*'):
138+
checkIfNextIsClosingBlock = False
139+
indexOfCharacter += 1
140+
141+
if brackets_list.count('[') != brackets_list.count(']'):
142+
print("ERROR: A possible missing square bracket [ or ] in file {0} [ = {1} ] = {2}".format(filepath,brackets_list.count('['),brackets_list.count(']')))
143+
bad_count_file += 1
144+
if brackets_list.count('(') != brackets_list.count(')'):
145+
print("ERROR: A possible missing round bracket ( or ) in file {0} ( = {1} ) = {2}".format(filepath,brackets_list.count('('),brackets_list.count(')')))
146+
bad_count_file += 1
147+
if brackets_list.count('{') != brackets_list.count('}'):
148+
print("ERROR: A possible missing curly brace {{ or }} in file {0} {{ = {1} }} = {2}".format(filepath,brackets_list.count('{'),brackets_list.count('}')))
149+
bad_count_file += 1
150+
pattern = re.compile('\s*(/\*[\s\S]+?\*/)\s*#include')
151+
if pattern.match(content):
152+
print("ERROR: A found #include after block comment in file {0}".format(filepath))
153+
bad_count_file += 1
154+
155+
156+
157+
return bad_count_file
158+
159+
def main():
160+
161+
print("Validating SQF")
162+
163+
sqf_list = []
164+
bad_count = 0
165+
166+
parser = argparse.ArgumentParser()
167+
parser.add_argument('-m','--module', help='only search specified module addon folder', required=False, default="")
168+
args = parser.parse_args()
169+
170+
# Allow running from root directory as well as from inside the tools directory
171+
rootDir = "../addons"
172+
if (os.path.exists("addons")):
173+
rootDir = "addons"
174+
175+
for root, dirnames, filenames in os.walk(rootDir + '/' + args.module):
176+
for filename in fnmatch.filter(filenames, '*.sqf'):
177+
sqf_list.append(os.path.join(root, filename))
178+
179+
for filename in sqf_list:
180+
bad_count = bad_count + check_sqf_syntax(filename)
181+
182+
183+
print("------\nChecked {0} files\nErrors detected: {1}".format(len(sqf_list), bad_count))
184+
if (bad_count == 0):
185+
print("SQF validation PASSED")
186+
else:
187+
print("SQF validation FAILED")
188+
189+
return bad_count
190+
191+
if __name__ == "__main__":
192+
sys.exit(main())

0 commit comments

Comments
 (0)