Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changed include to support wildcards #346

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions lua/starfall/editor.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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 ]
Expand Down Expand Up @@ -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
Expand Down
95 changes: 85 additions & 10 deletions lua/starfall/libs_sh/builtins.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
151 changes: 133 additions & 18 deletions lua/starfall/preprocessor.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 )

Expand Down Expand Up @@ -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
Expand Down