--[[----------------------------------------------------------------------------

KSLuaDecompiler.lua

--------------------------------------------------------------------------------

ADOBE CONFIDENTIAL
------------------
Copyright 2005 Adobe Systems Incorporated
All Rights Reserved.

NOTICE: All information contained herein is, and remain the property of
Adobe Systems Incorporated and its suppliers, if any. The intellectual and
technical concepts contained herein are proprietary to Adobe Systems
Incorporated and its suppliers and may be covered by U.S. and Foreign
Patents, patents in process, and are protected by trade secret or copyright
law.  Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

--------------------------------------------------------------------------------

INTEGRATION NOTE: This file (and the files it requires below) are branched from
Lightroom's linting code. Please use the Perforce branch spec named
wichita-integration-lightroom-lint to pull any updates that Lightroom team
may make to these files.

The corresponding files are found in Lightroom's Perforce depot at:
	//shadowland/main/frameworks/ide/source/common/...

----------------------------------------------------------------------------]]--

local findUnusedLocals = require "UnusedLocals"
local findShadowLocals = require "ShadowLocals"
local findNotEqualsBugs = require "NotEqualsBugs"
local findUndeclaredGlobals = require "UndeclaredGlobals"
local findVisibleNames = require "VisibleNames"

--------------------------------------------------------------------------------

local getFunctionDescription = assert( debug.describefn )

--------------------------------------------------------------------------------

local function crackInstructions( code )
	local cracked = { }
	
	for i, instruction in ipairs( code ) do
		cracked[ i ] = debug.disassemble( instruction )
	end
	
	return cracked
end

--------------------------------------------------------------------------------

local predeclaredTable = {
	_G = true,
	_env = true,
	assert = true,
	bindmethod = true,
	breakpoint = true,
	callmethod = true,
	collectgarbage = true,
	copcall = true,
	coroutine = true,
	coxpcall = true,
	declare = true,
	debug = true,
	dofile = true,
	error = true,
	--[[ [scouten 2010-10-28]: These are Lightroom's additions. Wichita has
		 no additional predefined globals. Leaving this table here, but commented
		 out to make future merges easier.
	export = true,
	]]-- END Lightroom extra globals
	getfenv = true,
	getmetatable = true,
	halt = true,
	hasmethod = true,
	--[[ [scouten 2010-10-28]: These are Lightroom's additions. Wichita has
		 no additional predefined globals. Leaving this table here, but commented
		 out to make future merges easier.
	import = true,
	]]-- END Lightroom extra globals
	io = true,
	ipairs = true,
	load = true,
	loadfile = true,
	loadstring = true,
	math = true,
	module = true,
	next = true,
	os = true,
	pack = true,
	package = true,
	pairs = true,
	pcall = true,
	pcallmethod = true,
	pcall2call = true,
	print = true,
	rawequal = true,
	rawget = true,
	rawset = true,
	require = true,
	select = true,
	setfenv = true,
	setmetatable = true,
	string = true,
	table = true,
	tonumber = true,
	tostring = true,
	type = true,
	unpack = true,
	xpcall = true,
	
	-- Our additions:
	--[[ [scouten 2010-10-28]: These are Lightroom's additions. Wichita has
		 no additional predefined globals. Leaving this table here, but commented
		 out to make future merges easier.
	
	_B = true,
	_BUNDLE = true,
	_PLUGIN = true,
	
	DEBUG = true,			-- Configurations
	NDEBUG = true,
	RELEASE_BUILD = true,
	MAC_ENV = true,
	WIN_ENV = true,
	
	LOC = true,
	ZSTR = true,
	
	AgCallWithAutoreleasePool = true,
	
	AgMutablePoint = true,	-- Geometry
	AgMutableRange = true,
	AgMutableRect = true,
	AgPoint = true,
	AgRange = true,
	AgRect = true,
	
	AgDirectoryEnumerator = true,
	
	AgAlwaysLog = true,
	AgDebugLog = true,
	AgAlwaysLogTraceback = true,
	AgDebugLogTraceback = true,
	AgAlwaysPrint = true,
	AgAlwaysStdout = true,
	AgDebugPrint = true,
	AgBreakOnThrow = true,
	AgBreakOnHalt = true,
	AgGetBreakOnHalt = true,
	AgSetBreakOnHalt = true,
	AgGetBreakOnThrow = true,
	AgSetBreakOnThrow = true,
	
	]]-- END Lightroom globals
}

