2015-10-11 21:20:56 +00:00
#!/usr/bin/env python3
2015-09-21 16:01:45 +00:00
import fnmatch
import os
import re
import ntpath
import sys
import argparse
2015-10-11 15:25:53 +00:00
def validKeyWordAfterCode ( content , index ) :
2018-09-20 19:34:59 +00:00
keyWords = [ " for " , " do " , " count " , " each " , " forEach " , " else " , " and " , " not " , " isEqualTo " , " in " , " call " , " spawn " , " execVM " , " catch " , " param " , " select " , " apply " , " findIf " , " remoteExec " ] ;
2015-10-11 15:25:53 +00:00
for word in keyWords :
try :
subWord = content . index ( word , index , index + len ( word ) )
return True ;
except :
pass
return False
2015-09-21 16:01:45 +00:00
def check_sqf_syntax ( filepath ) :
bad_count_file = 0
def pushClosing ( t ) :
closingStack . append ( closing . expr )
closing << Literal ( closingFor [ t [ 0 ] ] )
def popClosing ( ) :
closing << closingStack . pop ( )
2015-10-11 15:35:55 +00:00
with open ( filepath , ' r ' , encoding = ' utf-8 ' , errors = ' ignore ' ) as file :
2015-09-21 16:01:45 +00:00
content = file . read ( )
2015-10-11 15:25:53 +00:00
# Store all brackets we find in this file, so we can validate everything on the end
2015-09-21 16:01:45 +00:00
brackets_list = [ ]
2015-10-11 15:25:53 +00:00
# To check if we are in a comment block
2015-09-21 16:01:45 +00:00
isInCommentBlock = False
checkIfInComment = False
2015-10-11 15:25:53 +00:00
# Used in case we are in a line comment (//)
2015-09-21 16:01:45 +00:00
ignoreTillEndOfLine = False
2015-10-11 15:25:53 +00:00
# Used in case we are in a comment block (/* */). This is true if we detect a * inside a comment block.
# If the next character is a /, it means we end our comment block.
2015-09-21 16:01:45 +00:00
checkIfNextIsClosingBlock = False
2015-10-11 15:25:53 +00:00
# We ignore everything inside a string
2015-09-21 16:01:45 +00:00
isInString = False
2015-10-11 15:25:53 +00:00
# Used to store the starting type of a string, so we can match that to the end of a string
inStringType = ' ' ;
2015-09-21 16:01:45 +00:00
2015-10-11 15:25:53 +00:00
lastIsCurlyBrace = False
2018-09-20 19:34:59 +00:00
checkForSemicolon = False
2019-01-05 02:10:40 +00:00
onlyWhitespace = True
2015-10-11 15:25:53 +00:00
# Extra information so we know what line we find errors at
2016-05-03 13:15:55 +00:00
lineNumber = 1
2015-09-21 16:01:45 +00:00
2015-10-11 15:25:53 +00:00
indexOfCharacter = 0
# Parse all characters in the content of this file to search for potential errors
2015-09-21 16:01:45 +00:00
for c in content :
2015-10-11 15:25:53 +00:00
if ( lastIsCurlyBrace ) :
lastIsCurlyBrace = False
2018-09-20 19:34:59 +00:00
# Test generates false positives with binary commands that take CODE as 2nd arg (e.g. findIf)
checkForSemicolon = not re . search ( ' findIf ' , content , re . IGNORECASE )
2015-10-11 15:25:53 +00:00
if c == ' \n ' : # Keeping track of our line numbers
2019-01-05 02:10:40 +00:00
onlyWhitespace = True # reset so we can see if # is for a preprocessor command
2015-09-21 16:01:45 +00:00
lineNumber + = 1 # so we can print accurate line number information when we detect a possible error
2015-10-11 15:25:53 +00:00
if ( isInString ) : # while we are in a string, we can ignore everything else, except the end of the string
2015-09-21 16:01:45 +00:00
if ( c == inStringType ) :
isInString = False
2015-10-11 15:25:53 +00:00
# if we are not in a comment block, we will check if we are at the start of one or count the () {} and []
elif ( isInCommentBlock == False ) :
# This means we have encountered a /, so we are now checking if this is an inline comment or a comment block
if ( checkIfInComment ) :
2015-09-21 16:01:45 +00:00
checkIfInComment = False
if c == ' * ' : # if the next character after / is a *, we are at the start of a comment block
isInCommentBlock = True
2015-10-11 15:25:53 +00:00
elif ( c == ' / ' ) : # Otherwise, will check if we are in an line comment
2015-09-21 16:01:45 +00:00
ignoreTillEndOfLine = True # and an line comment is a / followed by another / (//) We won't care about anything that comes after it
2015-10-11 15:25:53 +00:00
2015-09-21 16:01:45 +00:00
if ( isInCommentBlock == False ) :
if ( ignoreTillEndOfLine ) : # we are in a line comment, just continue going through the characters until we find an end of line
if ( c == ' \n ' ) :
ignoreTillEndOfLine = False
2015-10-11 15:25:53 +00:00
else : # validate brackets
if ( c == ' " ' or c == " ' " ) :
2015-09-21 16:01:45 +00:00
isInString = True
inStringType = c
2019-01-05 02:10:40 +00:00
elif ( c == ' # ' and onlyWhitespace ) :
2018-11-18 16:10:28 +00:00
ignoreTillEndOfLine = True
2015-09-21 16:01:45 +00:00
elif ( c == ' / ' ) :
checkIfInComment = True
elif ( c == ' ( ' ) :
brackets_list . append ( ' ( ' )
elif ( c == ' ) ' ) :
if ( brackets_list [ - 1 ] in [ ' { ' , ' [ ' ] ) :
2015-10-11 15:25:53 +00:00
print ( " ERROR: Possible missing round bracket ' ) ' detected at {0} Line number: {1} " . format ( filepath , lineNumber ) )
2015-09-21 16:01:45 +00:00
bad_count_file + = 1
brackets_list . append ( ' ) ' )
elif ( c == ' [ ' ) :
brackets_list . append ( ' [ ' )
elif ( c == ' ] ' ) :
if ( brackets_list [ - 1 ] in [ ' { ' , ' ( ' ] ) :
2015-10-11 15:25:53 +00:00
print ( " ERROR: Possible missing square bracket ' ] ' detected at {0} Line number: {1} " . format ( filepath , lineNumber ) )
2015-09-21 16:01:45 +00:00
bad_count_file + = 1
brackets_list . append ( ' ] ' )
elif ( c == ' { ' ) :
brackets_list . append ( ' { ' )
elif ( c == ' } ' ) :
2015-10-11 15:25:53 +00:00
lastIsCurlyBrace = True
2015-09-21 16:01:45 +00:00
if ( brackets_list [ - 1 ] in [ ' ( ' , ' [ ' ] ) :
2015-10-11 15:25:53 +00:00
print ( " ERROR: Possible missing curly brace ' }} ' detected at {0} Line number: {1} " . format ( filepath , lineNumber ) )
2015-09-21 16:01:45 +00:00
bad_count_file + = 1
brackets_list . append ( ' } ' )
2015-12-21 16:06:04 +00:00
elif ( c == ' \t ' ) :
print ( " ERROR: Tab detected at {0} Line number: {1} " . format ( filepath , lineNumber ) )
bad_count_file + = 1
2016-05-21 11:26:04 +00:00
2019-01-05 02:10:40 +00:00
if ( c not in [ ' ' , ' \t ' , ' \n ' ] ) :
onlyWhitespace = False
2018-09-20 19:34:59 +00:00
if ( checkForSemicolon ) :
2015-10-11 15:25:53 +00:00
if ( c not in [ ' ' , ' \t ' , ' \n ' , ' / ' ] ) : # keep reading until no white space or comments
2018-09-20 19:34:59 +00:00
checkForSemicolon = False
2015-10-11 15:25:53 +00:00
if ( c not in [ ' ] ' , ' ) ' , ' } ' , ' ; ' , ' , ' , ' & ' , ' ! ' , ' | ' , ' = ' ] and not validKeyWordAfterCode ( content , indexOfCharacter ) ) : # , 'f', 'd', 'c', 'e', 'a', 'n', 'i']):
2018-09-20 19:34:59 +00:00
print ( " ERROR: Possible missing semicolon ' ; ' detected at {0} Line number: {1} " . format ( filepath , lineNumber ) )
2015-10-11 15:25:53 +00:00
bad_count_file + = 1
else : # Look for the end of our comment block
2015-09-21 16:01:45 +00:00
if ( c == ' * ' ) :
checkIfNextIsClosingBlock = True ;
elif ( checkIfNextIsClosingBlock ) :
if ( c == ' / ' ) :
isInCommentBlock = False
elif ( c != ' * ' ) :
checkIfNextIsClosingBlock = False
2015-10-11 15:25:53 +00:00
indexOfCharacter + = 1
2015-09-21 16:01:45 +00:00
if brackets_list . count ( ' [ ' ) != brackets_list . count ( ' ] ' ) :
2015-10-11 15:25:53 +00:00
print ( " ERROR: A possible missing square bracket [ or ] in file {0} [ = {1} ] = {2} " . format ( filepath , brackets_list . count ( ' [ ' ) , brackets_list . count ( ' ] ' ) ) )
2015-09-21 16:01:45 +00:00
bad_count_file + = 1
if brackets_list . count ( ' ( ' ) != brackets_list . count ( ' ) ' ) :
2015-10-11 15:25:53 +00:00
print ( " ERROR: A possible missing round bracket ( or ) in file {0} ( = {1} ) = {2} " . format ( filepath , brackets_list . count ( ' ( ' ) , brackets_list . count ( ' ) ' ) ) )
2015-09-21 16:01:45 +00:00
bad_count_file + = 1
if brackets_list . count ( ' { ' ) != brackets_list . count ( ' } ' ) :
2015-10-11 15:25:53 +00:00
print ( " ERROR: A possible missing curly brace {{ or }} in file {0} {{ = {1} }} = {2} " . format ( filepath , brackets_list . count ( ' { ' ) , brackets_list . count ( ' } ' ) ) )
2015-09-21 16:01:45 +00:00
bad_count_file + = 1
2018-09-17 19:19:29 +00:00
pattern = re . compile ( ' \ s*(/ \ *[ \ s \ S]+? \ */) \ s*#include ' )
if pattern . match ( content ) :
print ( " ERROR: A found #include after block comment in file {0} " . format ( filepath ) )
bad_count_file + = 1
2015-09-21 16:01:45 +00:00
return bad_count_file
def main ( ) :
2015-10-11 16:31:39 +00:00
print ( " Validating SQF " )
2015-09-21 16:01:45 +00:00
sqf_list = [ ]
bad_count = 0
parser = argparse . ArgumentParser ( )
2015-10-11 15:25:53 +00:00
parser . add_argument ( ' -m ' , ' --module ' , help = ' only search specified module addon folder ' , required = False , default = " " )
2015-09-21 16:01:45 +00:00
args = parser . parse_args ( )
2021-06-29 18:21:08 +00:00
for folder in [ ' addons ' , ' optionals ' ] :
# Allow running from root directory as well as from inside the tools directory
rootDir = " ../ " + folder
if ( os . path . exists ( folder ) ) :
rootDir = folder
for root , dirnames , filenames in os . walk ( rootDir + ' / ' + args . module ) :
for filename in fnmatch . filter ( filenames , ' *.sqf ' ) :
sqf_list . append ( os . path . join ( root , filename ) )
2015-09-21 16:01:45 +00:00
for filename in sqf_list :
bad_count = bad_count + check_sqf_syntax ( filename )
2015-10-11 16:31:39 +00:00
print ( " ------ \n Checked {0} files \n Errors detected: {1} " . format ( len ( sqf_list ) , bad_count ) )
2015-10-11 15:25:53 +00:00
if ( bad_count == 0 ) :
2015-10-11 16:31:39 +00:00
print ( " SQF validation PASSED " )
2015-10-11 15:25:53 +00:00
else :
2015-10-11 16:31:39 +00:00
print ( " SQF validation FAILED " )
2015-10-11 15:25:53 +00:00
2015-09-22 18:18:12 +00:00
return bad_count
2015-09-21 16:01:45 +00:00
if __name__ == " __main__ " :
2015-09-22 18:18:12 +00:00
sys . exit ( main ( ) )