From 97daccf7ed7d5aedb57a981761afeca6762fb6aa Mon Sep 17 00:00:00 2001 From: Brandon Davidson Date: Thu, 19 Feb 2015 22:22:37 -0600 Subject: [PATCH] Changed include to support wildcards; Preprocs now return an optional ok and err value --- lua/starfall/editor.lua | 12 ++- lua/starfall/libs_sh/builtins.lua | 95 +++++++++++++++++-- lua/starfall/preprocessor.lua | 151 ++++++++++++++++++++++++++---- 3 files changed, 226 insertions(+), 32 deletions(-) diff --git a/lua/starfall/editor.lua b/lua/starfall/editor.lua index 0c0fbe1b..e544c82a 100644 --- a/lua/starfall/editor.lua +++ b/lua/starfall/editor.lua @@ -868,11 +868,15 @@ if CLIENT then if path == codename and maincode then code = maincode else - code = file.Read( "starfall/"..path, "DATA" ) or error( "Bad include: " .. path, 0 ) + code = file.Read( "starfall/"..path, "DATA" ) or error( { fatal = false, err = "Bad include: " .. path }, 0 ) + end tbl.files[ path ] = code - SF.Preprocessor.ParseDirectives( path, code, {}, ppdata ) + local ok, err = SF.Preprocessor.ParseDirectives( path, code, {}, ppdata, nil, { "include" } ) + if not ok and err then + error( { fatal = false, err = err }, 0 ) + end if ppdata.includes and ppdata.includes[ path ] then local inc = ppdata.includes[ path ] @@ -929,8 +933,8 @@ if CLIENT then if ok then return true, tbl - elseif msg:sub( 1, 13 ) == "Bad include: " then - return false, msg + elseif type( msg ) == "table" and not msg.fatal then + return false, msg.err else error( msg, 0 ) end diff --git a/lua/starfall/libs_sh/builtins.lua b/lua/starfall/libs_sh/builtins.lua index 873f7c99..2dc359d5 100644 --- a/lua/starfall/libs_sh/builtins.lua +++ b/lua/starfall/libs_sh/builtins.lua @@ -245,19 +245,26 @@ function SF.DefaultEnvironment.printTable ( tbl ) printTableX( ( SERVER and SF.instance.player or LocalPlayer() ), tbl, 0, { t = true } ) end +local function parseInclude ( file ) + if file:find( "%*%*" ) ~= nil then + if file:find( "%*%*.*/" ) ~= nil then + SF.throw( file:match( "(%*%*[^/]*[/*^/*]*)$" ) .. " is invalid, did you mean '" .. file:match( "(%*%*.*[/*.*]*)$" ):gsub( "%*%*", "%*" ) .. "'?", 2 ) + end + file = file:gsub( "%*%*", "[^/*/*]" ) + else + file = file:gsub( "%*", "[^/]*" ) .. "$" + end ---- Runs an included script and caches the result. --- Works pretty much like standard Lua require() --- @param file The file to include. Make sure to --@include it --- @return Return value of the script -function SF.DefaultEnvironment.require (file) - SF.CheckType( file, "string" ) + return file +end + +local function sfRequire ( file ) local loaded = SF.instance.data.reqloaded if not loaded then loaded = {} SF.instance.data.reqloaded = loaded end - + if loaded[ file ] then return loaded[ file ] else @@ -268,17 +275,85 @@ function SF.DefaultEnvironment.require (file) end end ---- Runs an included script, but does not cache the result. --- Pretty much like standard Lua dofile() +--- Runs an included script and caches the result. +-- Works pretty much like standard Lua require() -- @param file The file to include. Make sure to --@include it +-- @param loadpriority Table of files that should be loaded before any others in the directory, only used if more than one file is specified. -- @return Return value of the script -function SF.DefaultEnvironment.dofile ( file ) +function SF.DefaultEnvironment.require ( file, loadpriority ) + SF.CheckType( file, "string" ) + if loadpriority ~= nil then SF.CheckType( loadpriority, "table" ) end + + file = parseInclude( file ) + + local parsedLoadPriority = {} + + for _, fileName in pairs( loadpriority or {} ) do + parsedLoadPriority.insert( parseInclude( fileName ) ) + end + + local returns = {} + + for _, pattern in pairs( parsedLoadPriority or {} ) do + for fileName, _ in pairs( SF.instance.scripts ) do + if fileName:find( pattern ) ~= nil and not returns[ fileName ] then + returns[ fileName ] = sfRequire( fileName ) + end + end + end + + for fileName, _ in pairs( SF.instance.scripts ) do + if fileName:find( file ) ~= nil and not returns[ fileName ] then + returns[ fileName ] = sfRequire( fileName ) + end + end + + return returns +end + +local function sfDofile ( file ) SF.CheckType( file, "string" ) local func = SF.instance.scripts[ file ] if not func then SF.throw( "Can't find file '" .. file .. "' ( Did you forget to --@include it? )", 2 ) end return func() end +--- Runs an included script, but does not cache the result. +-- Pretty much like standard Lua dofile() +-- @param file The file(s) to include. Make sure to --@include it +-- @param loadpriority Table of files that should be loaded before any others in the directory, only used if more than one file is specified. +-- @return Return value of the script +function SF.DefaultEnvironment.dofile ( file, loadpriority ) + SF.CheckType( file, "string" ) + if loadpriority ~= nil then SF.CheckType( loadpriority, "table" ) end + + file = parseInclude( file ) + + local parsedLoadPriority = {} + + for _, fileName in pairs( loadpriority or {} ) do + parsedLoadPriority.insert( parseInclude( fileName ) ) + end + + local returns = {} + + for _, pattern in pairs( parsedLoadPriority or {} ) do + for fileName, _ in pairs( SF.instance.scripts ) do + if fileName:find( pattern ) ~= nil and not returns[ fileName ] then + returns[ fileName ] = sfDofile( fileName ) + end + end + end + + for fileName, _ in pairs( SF.instance.scripts ) do + if fileName:find( file ) ~= nil and not returns[ fileName ] then + returns[ fileName ] = sfDofile( fileName ) + end + end + + return returns +end + --- GLua's loadstring -- Works like loadstring, except that it executes by default in the main environment -- @param str String to execute diff --git a/lua/starfall/preprocessor.lua b/lua/starfall/preprocessor.lua index 2472a1b8..7667aaf7 100644 --- a/lua/starfall/preprocessor.lua +++ b/lua/starfall/preprocessor.lua @@ -86,22 +86,20 @@ local function FindComments ( line ) return ret, count end - --- Parses a source file for directives. -- @param filename The file name of the source code -- @param source The source code to parse. --- @param directives A table of additional directives to use. --- @param data The data table passed to the directives. --- @param instance The instance -function SF.Preprocessor.ParseDirectives ( filename, source, directives, data, instance ) +local function parseDirectives ( filename, source ) local ending local endingLevel - + + local directives = {} + local str = source while str ~= "" do local line line, str = string.match( str, "^([^\n]*)\n?(.*)$" ) - + for _,comment in ipairs( FindComments( line ) ) do if ending then if comment.type == ending then @@ -123,23 +121,135 @@ function SF.Preprocessor.ParseDirectives ( filename, source, directives, data, i endingLevel = comment.level elseif comment.type == "line" then local directive, args = string.match( line, "--@([^ ]+)%s*(.*)$" ) - local func = directives[ directive ] or SF.Preprocessor.directives[ directive ] - if func then - func( args, filename, data, instance ) + if directive then + if not directives[ directive ] then directives[ directive ] = {} end + table.insert( directives[ directive ], args ) end end end - + if ending == "newline" then ending = nil end end + return directives +end + +--- Parses a source file for directives +-- @param filename The file name of the source code. +-- @param source The source code to parse. +-- @param preprocs The preprocessors to search for, can be a string for one, a table for multiple, and nil for all +-- @return Table of preprocs and their args +function SF.Preprocessor.GetDirectives ( filename, source, preprocs ) + local parsedDirectives = parseDirectives( filename, source ) + + local preprocs_type = type( preprocs ) + if preprocs_type == "string" then --1 specified preproc + return parsedDirectives[ preprocs ] + elseif preprocs_type == "table" then --Table of specified preprocs + local ret = {} + for _, preproc in pairs( preprocs ) do + ret[ preproc ] = parsedDirectives[ preproc ] + end + return ret + elseif preprocs == nil then --All preprocs in the file + return parsedDirectives + end end -local function directive_include (args, filename, data) - if not data.includes then data.includes = {} end - if not data.includes[ filename ] then data.includes[ filename ] = {} end - - local incl = data.includes[ filename ] - incl[ #incl + 1 ] = args +--- Parses a source file for directives +-- @param filename The file name of the source code. +-- @param source The source code to parse. +-- @param directives A table of additional directives to use. +-- @param data The data table passed to the directives. +-- @param instance The instance +-- @param preprocs The preprocessors to search for, can be a table for multiple, and nil for all +-- @return Table of preprocs and their args +function SF.Preprocessor.ParseDirectives ( filename, source, directives, data, instance, preprocs ) + local parsedDirectives = SF.Preprocessor.GetDirectives( filename, source ) + + local ok, err = true, {} + + if preprocs then + local _parsed = {} + for _, preproc in pairs( preprocs ) do + _parsed[ preproc ] = parsedDirectives[ preproc ] + end + parsedDirectives = _parsed + end + + for preproc, v in pairs( parsedDirectives or {} ) do + for _, args in pairs( v ) do + local func = directives[ preproc ] or SF.Preprocessor.directives[ preproc ] + if func then + local _ok, _err = func( args, filename, data, instance ) + if _ok == false then + ok = false + err[ preproc ] = _err + end + end + end + end + + return ok, err +end + +local function directive_include ( args, filename, data ) + if CLIENT and args then + if not data.includes then data.includes = {} end + if not data.includes[ filename ] then data.includes[ filename ] = {} end + + local incl = data.includes[ filename ] + + if args:find( "%*" ) == nil then + incl[ #incl + 1 ] = args + return + end + + if args:find( "%*%*" ) ~= nil then + if args:find( "%*%*.*/" ) ~= nil then + return false, args:match( "(%*%*.*)$" ) .. " is invalid, did you mean '" .. args:match( "(%*%*.*.*)$" ):gsub( "%*%*", "%*" ) .. "'?" + end + --Include recursively + args = args:gsub( "%*%*", "*" ) + local function find ( search ) + local files, dirs = file.Find( "starfall/" .. search, "DATA" ) + for _, file in pairs( files ) do + incl[ #incl + 1 ] = search:match( "(.*/).*$" ) .. file + end + + for _, dir in pairs( dirs ) do + find( search:match( "(.*/)[.*]$" ) .. dir .. "/*" ) + end + end + find( args ) + else + local smashedWords = {} + + local i = 1 + + for word in args:gmatch( "([^/]*)" ) do + if word ~= "" then + smashedWords[ i ] = ( smashedWords[ i ] and smashedWords[ i ] .. "/" .. word ) or word + if word:find( "*" ) ~= nil then + i = i + 1 + end + end + end + + local function find ( level, search ) + local files, dirs = file.Find( "starfall/" .. search, "DATA" ) + if level < #smashedWords then --It's a directory + for _, dir in pairs( dirs ) do + find( level + 1, ( search:match( "(.*/)[.*]$" ) or "" ) .. dir .. "/" .. smashedWords[ level + 1 ] ) + end + else --It's a file + for _, file in pairs( files ) do + incl[ #incl + 1 ] = ( search:match( "(.*/).*$" ) or "" ) .. file + end + end + end + find( 1, smashedWords[ 1 ] ) + end + end end SF.Preprocessor.SetGlobalDirective( "include", directive_include ) @@ -168,7 +278,12 @@ SF.Preprocessor.SetGlobalDirective( "model", directive_model ) -- @class directive -- @param path Path to the file -- @usage --- \--@include lib/someLibrary.txt +-- \--@include lib/**.txt +-- --** includes files recursivley +-- \--@include lib/*/init.txt +-- --* is treated as a wildcard that only applies to the directory that it is in +-- \--@include lib/afile.txt +-- --No *'s are treated like exact paths -- -- require( "lib/someLibrary.txt" ) -- -- CODE