local function canOmitDeclaration( x )
	return predeclaredTable[ x ] == true
end

-- additional globals for the lua templating language [arahn 07/25/2007]
--[[ [scouten 2010-10-28]: These are Lightroom's additions. Wichita has
	 no additional predefined globals. Leaving this table here, but commented
	 out to make future merges easier.

local kLuaTemplatingLanguageManifestGlobals = {
	AddPage = true,
	AddResources = true,
	AddResource = true,
	AddPhotoPages = true,
	AddGridPages = true,
	AddCustomCSS = true,
	IdentityPlate = true,
	importTags = true,
	PRIMARY = true,
	model = true,
}
]]-- END Additional Lightroom globals

--------------------------------------------------------------------------------

local function getXtraOkGlobalsForFilename( path )
	-- arahn : adding support for additional globals of the LuaTemplatingLanguage
	-- test to see if this is a "manifest.lua" file, and add the DSL (domain specific
	-- language) functions to the allowlist.
	local xtraOkGlobals = nil

--[[ [scouten 2010-10-28]: These are Lightroom's additions. Wichita has
	 no additional predefined globals. Leaving this table here, but commented
	 out to make future merges easier.

	if type( path ) == 'string' then
		if string.find( path, "manifest.lrweb$" ) then
			xtraOkGlobals = kLuaTemplatingLanguageManifestGlobals
		end
	end

]]-- END Additional Lightroom globals
	
	return xtraOkGlobals
end

--------------------------------------------------------------------------------

local function makeIsDeclaredPredicate( extra )
	if extra then
		assert( type( extra ) == "table", "extra must be a table" )
		
		return function( name )
			return canOmitDeclaration( name ) or extra[ name ]
		end
	else
		return canOmitDeclaration
	end
end

--------------------------------------------------------------------------------

local function findUndeclaredGlobalsFromLoadResult( f, err, optFilename )
	if f then
		local fd = getFunctionDescription( f )
		local isDeclared = makeIsDeclaredPredicate( getXtraOkGlobalsForFilename( optFilename ) )

		return findUndeclaredGlobals( isDeclared, crackInstructions, fd )
	else
		return { [ err ] = true }
	end
end

local function findUndeclaredGlobalsInLuaFile( path )
	local f, err = loadfile( path )
	
	return findUndeclaredGlobalsFromLoadResult( f, err, path )
end

local function findUndeclaredGlobalsInLuaSource( sourceString, optFilename )
	local f, err = loadstring( sourceString, optFilename )

	return findUndeclaredGlobalsFromLoadResult( f, err, optFilename )
end

--------------------------------------------------------------------------------

local toignore = {
	[ "(for index)" ] = true,
	[ "(for limit)" ] = true,
	[ "(for generator)" ] = true,
	[ "(for step)" ] = true,
	[ "(for state)" ] = true,
	[ "(for control)" ] = true,
	[ "_" ] = true, -- convention
}

--------------------------------------------------------------------------------

local function countParams( fd )
	local vararg = fd.is_vararg
	if vararg == 0 or vararg == 2 then
		-- 2 is used at the top level of a chunk; for that case arg is not an explicit local
		return fd.numParams
	else
		local varloc = fd.locals[ fd.numParams + 1 ]
		assert( varloc and varloc.varname == 'arg', "non vararg param where vararg expected" )
		return 1 + fd.numParams
	end
end

--------------------------------------------------------------------------------

-- make this adjustment so that this code can operate even without substrate,
-- this way it will work with import/export if substrate is available, but also
-- work with ordinary lua require if not
local export = rawget( _G, "export" ) or function( name ) return function( t ) return t end end

return export "AgLuaDecompiler" {
	findUndeclaredGlobalsInLuaSource = findUndeclaredGlobalsInLuaSource, -- ( sourceCodeStr -- aTable )
	findUndeclaredGlobalsInLuaFile = findUndeclaredGlobalsInLuaFile, -- ( filePathStr -- aTable )
	-- returns a table where all keys are identifiers (the values are all 'true')
	-- Note that the this function recurses into subfunctions and treats the top function
	-- specially, so it really is intended for use on the top function of a loaded code chunk.

	findSeriousWarningsInLuaSource = function( source, optFilename )
		local fn, err = loadstring( source )
		local globals, badRequires, unusedGlobals = findUndeclaredGlobalsFromLoadResult( fn, err, optFilename )
		
		if not fn then
			-- if it doesn't compile, undeclared globals sends back the message
			return globals
		end
		
		local fd = getFunctionDescription( fn )
		local notEqualsBugs = findNotEqualsBugs( crackInstructions, fd )
		
		local results = { }
		for name, line in pairs( globals ) do
			table.insert( results, { kind = "global", name = name, line = line } )
		end

		for _, bug in ipairs( notEqualsBugs ) do
			table.insert( results, bug )
		end

		for _, req in ipairs( badRequires ) do
			table.insert( results, req )
		end

		for _, ung in ipairs( unusedGlobals ) do
			table.insert( results, ung )
		end

		return results
	end,

	findWarningsInLuaSource = function( source )
		local results = { }
		local fd = getFunctionDescription( loadstring( source ) )

		findUnusedLocals( crackInstructions, countParams, toignore, fd, results )
		findShadowLocals( toignore, fd, results )
		
		return results
	end,

	findUnusedLocalsInLuaSource = function( source )
		-- SPAM [dtull 2009-04-06]: centralize logic for compiling and describing the chunk
		return findUnusedLocals( crackInstructions, countParams, toignore, getFunctionDescription( loadstring( source ) ) )
	end,
	
	findUnusedLocalsInLuaFile = function( filename )
		-- SPAM [dtull 2009-04-06]: centralize logic for compiling and describing the chunk
		return findUnusedLocals( crackInstructions, countParams, toignore, getFunctionDescription( loadfile( filename ) ) )
	end,
	
	findShadowLocalsInLuaSource = function( source )
		-- SPAM [dtull 2009-11-11]: centralize logic for compiling and describing the chunk
		return findShadowLocals( toignore, getFunctionDescription( loadstring( source ) ) )
	end,
	
	findShadowLocalsInLuaFile = function( filename )
		-- SPAM [dtull 2009-11-11]: centralize logic for compiling and describing the chunk
		return findShadowLocals( toignore, getFunctionDescription( loadfile( filename ) ) )
	end,

	findNotEqualsBugsInLuaSource = function( source )
		-- SPAM [dtull 2009-11-18]: centralize logic for compiling and describing the chunk
		return findNotEqualsBugs( crackInstructions, getFunctionDescription( loadstring( source ) ) )
	end,
	
	findNotEqualsBugsInLuaFile = function( filename )
		-- SPAM [dtull 2009-11-18]: centralize logic for compiling and describing the chunk
		return findNotEqualsBugs( crackInstructions, getFunctionDescription( loadfile( filename ) ) )
	end,

	findVisibleNamesInLuaSource = function( source, line )
		-- SPAM [dtull 2009-12-1]: centralize logic for compiling and describing the chunk
		return findVisibleNames( countParams, toignore, getFunctionDescription( loadstring( source ) ), line )
	end,

	getFunctionDescription = getFunctionDescription,
	crackInstructions = crackInstructions,
	
	-- for use in evaluating the "legitimacy" of _B contents
	canOmitDeclaration  = canOmitDeclaration
}
