Merge remote-tracking branch 'refs/remotes/origin/master' into master-backup

This commit is contained in:
FlightControl_Master
2017-10-03 11:06:35 +02:00
289 changed files with 46941 additions and 1323 deletions

View File

@@ -3,7 +3,7 @@
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.ui.externaltools.launchGroup"/>
</listAttribute>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Utils/luarocks/lua5.1.exe}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Utils/Generate_Moose.bat}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="&quot;Moose_Create.lua&quot; &#13;&#10;&quot;D&quot;&#13;&#10;&quot;${current_date}&quot; &#13;&#10;&quot;${workspace_loc:/Moose_Framework//Moose Development/Moose}&quot; &#13;&#10;&quot;${workspace_loc:/Moose_Framework/Moose Mission Setup}&quot;"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Moose_Framework/Moose Mission Setup}"/>
</launchConfiguration>

View File

@@ -3,7 +3,7 @@
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.ui.externaltools.launchGroup"/>
</listAttribute>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Utils/luarocks/lua5.1.exe}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Utils/Generate_Moose.bat}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="&quot;Moose_Create.lua&quot; &#13;&#10;&quot;S&quot;&#13;&#10;&quot;${current_date}&quot; &#13;&#10;&quot;${workspace_loc:/Moose_Framework//Moose Development/Moose}&quot; &#13;&#10;&quot;${workspace_loc:/Moose_Framework/Moose Mission Setup}&quot;"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Moose_Framework/Moose Mission Setup}"/>
</launchConfiguration>

View File

@@ -248,6 +248,7 @@ function AI_A2A:New( AIGroup )
-- @param #AI_A2A self
-- @param #number Delay
self:AddTransition( "*", "Takeoff", "Airborne" )
self:AddTransition( "*", "Return", "Returning" )
self:AddTransition( "*", "Hold", "Holding" )
self:AddTransition( "*", "Home", "Home" )
@@ -263,6 +264,13 @@ function AI_A2A:New( AIGroup )
return self
end
--- @param Wrapper.Group#GROUP self
-- @param Core.Event#EVENTDATA EventData
function GROUP:OnEventTakeoff( EventData, Fsm )
Fsm:Takeoff()
self:UnHandleEvent( EVENTS.Takeoff )
end
function AI_A2A:SetDispatcher( Dispatcher )
self.Dispatcher = Dispatcher
end
@@ -458,7 +466,6 @@ function AI_A2A:onafterStatus()
else
self:E( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... RTB!" )
local OldAIControllable = self.Controllable
local AIControllableTemplate = self.Controllable:GetTemplate()
local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
local TimedOrbitTask = OldAIControllable:TaskControlled( OrbitTask, OldAIControllable:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil ) )

View File

@@ -141,7 +141,7 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
self.EngageMinSpeed = EngageMinSpeed
self.EngageMaxSpeed = EngageMaxSpeed
self:AddTransition( { "Patrolling", "Engaging", "Returning" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_CAP.
self:AddTransition( { "Patrolling", "Engaging", "Returning", "Airborne" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_CAP.
--- OnBefore Transition Handler for Event Engage.
-- @function [parent=#AI_A2A_CAP] OnBeforeEngage
@@ -302,6 +302,17 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
return self
end
--- onafter State Transition for Event Patrol.
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIGroup The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2A_CAP:onafterStart( AIGroup, From, Event, To )
AIGroup:HandleEvent( EVENTS.Takeoff, nil, self )
end
--- Set the Engage Zone which defines where the AI will engage bogies.
-- @param #AI_A2A_CAP self

View File

@@ -978,6 +978,7 @@ do -- AI_A2A_DISPATCHER
-- This will avoid the detection to still "know" the shot unit until the next detection.
-- Otherwise, a new intercept or engage may happen for an already shot plane!
self:HandleEvent( EVENTS.Crash, self.OnEventCrashOrDead )
self:HandleEvent( EVENTS.Dead, self.OnEventCrashOrDead )
@@ -1304,6 +1305,12 @@ do -- AI_A2A_DISPATCHER
function AI_A2A_DISPATCHER:GetDefenderTaskTarget( Defender )
return self:GetDefenderTask( Defender ).Target
end
---
-- @param #AI_A2A_DISPATCHER self
function AI_A2A_DISPATCHER:GetDefenderTaskSquadronName( Defender )
return self:GetDefenderTask( Defender ).SquadronName
end
---
-- @param #AI_A2A_DISPATCHER self
@@ -1354,6 +1361,8 @@ do -- AI_A2A_DISPATCHER
-- @param #AI_A2A_DISPATCHER self
function AI_A2A_DISPATCHER:SetDefenderTask( SquadronName, Defender, Type, Fsm, Target )
self:F( { SquadronName = SquadronName, Defender = Defender:GetName() } )
self.DefenderTasks[Defender] = self.DefenderTasks[Defender] or {}
self.DefenderTasks[Defender].Type = Type
self.DefenderTasks[Defender].Fsm = Fsm
@@ -2513,23 +2522,31 @@ do -- AI_A2A_DISPATCHER
function AI_A2A_DISPATCHER:CountDefendersEngaged( AttackerDetection )
-- First, count the active AIGroups Units, targetting the DetectedSet
local AIUnitCount = 0
local DefenderCount = 0
self:E( "Counting Defenders Engaged for Attacker:" )
local DetectedSet = AttackerDetection.Set
DetectedSet:Flush()
local DefenderTasks = self:GetDefenderTasks()
for AIGroup, DefenderTask in pairs( DefenderTasks ) do
local AIGroup = AIGroup -- Wrapper.Group#GROUP
local DefenderTask = self:GetDefenderTaskTarget( AIGroup )
if DefenderTask and DefenderTask.Index == AttackerDetection.Index then
AIUnitCount = AIUnitCount + AIGroup:GetSize()
self:E( "Defender Group Name: " .. AIGroup:GetName() .. ", Size: " .. AIGroup:GetSize() )
for DefenderGroup, DefenderTask in pairs( DefenderTasks ) do
local Defender = DefenderGroup -- Wrapper.Group#GROUP
local DefenderTaskTarget = DefenderTask.Target
local DefenderSquadronName = DefenderTask.SquadronName
if DefenderTaskTarget and DefenderTaskTarget.Index == AttackerDetection.Index then
local Squadron = self:GetSquadron( DefenderSquadronName )
local SquadronOverhead = Squadron.Overhead or self.DefenderDefault.Overhead
local DefenderSize = Defender:GetInitialSize()
DefenderCount = DefenderCount + DefenderSize / SquadronOverhead
self:F( "Defender Group Name: " .. Defender:GetName() .. ", Size: " .. DefenderSize )
end
end
return AIUnitCount
self:F( { DefenderCount = DefenderCount } )
return DefenderCount
end
---
@@ -2611,10 +2628,21 @@ do -- AI_A2A_DISPATCHER
Fsm:SetDisengageRadius( self.DisengageRadius )
Fsm:SetTanker( DefenderSquadron.TankerName or self.DefenderDefault.TankerName )
Fsm:Start()
Fsm:__Patrol( 2 )
self:SetDefenderTask( SquadronName, DefenderCAP, "CAP", Fsm )
function Fsm:onafterTakeoff( Defender, From, Event, To )
self:F({"GCI Birth", Defender:GetName()})
--self:GetParent(self).onafterBirth( self, Defender, From, Event, To )
local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( Defender )
if Squadron then
Fsm:__Patrol( 2 ) -- Start Patrolling
end
end
function Fsm:onafterRTB( Defender, From, Event, To )
self:F({"CAP RTB", Defender:GetName()})
self:GetParent(self).onafterRTB( self, Defender, From, Event, To )
@@ -2710,7 +2738,7 @@ do -- AI_A2A_DISPATCHER
local SpawnCoord = DefenderSquadron.Airbase:GetCoordinate() -- Core.Point#COORDINATE
local AttackerCoord = AttackerUnit:GetCoordinate()
local InterceptCoord = AttackerDetection.InterceptCoord
self:F({InterceptCoord = InterceptCoord})
self:F( { InterceptCoord = InterceptCoord } )
if InterceptCoord then
local InterceptDistance = SpawnCoord:Get2DDistance( InterceptCoord )
local AirbaseDistance = SpawnCoord:Get2DDistance( AttackerCoord )
@@ -2746,6 +2774,13 @@ do -- AI_A2A_DISPATCHER
self:F( { Grouping = DefenderGrouping, SquadronGrouping = DefenderSquadron.Grouping, DefaultGrouping = self.DefenderDefault.Grouping } )
self:F( { DefendersCount = DefenderCount, DefendersNeeded = DefendersNeeded } )
-- DefenderSquadron.Resources can have the value nil, which expresses unlimited resources.
-- DefendersNeeded cannot exceed DefenderSquadron.Resources!
if DefenderSquadron.Resources and DefendersNeeded > DefenderSquadron.Resources then
DefendersNeeded = DefenderSquadron.Resources
BreakLoop = true
end
while ( DefendersNeeded > 0 ) do
local Spawn = DefenderSquadron.Spawn[ math.random( 1, #DefenderSquadron.Spawn ) ] -- Functional.Spawn#SPAWN
@@ -2758,15 +2793,15 @@ do -- AI_A2A_DISPATCHER
local TakeoffMethod = self:GetSquadronTakeoff( ClosestDefenderSquadronName )
local DefenderGCI = Spawn:SpawnAtAirbase( DefenderSquadron.Airbase, TakeoffMethod, DefenderSquadron.TakeoffAltitude or self.DefenderDefault.TakeoffAltitude ) -- Wrapper.Group#GROUP
self:F( { GCIDefender = DefenderGCI:GetName() } )
self:E( { GCIDefender = DefenderGCI:GetName() } )
DefendersNeeded = DefendersNeeded - DefenderGrouping
self:AddDefenderToSquadron( DefenderSquadron, DefenderGCI, DefenderGrouping )
if DefenderGCI then
DefenderCount = DefenderCount - DefenderGrouping
DefenderCount = DefenderCount - DefenderGrouping / DefenderOverhead
local Fsm = AI_A2A_GCI:New( DefenderGCI, Gci.EngageMinSpeed, Gci.EngageMaxSpeed )
Fsm:SetDispatcher( self )
@@ -2775,12 +2810,24 @@ do -- AI_A2A_DISPATCHER
Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold )
Fsm:SetDisengageRadius( self.DisengageRadius )
Fsm:Start()
Fsm:__Engage( 2, AttackerDetection.Set ) -- Engage on the TargetSetUnit
self:SetDefenderTask( ClosestDefenderSquadronName, DefenderGCI, "GCI", Fsm, AttackerDetection )
function Fsm:onafterTakeoff( Defender, From, Event, To )
self:F({"GCI Birth", Defender:GetName()})
--self:GetParent(self).onafterBirth( self, Defender, From, Event, To )
local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( Defender )
local DefenderTarget = Dispatcher:GetDefenderTaskTarget( Defender )
if DefenderTarget then
Fsm:__Engage( 2, DefenderTarget.Set ) -- Engage on the TargetSetUnit
end
end
function Fsm:onafterRTB( Defender, From, Event, To )
self:F({"GCI RTB", Defender:GetName()})
self:GetParent(self).onafterRTB( self, Defender, From, Event, To )
@@ -2909,7 +2956,11 @@ do -- AI_A2A_DISPATCHER
for AIGroup, DefenderTask in pairs( self:GetDefenderTasks() ) do
local AIGroup = AIGroup -- Wrapper.Group#GROUP
if not AIGroup:IsAlive() then
self:ClearDefenderTask( AIGroup )
local DefenderTaskFsm = self:GetDefenderTaskFsm( AIGroup )
self:E( { Defender = AIGroup:GetName(), DefenderState = DefenderTaskFsm:GetState() } )
if not DefenderTaskFsm:Is( "Started" ) then
self:ClearDefenderTask( AIGroup )
end
else
if DefenderTask.Target then
local AttackerItem = Detection:GetDetectedItem( DefenderTask.Target.Index )

View File

@@ -134,7 +134,7 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
self.PatrolAltType = "RADIO"
self:AddTransition( { "Started", "Engaging", "Returning" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_GCI.
self:AddTransition( { "Started", "Engaging", "Returning", "Airborne" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_GCI.
--- OnBefore Transition Handler for Event Engage.
-- @function [parent=#AI_A2A_GCI] OnBeforeEngage
@@ -295,6 +295,19 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
return self
end
--- onafter State Transition for Event Patrol.
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIGroup The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2A_GCI:onafterStart( AIGroup, From, Event, To )
AIGroup:HandleEvent( EVENTS.Takeoff, nil, self )
end
--- onafter State Transition for Event Patrol.
-- @param #AI_A2A_GCI self

View File

@@ -179,7 +179,7 @@ function AI_A2A_PATROL:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeil
-- defafult PatrolAltType to "RADIO" if not specified
self.PatrolAltType = PatrolAltType or "RADIO"
self:AddTransition( { "Started", "Refuelling" }, "Patrol", "Patrolling" )
self:AddTransition( { "Started", "Airborne", "Refuelling" }, "Patrol", "Patrolling" )
--- OnBefore Transition Handler for Event Patrol.
-- @function [parent=#AI_A2A_PATROL] OnBeforePatrol

View File

@@ -358,16 +358,20 @@ function AI_CAP_ZONE:onafterStart( Controllable, From, Event, To )
end
-- todo: need to fix this global function
--- @param Wrapper.Controllable#CONTROLLABLE AIControllable
function _NewEngageCapRoute( AIControllable )
--- @param AI.AI_CAP#AI_CAP_ZONE
-- @param Wrapper.Group#GROUP EngageGroup
function AI_CAP_ZONE.EngageRoute( EngageGroup, Fsm )
AIControllable:T( "NewEngageRoute" )
local EngageZone = AIControllable:GetState( AIControllable, "EngageZone" ) -- AI.AI_Cap#AI_CAP_ZONE
EngageZone:__Engage( 1 )
EngageGroup:F( { "AI_CAP_ZONE.EngageRoute:", EngageGroup:GetName() } )
if EngageGroup:IsAlive() then
Fsm:__Engage( 1 )
end
end
--- @param #AI_CAP_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
@@ -503,28 +507,20 @@ function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To )
end
end
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
self.Controllable:WayPointInitialize( EngageRoute )
if #AttackTasks == 0 then
self:F("No targets found -> Going back to Patrolling")
self:__Abort( 1 )
self:__Route( 1 )
self:SetDetectionActivated()
else
AttackTasks[#AttackTasks+1] = Controllable:TaskFunction( "AI_CAP_ZONE.EngageRoute", self )
EngageRoute[1].task = Controllable:TaskCombo( AttackTasks )
--- Do a trick, link the NewEngageRoute function of the object to the AIControllable in a temporary variable ...
self.Controllable:SetState( self.Controllable, "EngageZone", self )
self.Controllable:WayPointFunction( #EngageRoute, 1, "_NewEngageCapRoute" )
self:SetDetectionDeactivated()
end
--- NOW ROUTE THE GROUP!
self.Controllable:WayPointExecute( 1, 2 )
Controllable:Route( EngageRoute, 0.5 )
end
end

View File

@@ -373,12 +373,15 @@ function AI_CAS_ZONE:onafterStart( Controllable, From, Event, To )
self:SetDetectionDeactivated() -- When not engaging, set the detection off.
end
--- @param Wrapper.Controllable#CONTROLLABLE AIControllable
function _NewEngageRoute( AIControllable )
--- @param AI.AI_CAS#AI_CAS_ZONE
-- @param Wrapper.Group#GROUP EngageGroup
function AI_CAS_ZONE.EngageRoute( EngageGroup, Fsm )
AIControllable:T( "NewEngageRoute" )
local EngageZone = AIControllable:GetState( AIControllable, "EngageZone" ) -- AI.AI_Cas#AI_CAS_ZONE
EngageZone:__Engage( 1, EngageZone.EngageSpeed, EngageZone.EngageAltitude, EngageZone.EngageWeaponExpend, EngageZone.EngageAttackQty, EngageZone.EngageDirection )
EngageGroup:F( { "AI_CAS_ZONE.EngageRoute:", EngageGroup:GetName() } )
if EngageGroup:IsAlive() then
Fsm:__Engage( 1, Fsm.EngageSpeed, Fsm.EngageAltitude, Fsm.EngageWeaponExpend, Fsm.EngageAttackQty, Fsm.EngageDirection )
end
end
@@ -464,6 +467,9 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
if Controllable:IsAlive() then
Controllable:OptionROEOpenFire()
Controllable:OptionROTVertical()
local EngageRoute = {}
--- Calculate the current route point.
@@ -485,7 +491,7 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
local AttackTasks = {}
for DetectedUnitID, DetectedUnit in pairs( self.DetectedUnits ) do
for DetectedUnit, Detected in pairs( self.DetectedUnits ) do
local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT
self:T( DetectedUnit )
if DetectedUnit:IsAlive() then
@@ -503,7 +509,8 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
end
end
EngageRoute[1].task = Controllable:TaskCombo( AttackTasks )
AttackTasks[#AttackTasks+1] = Controllable:TaskFunction( "AI_CAS_ZONE.EngageRoute", self )
EngageRoute[#EngageRoute].task = Controllable:TaskCombo( AttackTasks )
--- Define a random point in the @{Zone}. The AI will fly to that point within the zone.
@@ -524,20 +531,8 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
)
EngageRoute[#EngageRoute+1] = ToTargetRoutePoint
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
Controllable:WayPointInitialize( EngageRoute )
--- Do a trick, link the NewEngageRoute function of the object to the AIControllable in a temporary variable ...
Controllable:SetState( Controllable, "EngageZone", self )
Controllable:WayPointFunction( #EngageRoute, 1, "_NewEngageRoute" )
--- NOW ROUTE THE GROUP!
Controllable:WayPointExecute( 1 )
Controllable:OptionROEOpenFire()
Controllable:OptionROTVertical()
Controllable:Route( EngageRoute, 0.5 )
self:SetRefreshTimeInterval( 2 )
self:SetDetectionActivated()

View File

@@ -834,7 +834,6 @@ function AI_PATROL_ZONE:onafterStatus()
if Fuel < self.PatrolFuelThresholdPercentage then
self:E( self.Controllable:GetName() .. " is out of fuel:" .. Fuel .. ", RTB!" )
local OldAIControllable = self.Controllable
local AIControllableTemplate = self.Controllable:GetTemplate()
local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
local TimedOrbitTask = OldAIControllable:TaskControlled( OrbitTask, OldAIControllable:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil ) )

View File

@@ -91,7 +91,7 @@ do -- ACT_ACCOUNT
--- StateMachine callback function
-- @param #ACT_ACCOUNT self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
@@ -107,7 +107,7 @@ do -- ACT_ACCOUNT
--- StateMachine callback function
-- @param #ACT_ACCOUNT self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
@@ -125,7 +125,7 @@ do -- ACT_ACCOUNT
--- StateMachine callback function
-- @param #ACT_ACCOUNT self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
@@ -185,30 +185,31 @@ do -- ACT_ACCOUNT_DEADS
--- StateMachine callback function
-- @param #ACT_ACCOUNT_DEADS self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ACCOUNT_DEADS:onenterReport( ProcessUnit, Task, From, Event, To )
self:E( { ProcessUnit, From, Event, To } )
self:Message( "Your group with assigned " .. self.TaskName .. " task has " .. Task.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed." )
local MessageText = "Your group with assigned " .. self.TaskName .. " task has " .. Task.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed."
self:GetCommandCenter():MessageTypeToGroup( MessageText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
end
--- StateMachine callback function
-- @param #ACT_ACCOUNT_DEADS self
-- @param Wrapper.Client#CLIENT ProcessClient
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Tasking.Task#TASK Task
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Event#EVENTDATA EventData
function ACT_ACCOUNT_DEADS:onafterEvent( ProcessClient, Task, From, Event, To, EventData )
self:T( { ProcessClient:GetName(), Task:GetName(), From, Event, To, EventData } )
function ACT_ACCOUNT_DEADS:onafterEvent( ProcessUnit, Task, From, Event, To, EventData )
self:T( { ProcessUnit:GetName(), Task:GetName(), From, Event, To, EventData } )
if Task.TargetSetUnit:FindUnit( EventData.IniUnitName ) then
local PlayerName = ProcessClient:GetPlayerName()
local PlayerName = ProcessUnit:GetPlayerName()
local PlayerHit = self.PlayerHits and self.PlayerHits[EventData.IniUnitName]
if PlayerHit == PlayerName then
self:Player( EventData )
@@ -220,21 +221,23 @@ do -- ACT_ACCOUNT_DEADS
--- StateMachine callback function
-- @param #ACT_ACCOUNT_DEADS self
-- @param Wrapper.Client#CLIENT ProcessClient
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Tasking.Task#TASK Task
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Event#EVENTDATA EventData
function ACT_ACCOUNT_DEADS:onenterAccountForPlayer( ProcessClient, Task, From, Event, To, EventData )
self:T( { ProcessClient:GetName(), Task:GetName(), From, Event, To, EventData } )
function ACT_ACCOUNT_DEADS:onenterAccountForPlayer( ProcessUnit, Task, From, Event, To, EventData )
self:T( { ProcessUnit:GetName(), Task:GetName(), From, Event, To, EventData } )
local TaskGroup = ProcessClient:GetGroup()
local TaskGroup = ProcessUnit:GetGroup()
Task.TargetSetUnit:Remove( EventData.IniUnitName )
self:Message( "You have destroyed a target.\nYour group assigned with task " .. self.TaskName .. " has\n" .. Task.TargetSetUnit:Count() .. " targets ( " .. Task.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed." )
local PlayerName = ProcessClient:GetPlayerName()
local MessageText = "You have destroyed a target.\nYour group assigned with task " .. self.TaskName .. " has\n" .. Task.TargetSetUnit:Count() .. " targets ( " .. Task.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed."
self:GetCommandCenter():MessageTypeToGroup( MessageText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
local PlayerName = ProcessUnit:GetPlayerName()
Task:AddProgress( PlayerName, "Destroyed " .. EventData.IniTypeName, timer.getTime(), 1 )
if Task.TargetSetUnit:Count() > 0 then
@@ -246,18 +249,20 @@ do -- ACT_ACCOUNT_DEADS
--- StateMachine callback function
-- @param #ACT_ACCOUNT_DEADS self
-- @param Wrapper.Client#CLIENT ProcessClient
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Tasking.Task#TASK Task
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Event#EVENTDATA EventData
function ACT_ACCOUNT_DEADS:onenterAccountForOther( ProcessClient, Task, From, Event, To, EventData )
self:T( { ProcessClient:GetName(), Task:GetName(), From, Event, To, EventData } )
function ACT_ACCOUNT_DEADS:onenterAccountForOther( ProcessUnit, Task, From, Event, To, EventData )
self:T( { ProcessUnit:GetName(), Task:GetName(), From, Event, To, EventData } )
local TaskGroup = ProcessClient:GetGroup()
local TaskGroup = ProcessUnit:GetGroup()
Task.TargetSetUnit:Remove( EventData.IniUnitName )
self:Message( "One of the task targets has been destroyed.\nYour group assigned with task " .. self.TaskName .. " has\n" .. Task.TargetSetUnit:Count() .. " targets ( " .. Task.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed." )
local MessageText = "One of the task targets has been destroyed.\nYour group assigned with task " .. self.TaskName .. " has\n" .. Task.TargetSetUnit:Count() .. " targets ( " .. Task.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed."
self:GetCommandCenter():MessageTypeToGroup( MessageText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
if Task.TargetSetUnit:Count() > 0 then
self:__More( 1 )

View File

@@ -229,14 +229,14 @@ do -- ACT_ASSIGN_MENU_ACCEPT
--- StateMachine callback function
-- @param #ACT_ASSIGN_MENU_ACCEPT self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ASSIGN_MENU_ACCEPT:onafterStart( ProcessUnit, From, Event, To )
self:E( { ProcessUnit, From, Event, To } )
self:Message( "Access the radio menu to accept the task. You have 30 seconds or the assignment will be cancelled." )
self:GetCommandCenter():MessageTypeToGroup( "Access the radio menu to accept the task. You have 30 seconds or the assignment will be cancelled.", ProcessUnit:GetGroup(), MESSAGE.Type.Information )
local ProcessGroup = ProcessUnit:GetGroup()
@@ -263,7 +263,7 @@ do -- ACT_ASSIGN_MENU_ACCEPT
--- StateMachine callback function
-- @param #ACT_ASSIGN_MENU_ACCEPT self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
@@ -275,7 +275,7 @@ do -- ACT_ASSIGN_MENU_ACCEPT
--- StateMachine callback function
-- @param #ACT_ASSIGN_MENU_ACCEPT self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To

View File

@@ -154,7 +154,7 @@ do -- ACT_ROUTE
--- Get the routing text to be displayed.
-- The route mode determines the text displayed.
-- @param #ACT_ROUTE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable
-- @param Wrapper.Unit#UNIT Controllable
-- @return #string
function ACT_ROUTE:GetRouteText( Controllable )
@@ -215,7 +215,7 @@ do -- ACT_ROUTE
--- StateMachine callback function
-- @param #ACT_ROUTE self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
@@ -227,7 +227,7 @@ do -- ACT_ROUTE
--- Check if the controllable has arrived.
-- @param #ACT_ROUTE self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @return #boolean
function ACT_ROUTE:onfuncHasArrived( ProcessUnit )
return false
@@ -235,7 +235,7 @@ do -- ACT_ROUTE
--- StateMachine callback function
-- @param #ACT_ROUTE self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
@@ -352,7 +352,7 @@ do -- ACT_ROUTE_POINT
--- Method override to check if the controllable has arrived.
-- @param #ACT_ROUTE_POINT self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @return #boolean
function ACT_ROUTE_POINT:onfuncHasArrived( ProcessUnit )
@@ -361,7 +361,7 @@ do -- ACT_ROUTE_POINT
if Distance <= self.Range then
local RouteText = "You have arrived."
self:Message( RouteText )
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
return true
end
end
@@ -373,14 +373,15 @@ do -- ACT_ROUTE_POINT
--- StateMachine callback function
-- @param #ACT_ROUTE_POINT self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ROUTE_POINT:onafterReport( ProcessUnit, From, Event, To )
local RouteText = self:GetRouteText( ProcessUnit )
self:Message( RouteText )
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Update )
end
end -- ACT_ROUTE_POINT
@@ -445,13 +446,13 @@ do -- ACT_ROUTE_ZONE
--- Method override to check if the controllable has arrived.
-- @param #ACT_ROUTE self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @return #boolean
function ACT_ROUTE_ZONE:onfuncHasArrived( ProcessUnit )
if ProcessUnit:IsInZone( self.Zone ) then
local RouteText = "You have arrived within the zone."
self:Message( RouteText )
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
end
return ProcessUnit:IsInZone( self.Zone )
@@ -461,7 +462,7 @@ do -- ACT_ROUTE_ZONE
--- StateMachine callback function
-- @param #ACT_ROUTE_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
@@ -469,7 +470,7 @@ do -- ACT_ROUTE_ZONE
self:E( { ProcessUnit = ProcessUnit } )
local RouteText = self:GetRouteText( ProcessUnit )
self:Message( RouteText )
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Update )
end
end -- ACT_ROUTE_ZONE

View File

@@ -198,13 +198,17 @@ BASE = {
ClassID = 0,
Events = {},
States = {},
_ = {},
}
--- @field #BASE.__
BASE.__ = {}
--- @field #BASE._
BASE._ = {
Schedules = {} --- Contains the Schedulers Active
}
--- The Formation Class
-- @type FORMATION
-- @field Cone A cone formation.
@@ -608,6 +612,22 @@ function BASE:CreateEventCrash( EventTime, Initiator )
world.onEvent( Event )
end
--- Creation of a Takeoff Event.
-- @param #BASE self
-- @param Dcs.DCSTypes#Time EventTime The time stamp of the event.
-- @param Dcs.DCSWrapper.Object#Object Initiator The initiating object of the event.
function BASE:CreateEventTakeoff( EventTime, Initiator )
self:F( { EventTime, Initiator } )
local Event = {
id = world.event.S_EVENT_TAKEOFF,
time = EventTime,
initiator = Initiator,
}
world.onEvent( Event )
end
-- TODO: Complete Dcs.DCSTypes#Event structure.
--- The main event handling function... This function captures all events generated for the class.
-- @param #BASE self
@@ -638,6 +658,86 @@ function BASE:onEvent(event)
end
end
do -- Scheduling
--- Schedule a new time event. Note that the schedule will only take place if the scheduler is *started*. Even for a single schedule event, the scheduler needs to be started also.
-- @param #BASE self
-- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called.
-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.
-- @param #table ... Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
-- @return #number The ScheduleID of the planned schedule.
function BASE:ScheduleOnce( Start, SchedulerFunction, ... )
self:F2( { Start } )
self:T3( { ... } )
local ObjectName = "-"
ObjectName = self.ClassName .. self.ClassID
self:F3( { "ScheduleOnce: ", ObjectName, Start } )
self.SchedulerObject = self
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule(
self,
SchedulerFunction,
{ ... },
Start,
nil,
nil,
nil
)
self._.Schedules[#self.Schedules+1] = ScheduleID
return self._.Schedules
end
--- Schedule a new time event. Note that the schedule will only take place if the scheduler is *started*. Even for a single schedule event, the scheduler needs to be started also.
-- @param #BASE self
-- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called.
-- @param #number Repeat Specifies the interval in seconds when the scheduler will call the event function.
-- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat.
-- @param #number Stop Specifies the amount of seconds when the scheduler will be stopped.
-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.
-- @param #table ... Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
-- @return #number The ScheduleID of the planned schedule.
function BASE:ScheduleRepeat( Start, Repeat, RandomizeFactor, Stop, SchedulerFunction, ... )
self:F2( { Start } )
self:T3( { ... } )
local ObjectName = "-"
ObjectName = self.ClassName .. self.ClassID
self:F3( { "ScheduleRepeat: ", ObjectName, Start, Repeat, RandomizeFactor, Stop } )
self.SchedulerObject = self
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule(
self,
SchedulerFunction,
{ ... },
Start,
Repeat,
RandomizeFactor,
Stop
)
self._.Schedules[SchedulerFunction] = ScheduleID
return self._.Schedules
end
--- Stops the Schedule.
-- @param #BASE self
-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.
function BASE:ScheduleStop( SchedulerFunction )
self:F3( { "ScheduleStop:" } )
_SCHEDULEDISPATCHER:Stop( self, self._.Schedules[SchedulerFunction] )
end
end
--- Set a state or property of the Object given a Key and a Value.
-- Note that if the Object is destroyed, nillified or garbage collected, then the Values and Keys will also be gone.
-- @param #BASE self

View File

@@ -561,12 +561,13 @@ end
-- @param Core.Base#BASE EventClass The self instance of the class for which the event is.
-- @param EventID
-- @return #EVENT
function EVENT:OnEventForGroup( GroupName, EventFunction, EventClass, EventID )
self:F2( GroupName )
function EVENT:OnEventForGroup( GroupName, EventFunction, EventClass, EventID, ... )
self:E( GroupName )
local Event = self:Init( EventID, EventClass )
Event.EventGroup = true
Event.EventFunction = EventFunction
Event.Params = arg
return self
end
@@ -950,7 +951,7 @@ function EVENT:onEvent( Event )
local Result, Value = xpcall(
function()
return EventData.EventFunction( EventClass, Event )
return EventData.EventFunction( EventClass, Event, unpack( EventData.Params ) )
end, ErrorHandler )
else
@@ -966,14 +967,14 @@ function EVENT:onEvent( Event )
local Result, Value = xpcall(
function()
return EventFunction( EventClass, Event )
return EventFunction( EventClass, Event, unpack( EventData.Params ) )
end, ErrorHandler )
end
end
end
else
-- The EventClass is not alive anymore, we remove it from the EventHandlers...
self:RemoveEvent( EventClass, Event.id )
--self:RemoveEvent( EventClass, Event.id )
end
else

View File

@@ -139,11 +139,24 @@ do -- MENU_COMMAND_BASE
function MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, CommandMenuArguments )
local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) -- #MENU_COMMAND_BASE
-- When a menu function goes into error, DCS displays an obscure menu message.
-- This error handler catches the menu error and displays the full call stack.
local ErrorHandler = function( errmsg )
env.info( "MOOSE error in MENU COMMAND function: " .. errmsg )
if debug ~= nil then
env.info( debug.traceback() )
end
return errmsg
end
self:SetCommandMenuFunction( CommandMenuFunction )
self:SetCommandMenuArguments( CommandMenuArguments )
self.MenuCallHandler = function()
self.CommandMenuFunction( unpack( self.CommandMenuArguments ) )
local function MenuFunction()
return self.CommandMenuFunction( unpack( self.CommandMenuArguments ) )
end
local Status, Result = xpcall( MenuFunction, ErrorHandler )
end
return self

View File

@@ -53,6 +53,16 @@ MESSAGE = {
MessageID = 0,
}
--- Message Types
-- @type MESSAGE.Type
MESSAGE.Type = {
Update = "Update",
Information = "Information",
Briefing = "Briefing Report",
Overview = "Overview Report",
Detailed = "Detailed Report"
}
--- Creates a new MESSAGE object. Note that these MESSAGE objects are not yet displayed on the display panel. You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients.
-- @param self
@@ -74,6 +84,9 @@ function MESSAGE:New( MessageText, MessageDuration, MessageCategory )
local self = BASE:Inherit( self, BASE:New() )
self:F( { MessageText, MessageDuration, MessageCategory } )
self.MessageType = nil
-- When no MessageCategory is given, we don't show it as a title...
if MessageCategory and MessageCategory ~= "" then
if MessageCategory:sub(-1) ~= "\n" then
@@ -96,6 +109,37 @@ function MESSAGE:New( MessageText, MessageDuration, MessageCategory )
return self
end
--- Creates a new MESSAGE object of a certain type.
-- Note that these MESSAGE objects are not yet displayed on the display panel.
-- You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients.
-- The message display times are automatically defined based on the timing settings in the @{Settings} menu.
-- @param self
-- @param #string MessageText is the text of the Message.
-- @param #MESSAGE.Type MessageType The type of the message.
-- @return #MESSAGE
-- @usage
-- MessageAll = MESSAGE:NewType( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", MESSAGE.Type.Information )
-- MessageRED = MESSAGE:NewType( "To the RED Players: You receive a penalty because you've killed one of your own units", MESSAGE.Type.Information )
-- MessageClient1 = MESSAGE:NewType( "Congratulations, you've just hit a target", MESSAGE.Type.Update )
-- MessageClient2 = MESSAGE:NewType( "Congratulations, you've just killed a target", MESSAGE.Type.Update )
function MESSAGE:NewType( MessageText, MessageType )
local self = BASE:Inherit( self, BASE:New() )
self:F( { MessageText } )
self.MessageType = MessageType
self.MessageTime = timer.getTime()
self.MessageText = MessageText:gsub("^\n","",1):gsub("\n$","",1)
return self
end
--- Sends a MESSAGE to a Client Group. Note that the Group needs to be defined within the ME with the skillset "Client" or "Player".
-- @param #MESSAGE self
-- @param Wrapper.Client#CLIENT Client is the Group of the Client.
@@ -115,14 +159,22 @@ end
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" )
-- MessageClient1:ToClient( ClientGroup )
-- MessageClient2:ToClient( ClientGroup )
function MESSAGE:ToClient( Client )
function MESSAGE:ToClient( Client, Settings )
self:F( Client )
if Client and Client:GetClientGroupID() then
local ClientGroupID = Client:GetClientGroupID()
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration )
if self.MessageType then
local Settings = Settings or ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
self.MessageCategory = self.MessageType .. ": "
end
if self.MessageDuration ~= 0 then
local ClientGroupID = Client:GetClientGroupID()
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration )
end
end
return self
@@ -132,13 +184,21 @@ end
-- @param #MESSAGE self
-- @param Wrapper.Group#GROUP Group is the Group.
-- @return #MESSAGE
function MESSAGE:ToGroup( Group )
function MESSAGE:ToGroup( Group, Settings )
self:F( Group.GroupName )
if Group then
if self.MessageType then
local Settings = Settings or ( Group and _DATABASE:GetPlayerSettings( Group:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
self.MessageCategory = self.MessageType .. ": "
end
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration )
if self.MessageDuration ~= 0 then
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration )
end
end
return self
@@ -193,12 +253,20 @@ end
-- or
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
-- MessageRED:ToCoalition( coalition.side.RED )
function MESSAGE:ToCoalition( CoalitionSide )
function MESSAGE:ToCoalition( CoalitionSide, Settings )
self:F( CoalitionSide )
if self.MessageType then
local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
self.MessageCategory = self.MessageType .. ": "
end
if CoalitionSide then
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
trigger.action.outTextForCoalition( CoalitionSide, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration )
if self.MessageDuration ~= 0 then
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
trigger.action.outTextForCoalition( CoalitionSide, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration )
end
end
return self
@@ -232,8 +300,16 @@ end
function MESSAGE:ToAll()
self:F()
self:ToCoalition( coalition.side.RED )
self:ToCoalition( coalition.side.BLUE )
if self.MessageType then
local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
self.MessageCategory = self.MessageType .. ": "
end
if self.MessageDuration ~= 0 then
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
trigger.action.outText( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration )
end
return self
end
@@ -245,8 +321,7 @@ end
function MESSAGE:ToAllIf( Condition )
if Condition and Condition == true then
self:ToCoalition( coalition.side.RED )
self:ToCoalition( coalition.side.BLUE )
self:ToAll()
end
return self

View File

@@ -90,6 +90,18 @@ do -- COORDINATE
-- * @{#COORDINATE.IlluminationBomb}(): To illuminate the point.
--
--
-- ## Markings
--
-- Place markers (text boxes with clarifications for briefings, target locations or any other reference point) on the map for all players, coalitions or specific groups:
--
-- * @{#COORDINATE.MarkToAll}(): Place a mark to all players.
-- * @{#COORDINATE.MarkToCoalition}(): Place a mark to a coalition.
-- * @{#COORDINATE.MarkToCoalitionRed}(): Place a mark to the red coalition.
-- * @{#COORDINATE.MarkToCoalitionBlue}(): Place a mark to the blue coalition.
-- * @{#COORDINATE.MarkToGroup}(): Place a mark to a group (needs to have a client in it or a CA group (CA group is bugged)).
-- * @{#COORDINATE.RemoveMark}(): Removes a mark from the map.
--
--
-- ## 3D calculation methods
--
-- Various calculation methods exist to use or manipulate 3D space. Find below a short description of each method:
@@ -289,9 +301,45 @@ do -- COORDINATE
end
--- Set the heading of the coordinate, if applicable.
-- @param #COORDINATE self
function COORDINATE:SetHeading( Heading )
self.Heading = Heading
end
--- Get the heading of the coordinate, if applicable.
-- @param #COORDINATE self
-- @return #number or nil
function COORDINATE:GetHeading()
return self.Heading
end
--- Set the velocity of the COORDINATE.
-- @param #COORDINATE self
-- @param #string Velocity Velocity in meters per second.
function COORDINATE:SetVelocity( Velocity )
self.Velocity = Velocity
end
--- Return the velocity of the COORDINATE.
-- @param #COORDINATE self
-- @return #number Velocity in meters per second.
function COORDINATE:GetVelocity()
local Velocity = self.Velocity
return Velocity or 0
end
--- Return velocity text of the COORDINATE.
-- @param #COORDINATE self
-- @return #string
function COORDINATE:GetMovingText( Settings )
return self:GetVelocityText( Settings ) .. ", " .. self:GetHeadingText( Settings )
end
--- Return a direction vector Vec3 from COORDINATE to the COORDINATE.
@@ -302,6 +350,7 @@ do -- COORDINATE
return { x = TargetCoordinate.x - self.x, y = TargetCoordinate.y - self.y, z = TargetCoordinate.z - self.z }
end
--- Get a correction in radians of the real magnetic north of the COORDINATE.
-- @param #COORDINATE self
-- @return #number CorrectionRadians The correction in radians.
@@ -347,6 +396,7 @@ do -- COORDINATE
return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5
end
--- Return the 3D distance in meters between the target COORDINATE and the COORDINATE.
-- @param #COORDINATE self
-- @param #COORDINATE TargetCoordinate The target COORDINATE.
@@ -413,6 +463,38 @@ do -- COORDINATE
end
--- Return the velocity text of the COORDINATE.
-- @param #COORDINATE self
-- @return #string Velocity text.
function COORDINATE:GetVelocityText( Settings )
local Velocity = self:GetVelocity()
local Settings = Settings or _SETTINGS
if Velocity then
if Settings:IsMetric() then
return string.format( " moving at %d km/h", UTILS.MpsToKmph( Velocity ) )
else
return string.format( " moving at %d mi/h", UTILS.MpsToKmph( Velocity ) / 1.852 )
end
else
return " stationary"
end
end
--- Return the heading text of the COORDINATE.
-- @param #COORDINATE self
-- @return #string Heading text.
function COORDINATE:GetHeadingText( Settings )
local Heading = self:GetHeading()
if Heading then
return string.format( " bearing %3d°", Heading )
else
return " bearing unknown"
end
end
--- Provides a Bearing / Range string
-- @param #COORDINATE self
-- @param #number AngleRadians The angle in randians
@@ -650,6 +732,88 @@ do -- COORDINATE
self:F2( Azimuth )
self:Flare( FLARECOLOR.Red, Azimuth )
end
do -- Markings
--- Mark to All
-- @param #COORDINATE self
-- @param #string MarkText Free format text that shows the marking clarification.
-- @return #number The resulting Mark ID which is a number.
-- @usage
-- local TargetCoord = TargetGroup:GetCoordinate()
-- local MarkID = TargetCoord:MarkToAll( "This is a target for all players" )
function COORDINATE:MarkToAll( MarkText )
local MarkID = UTILS.GetMarkID()
trigger.action.markToAll( MarkID, MarkText, self:GetVec3() )
return MarkID
end
--- Mark to Coalition
-- @param #COORDINATE self
-- @param #string MarkText Free format text that shows the marking clarification.
-- @param Coalition
-- @return #number The resulting Mark ID which is a number.
-- @usage
-- local TargetCoord = TargetGroup:GetCoordinate()
-- local MarkID = TargetCoord:MarkToCoalition( "This is a target for the red coalition", coalition.side.RED )
function COORDINATE:MarkToCoalition( MarkText, Coalition )
local MarkID = UTILS.GetMarkID()
trigger.action.markToCoalition( MarkID, MarkText, self:GetVec3(), Coalition )
return MarkID
end
--- Mark to Red Coalition
-- @param #COORDINATE self
-- @param #string MarkText Free format text that shows the marking clarification.
-- @return #number The resulting Mark ID which is a number.
-- @usage
-- local TargetCoord = TargetGroup:GetCoordinate()
-- local MarkID = TargetCoord:MarkToCoalitionRed( "This is a target for the red coalition" )
function COORDINATE:MarkToCoalitionRed( MarkText )
return self:MarkToCoalition( MarkText, coalition.side.RED )
end
--- Mark to Blue Coalition
-- @param #COORDINATE self
-- @param #string MarkText Free format text that shows the marking clarification.
-- @return #number The resulting Mark ID which is a number.
-- @usage
-- local TargetCoord = TargetGroup:GetCoordinate()
-- local MarkID = TargetCoord:MarkToCoalitionBlue( "This is a target for the blue coalition" )
function COORDINATE:MarkToCoalitionBlue( MarkText )
return self:MarkToCoalition( MarkText, coalition.side.BLUE )
end
--- Mark to Group
-- @param #COORDINATE self
-- @param #string MarkText Free format text that shows the marking clarification.
-- @param Wrapper.Group#GROUP MarkGroup The @{Group} that receives the mark.
-- @return #number The resulting Mark ID which is a number.
-- @usage
-- local TargetCoord = TargetGroup:GetCoordinate()
-- local MarkGroup = GROUP:FindByName( "AttackGroup" )
-- local MarkID = TargetCoord:MarkToGroup( "This is a target for the attack group", AttackGroup )
function COORDINATE:MarkToGroup( MarkText, MarkGroup )
local MarkID = UTILS.GetMarkID()
trigger.action.markToGroup( MarkID, MarkText, self:GetVec3(), MarkGroup:GetID() )
return MarkID
end
--- Remove a mark
-- @param #COORDINATE self
-- @param #number MarkID The ID of the mark to be removed.
-- @usage
-- local TargetCoord = TargetGroup:GetCoordinate()
-- local MarkGroup = GROUP:FindByName( "AttackGroup" )
-- local MarkID = TargetCoord:MarkToGroup( "This is a target for the attack group", AttackGroup )
-- <<< logic >>>
-- RemoveMark( MarkID ) -- The mark is now removed
function COORDINATE:RemoveMark( MarkID )
trigger.action.removeMark( MarkID )
end
end -- Markings
--- Returns if a Coordinate has Line of Sight (LOS) with the ToCoordinate.
-- @param #COORDINATE self
@@ -670,6 +834,39 @@ do -- COORDINATE
end
--- Returns if a Coordinate is in a certain Radius of this Coordinate in 2D plane using the X and Z axis.
-- @param #COORDINATE self
-- @param #COORDINATE ToCoordinate The coordinate that will be tested if it is in the radius of this coordinate.
-- @param #number Radius The radius of the circle on the 2D plane around this coordinate.
-- @return #boolean true if in the Radius.
function COORDINATE:IsInRadius( Coordinate, Radius )
local InVec2 = self:GetVec2()
local Vec2 = Coordinate:GetVec2()
local InRadius = UTILS.IsInRadius( InVec2, Vec2, Radius)
return InRadius
end
--- Returns if a Coordinate is in a certain radius of this Coordinate in 3D space using the X, Y and Z axis.
-- So Radius defines the radius of the a Sphere in 3D space around this coordinate.
-- @param #COORDINATE self
-- @param #COORDINATE ToCoordinate The coordinate that will be tested if it is in the radius of this coordinate.
-- @param #number Radius The radius of the sphere in the 3D space around this coordinate.
-- @return #boolean true if in the Sphere.
function COORDINATE:IsInSphere( Coordinate, Radius )
local InVec3 = self:GetVec3()
local Vec3 = Coordinate:GetVec3()
local InSphere = UTILS.IsInSphere( InVec3, Vec3, Radius)
return InSphere
end
--- Return a BR string from a COORDINATE to the COORDINATE.
-- @param #COORDINATE self
-- @param #COORDINATE TargetCoordinate The target COORDINATE.
@@ -798,6 +995,78 @@ do -- COORDINATE
end
--- Provides a coordinate string of the point, based on the A2G coordinate format system.
-- @param #COORDINATE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable
-- @param Core.Settings#SETTINGS Settings
-- @return #string The coordinate Text in the configured coordinate system.
function COORDINATE:ToStringA2G( Controllable, Settings ) -- R2.2
self:F( { Controllable = Controllable and Controllable:GetName() } )
local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS
if Settings:IsA2G_BR() then
-- If no Controllable is given to calculate the BR from, then MGRS will be used!!!
if Controllable then
local Coordinate = Controllable:GetCoordinate()
return Controllable and self:ToStringBR( Coordinate, Settings ) or self:ToStringMGRS( Settings )
else
return self:ToStringMGRS( Settings )
end
end
if Settings:IsA2G_LL_DMS() then
return self:ToStringLLDMS( Settings )
end
if Settings:IsA2G_LL_DDM() then
return self:ToStringLLDDM( Settings )
end
if Settings:IsA2G_MGRS() then
return self:ToStringMGRS( Settings )
end
return nil
end
--- Provides a coordinate string of the point, based on the A2A coordinate format system.
-- @param #COORDINATE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable
-- @param Core.Settings#SETTINGS Settings
-- @return #string The coordinate Text in the configured coordinate system.
function COORDINATE:ToStringA2A( Controllable, Settings ) -- R2.2
self:F( { Controllable = Controllable and Controllable:GetName() } )
local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS
if Settings:IsA2A_BRAA() then
if Controllable then
local Coordinate = Controllable:GetCoordinate()
return self:ToStringBRA( Coordinate, Settings )
else
return self:ToStringMGRS( Settings )
end
end
if Settings:IsA2A_BULLS() then
local Coalition = Controllable:GetCoalition()
return self:ToStringBULLS( Coalition, Settings )
end
if Settings:IsA2A_LL_DMS() then
return self:ToStringLLDMS( Settings )
end
if Settings:IsA2A_LL_DDM() then
return self:ToStringLLDDM( Settings )
end
if Settings:IsA2A_MGRS() then
return self:ToStringMGRS( Settings )
end
return nil
end
--- Provides a coordinate string of the point, based on a coordinate format system:
-- * Uses default settings in COORDINATE.
-- * Can be overridden if for a GROUP containing x clients, a menu was selected to override the default.
@@ -836,47 +1105,10 @@ do -- COORDINATE
end
if ModeA2A then
if Settings:IsA2A_BRAA() then
if Controllable then
local Coordinate = Controllable:GetCoordinate()
return self:ToStringBRA( Coordinate, Settings )
else
return self:ToStringMGRS( Settings )
end
end
if Settings:IsA2A_BULLS() then
local Coalition = Controllable:GetCoalition()
return self:ToStringBULLS( Coalition, Settings )
end
if Settings:IsA2A_LL_DMS() then
return self:ToStringLLDMS( Settings )
end
if Settings:IsA2A_LL_DDM() then
return self:ToStringLLDDM( Settings )
end
if Settings:IsA2A_MGRS() then
return self:ToStringMGRS( Settings )
end
if ModeA2A == true then
return self:ToStringA2A( Controllable, Settings )
else
if Settings:IsA2G_BR() then
-- If no Controllable is given to calculate the BR from, then MGRS will be used!!!
if Controllable then
local Coordinate = Controllable:GetCoordinate()
return Controllable and self:ToStringBR( Coordinate, Settings ) or self:ToStringMGRS( Settings )
else
return self:ToStringMGRS( Settings )
end
end
if Settings:IsA2G_LL_DMS() then
return self:ToStringLLDMS( Settings )
end
if Settings:IsA2G_LL_DDM() then
return self:ToStringLLDDM( Settings )
end
if Settings:IsA2G_MGRS() then
return self:ToStringMGRS( Settings )
end
return self:ToStringA2G( Controllable, Settings )
end
return nil

View File

@@ -217,7 +217,6 @@ function SET_BASE:Count()
end
--- Copies the Filter criteria from a given Set (for rebuilding a new Set based on an existing Set).
-- @param #SET_BASE self
-- @param #SET_BASE BaseSet
@@ -1788,19 +1787,131 @@ end
function SET_UNIT:CalculateThreatLevelA2G()
local MaxThreatLevelA2G = 0
local MaxThreatText = ""
for UnitName, UnitData in pairs( self:GetSet() ) do
local ThreatUnit = UnitData -- Wrapper.Unit#UNIT
local ThreatLevelA2G = ThreatUnit:GetThreatLevel()
local ThreatLevelA2G, ThreatText = ThreatUnit:GetThreatLevel()
if ThreatLevelA2G > MaxThreatLevelA2G then
MaxThreatLevelA2G = ThreatLevelA2G
MaxThreatText = ThreatText
end
end
self:T3( MaxThreatLevelA2G )
return MaxThreatLevelA2G
self:F( { MaxThreatLevelA2G = MaxThreatLevelA2G, MaxThreatText = MaxThreatText } )
return MaxThreatLevelA2G, MaxThreatText
end
--- Get the center coordinate of the SET_UNIT.
-- @param #SET_UNIT self
-- @return Core.Point#COORDINATE The center coordinate of all the units in the set, including heading in degrees and speed in mps in case of moving units.
function SET_UNIT:GetCoordinate()
local Coordinate = self:GetFirst():GetCoordinate()
local x1 = Coordinate.x
local x2 = Coordinate.x
local y1 = Coordinate.y
local y2 = Coordinate.y
local z1 = Coordinate.z
local z2 = Coordinate.z
local MaxVelocity = 0
local AvgHeading = nil
local MovingCount = 0
for UnitName, UnitData in pairs( self:GetSet() ) do
local Unit = UnitData -- Wrapper.Unit#UNIT
local Coordinate = Unit:GetCoordinate()
x1 = ( Coordinate.x < x1 ) and Coordinate.x or x1
x2 = ( Coordinate.x > x2 ) and Coordinate.x or x2
y1 = ( Coordinate.y < y1 ) and Coordinate.y or y1
y2 = ( Coordinate.y > y2 ) and Coordinate.y or y2
z1 = ( Coordinate.y < z1 ) and Coordinate.z or z1
z2 = ( Coordinate.y > z2 ) and Coordinate.z or z2
local Velocity = Coordinate:GetVelocity()
if Velocity ~= 0 then
MaxVelocity = ( MaxVelocity < Velocity ) and Velocity or MaxVelocity
local Heading = Coordinate:GetHeading()
AvgHeading = AvgHeading and ( AvgHeading + Heading ) or Heading
MovingCount = MovingCount + 1
end
end
AvgHeading = AvgHeading and ( AvgHeading / MovingCount )
Coordinate.x = ( x2 - x1 ) / 2 + x1
Coordinate.y = ( y2 - y1 ) / 2 + y1
Coordinate.z = ( z2 - z1 ) / 2 + z1
Coordinate:SetHeading( AvgHeading )
Coordinate:SetVelocity( MaxVelocity )
self:F( { Coordinate = Coordinate } )
return Coordinate
end
--- Get the maximum velocity of the SET_UNIT.
-- @param #SET_UNIT self
-- @return #number The speed in mps in case of moving units.
function SET_UNIT:GetVelocity()
local Coordinate = self:GetFirst():GetCoordinate()
local MaxVelocity = 0
for UnitName, UnitData in pairs( self:GetSet() ) do
local Unit = UnitData -- Wrapper.Unit#UNIT
local Coordinate = Unit:GetCoordinate()
local Velocity = Coordinate:GetVelocity()
if Velocity ~= 0 then
MaxVelocity = ( MaxVelocity < Velocity ) and Velocity or MaxVelocity
end
end
self:F( { MaxVelocity = MaxVelocity } )
return MaxVelocity
end
--- Get the average heading of the SET_UNIT.
-- @param #SET_UNIT self
-- @return #number Heading Heading in degrees and speed in mps in case of moving units.
function SET_UNIT:GetHeading()
local HeadingSet = nil
local MovingCount = 0
for UnitName, UnitData in pairs( self:GetSet() ) do
local Unit = UnitData -- Wrapper.Unit#UNIT
local Coordinate = Unit:GetCoordinate()
local Velocity = Coordinate:GetVelocity()
if Velocity ~= 0 then
local Heading = Coordinate:GetHeading()
if HeadingSet == nil then
HeadingSet = Heading
else
local HeadingDiff = ( HeadingSet - Heading + 180 + 360 ) % 360 - 180
HeadingDiff = math.abs( HeadingDiff )
if HeadingDiff > 5 then
HeadingSet = nil
break
end
end
end
end
return HeadingSet
end
--- Returns if the @{Set} has targets having a radar (of a given type).
-- @param #SET_UNIT self

View File

@@ -59,6 +59,11 @@ do -- SETTINGS
self:SetA2A_BRAA() -- Defaults
self:SetLL_Accuracy( 3 ) -- Defaults
self:SetMGRS_Accuracy( 5 ) -- Defaults
self:SetMessageTime( MESSAGE.Type.Briefing, 180 )
self:SetMessageTime( MESSAGE.Type.Detailed, 60 )
self:SetMessageTime( MESSAGE.Type.Information, 30 )
self:SetMessageTime( MESSAGE.Type.Overview, 60 )
self:SetMessageTime( MESSAGE.Type.Update, 15 )
return self
else
local Settings = _DATABASE:GetPlayerSettings( PlayerName )
@@ -127,8 +132,23 @@ do -- SETTINGS
return self.MGRS_Accuracy or _SETTINGS:GetMGRS_Accuracy()
end
--- Sets the SETTINGS Message Display Timing of a MessageType
-- @param #SETTINGS self
-- @param Core.Message#MESSAGE MessageType The type of the message.
-- @param #number MessageTime The display time duration in seconds of the MessageType.
function SETTINGS:SetMessageTime( MessageType, MessageTime )
self.MessageTypeTimings = self.MessageTypeTimings or {}
self.MessageTypeTimings[MessageType] = MessageTime
end
--- Gets the SETTINGS Message Display Timing of a MessageType
-- @param #SETTINGS self
-- @param Core.Message#MESSAGE MessageType The type of the message.
-- @return #number
function SETTINGS:GetMessageTime( MessageType )
return ( self.MessageTypeTimings and self.MessageTypeTimings[MessageType] ) or _SETTINGS:GetMessageTime( MessageType )
end
--- Sets A2G LL DMS
-- @param #SETTINGS self
@@ -345,6 +365,46 @@ do -- SETTINGS
if self:IsImperial() then
MENU_GROUP_COMMAND:New( MenuGroup, "Metric (Kilometers,Meters)", MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, true ):SetTime( MenuTime )
end
local MessagesMenu = MENU_GROUP:New( MenuGroup, "Messages and Reports", SettingsMenu ):SetTime( MenuTime )
local UpdateMessagesMenu = MENU_GROUP:New( MenuGroup, "Update Messages", MessagesMenu ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "Off", UpdateMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Update, 0 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "5 seconds", UpdateMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Update, 5 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "10 seconds", UpdateMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Update, 10 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "15 seconds", UpdateMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Update, 15 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "30 seconds", UpdateMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Update, 30 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "1 minute", UpdateMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Update, 60 ):SetTime( MenuTime )
local InformationMessagesMenu = MENU_GROUP:New( MenuGroup, "Information Messages", MessagesMenu ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "5 seconds", InformationMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Information, 5 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "10 seconds", InformationMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Information, 10 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "15 seconds", InformationMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Information, 15 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "30 seconds", InformationMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Information, 30 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "1 minute", InformationMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Information, 60 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "2 minutes", InformationMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Information, 120 ):SetTime( MenuTime )
local BriefingReportsMenu = MENU_GROUP:New( MenuGroup, "Briefing Reports", MessagesMenu ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "15 seconds", BriefingReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Briefing, 15 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "30 seconds", BriefingReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Briefing, 30 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "1 minute", BriefingReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Briefing, 60 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "2 minutes", BriefingReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Briefing, 120 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "3 minutes", BriefingReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Briefing, 180 ):SetTime( MenuTime )
local OverviewReportsMenu = MENU_GROUP:New( MenuGroup, "Overview Reports", MessagesMenu ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "15 seconds", OverviewReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Overview, 15 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "30 seconds", OverviewReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Overview, 30 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "1 minute", OverviewReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Overview, 60 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "2 minutes", OverviewReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Overview, 120 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "3 minutes", OverviewReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Overview, 180 ):SetTime( MenuTime )
local DetailedReportsMenu = MENU_GROUP:New( MenuGroup, "Detailed Reports", MessagesMenu ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "15 seconds", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 15 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "30 seconds", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 30 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "1 minute", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 60 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "2 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 120 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "3 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 180 ):SetTime( MenuTime )
SettingsMenu:Remove( MenuTime )
@@ -444,6 +504,47 @@ do -- SETTINGS
if self:IsImperial() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Metric (Kilometers,Meters)", MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, true )
end
local MessagesMenu = MENU_GROUP:New( PlayerGroup, "Messages and Reports", PlayerMenu )
local UpdateMessagesMenu = MENU_GROUP:New( PlayerGroup, "Update Messages", MessagesMenu )
MENU_GROUP_COMMAND:New( PlayerGroup, "Off", UpdateMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Update, 0 )
MENU_GROUP_COMMAND:New( PlayerGroup, "5 seconds", UpdateMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Update, 5 )
MENU_GROUP_COMMAND:New( PlayerGroup, "10 seconds", UpdateMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Update, 10 )
MENU_GROUP_COMMAND:New( PlayerGroup, "15 seconds", UpdateMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Update, 15 )
MENU_GROUP_COMMAND:New( PlayerGroup, "30 seconds", UpdateMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Update, 30 )
MENU_GROUP_COMMAND:New( PlayerGroup, "1 minute", UpdateMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Update, 60 )
local InformationMessagesMenu = MENU_GROUP:New( PlayerGroup, "Information Messages", MessagesMenu )
MENU_GROUP_COMMAND:New( PlayerGroup, "5 seconds", InformationMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Information, 5 )
MENU_GROUP_COMMAND:New( PlayerGroup, "10 seconds", InformationMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Information, 10 )
MENU_GROUP_COMMAND:New( PlayerGroup, "15 seconds", InformationMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Information, 15 )
MENU_GROUP_COMMAND:New( PlayerGroup, "30 seconds", InformationMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Information, 30 )
MENU_GROUP_COMMAND:New( PlayerGroup, "1 minute", InformationMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Information, 60 )
MENU_GROUP_COMMAND:New( PlayerGroup, "2 minutes", InformationMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Information, 120 )
local BriefingReportsMenu = MENU_GROUP:New( PlayerGroup, "Briefing Reports", MessagesMenu )
MENU_GROUP_COMMAND:New( PlayerGroup, "15 seconds", BriefingReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Briefing, 15 )
MENU_GROUP_COMMAND:New( PlayerGroup, "30 seconds", BriefingReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Briefing, 30 )
MENU_GROUP_COMMAND:New( PlayerGroup, "1 minute", BriefingReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Briefing, 60 )
MENU_GROUP_COMMAND:New( PlayerGroup, "2 minutes", BriefingReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Briefing, 120 )
MENU_GROUP_COMMAND:New( PlayerGroup, "3 minutes", BriefingReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Briefing, 180 )
local OverviewReportsMenu = MENU_GROUP:New( PlayerGroup, "Overview Reports", MessagesMenu )
MENU_GROUP_COMMAND:New( PlayerGroup, "15 seconds", OverviewReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Overview, 15 )
MENU_GROUP_COMMAND:New( PlayerGroup, "30 seconds", OverviewReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Overview, 30 )
MENU_GROUP_COMMAND:New( PlayerGroup, "1 minute", OverviewReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Overview, 60 )
MENU_GROUP_COMMAND:New( PlayerGroup, "2 minutes", OverviewReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Overview, 120 )
MENU_GROUP_COMMAND:New( PlayerGroup, "3 minutes", OverviewReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Overview, 180 )
local DetailedReportsMenu = MENU_GROUP:New( PlayerGroup, "Detailed Reports", MessagesMenu )
MENU_GROUP_COMMAND:New( PlayerGroup, "15 seconds", DetailedReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.DetailedReportsMenu, 15 )
MENU_GROUP_COMMAND:New( PlayerGroup, "30 seconds", DetailedReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.DetailedReportsMenu, 30 )
MENU_GROUP_COMMAND:New( PlayerGroup, "1 minute", DetailedReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.DetailedReportsMenu, 60 )
MENU_GROUP_COMMAND:New( PlayerGroup, "2 minutes", DetailedReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.DetailedReportsMenu, 120 )
MENU_GROUP_COMMAND:New( PlayerGroup, "3 minutes", DetailedReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.DetailedReportsMenu, 180 )
return self
end
@@ -465,44 +566,50 @@ do -- SETTINGS
--- @param #SETTINGS self
function SETTINGS:A2GMenuSystem( MenuGroup, RootMenu, A2GSystem )
self.A2GSystem = A2GSystem
MESSAGE:New( string.format("Settings: Default A2G coordinate system set to %s for all players!.", A2GSystem ), 5 ):ToAll()
MESSAGE:New( string.format("Settings: Default A2G coordinate system set to %s for all players!", A2GSystem ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu )
end
--- @param #SETTINGS self
function SETTINGS:A2AMenuSystem( MenuGroup, RootMenu, A2ASystem )
self.A2ASystem = A2ASystem
MESSAGE:New( string.format("Settings: Default A2A coordinate system set to %s for all players!.", A2ASystem ), 5 ):ToAll()
MESSAGE:New( string.format("Settings: Default A2A coordinate system set to %s for all players!", A2ASystem ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu )
end
--- @param #SETTINGS self
function SETTINGS:MenuLL_DDM_Accuracy( MenuGroup, RootMenu, LL_Accuracy )
self.LL_Accuracy = LL_Accuracy
MESSAGE:New( string.format("Settings: Default LL accuracy set to %s for all players!.", LL_Accuracy ), 5 ):ToAll()
MESSAGE:New( string.format("Settings: Default LL accuracy set to %s for all players!", LL_Accuracy ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu )
end
--- @param #SETTINGS self
function SETTINGS:MenuMGRS_Accuracy( MenuGroup, RootMenu, MGRS_Accuracy )
self.MGRS_Accuracy = MGRS_Accuracy
MESSAGE:New( string.format("Settings: Default MGRS accuracy set to %s for all players!.", MGRS_Accuracy ), 5 ):ToAll()
MESSAGE:New( string.format("Settings: Default MGRS accuracy set to %s for all players!", MGRS_Accuracy ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu )
end
--- @param #SETTINGS self
function SETTINGS:MenuMWSystem( MenuGroup, RootMenu, MW )
self.Metric = MW
MESSAGE:New( string.format("Settings: Default measurement format set to %s for all players!.", MW and "Metric" or "Imperial" ), 5 ):ToAll()
MESSAGE:New( string.format("Settings: Default measurement format set to %s for all players!", MW and "Metric" or "Imperial" ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu )
end
--- @param #SETTINGS self
function SETTINGS:MenuMessageTimingsSystem( MenuGroup, RootMenu, MessageType, MessageTime )
self:SetMessageTime( MessageType, MessageTime )
MESSAGE:New( string.format( "Settings: Default message time set for %s to %d.", MessageType, MessageTime ), 5 ):ToAll()
end
do
--- @param #SETTINGS self
function SETTINGS:MenuGroupA2GSystem( PlayerUnit, PlayerGroup, PlayerName, A2GSystem )
BASE:E( {self, PlayerUnit:GetName(), A2GSystem} )
self.A2GSystem = A2GSystem
MESSAGE:New( string.format("Settings: A2G format set to %s for player %s.", A2GSystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
MESSAGE:New( string.format( "Settings: A2G format set to %s for player %s.", A2GSystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
@@ -510,7 +617,7 @@ do -- SETTINGS
--- @param #SETTINGS self
function SETTINGS:MenuGroupA2ASystem( PlayerUnit, PlayerGroup, PlayerName, A2ASystem )
self.A2ASystem = A2ASystem
MESSAGE:New( string.format("Settings: A2A format set to %s for player %s.", A2ASystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
MESSAGE:New( string.format( "Settings: A2A format set to %s for player %s.", A2ASystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
@@ -518,7 +625,7 @@ do -- SETTINGS
--- @param #SETTINGS self
function SETTINGS:MenuGroupLL_DDM_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, LL_Accuracy )
self.LL_Accuracy = LL_Accuracy
MESSAGE:New( string.format("Settings: A2G LL format accuracy set to %d for player %s.", LL_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup )
MESSAGE:New( string.format( "Settings: A2G LL format accuracy set to %d for player %s.", LL_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup )
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
@@ -526,7 +633,7 @@ do -- SETTINGS
--- @param #SETTINGS self
function SETTINGS:MenuGroupMGRS_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, MGRS_Accuracy )
self.MGRS_Accuracy = MGRS_Accuracy
MESSAGE:New( string.format("Settings: A2G MGRS format accuracy set to %d for player %s.", MGRS_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup )
MESSAGE:New( string.format( "Settings: A2G MGRS format accuracy set to %d for player %s.", MGRS_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup )
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
@@ -534,10 +641,16 @@ do -- SETTINGS
--- @param #SETTINGS self
function SETTINGS:MenuGroupMWSystem( PlayerUnit, PlayerGroup, PlayerName, MW )
self.Metric = MW
MESSAGE:New( string.format("Settings: Measurement format set to %s for player %s.", MW and "Metric" or "Imperial", PlayerName ), 5 ):ToGroup( PlayerGroup )
MESSAGE:New( string.format( "Settings: Measurement format set to %s for player %s.", MW and "Metric" or "Imperial", PlayerName ), 5 ):ToGroup( PlayerGroup )
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
--- @param #SETTINGS self
function SETTINGS:MenuGroupMessageTimingsSystem( PlayerUnit, PlayerGroup, PlayerName, MessageType, MessageTime )
self:SetMessageTime( MessageType, MessageTime )
MESSAGE:New( string.format( "Settings: Default message time set for %s to %d.", MessageType, MessageTime ), 5 ):ToGroup( PlayerGroup )
end
end

View File

@@ -645,6 +645,22 @@ function ZONE_RADIUS:GetRandomPointVec3( inner, outer )
end
--- Returns a @{Point#COORDINATE} object reflecting a random 3D location within the zone.
-- @param #ZONE_RADIUS self
-- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0.
-- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.
-- @return Core.Point#COORDINATE
function ZONE_RADIUS:GetRandomCoordinate( inner, outer )
self:F( self.ZoneName, inner, outer )
local Coordinate = COORDINATE:NewFromVec2( self:GetRandomVec2() )
self:T3( { Coordinate = Coordinate } )
return Coordinate
end
--- @type ZONE
-- @extends #ZONE_RADIUS
@@ -1091,6 +1107,20 @@ function ZONE_POLYGON_BASE:GetRandomPointVec3()
end
--- Return a @{Point#COORDINATE} object representing a random 3D point at landheight within the zone.
-- @param #ZONE_POLYGON_BASE self
-- @return Core.Point#COORDINATE
function ZONE_POLYGON_BASE:GetRandomCoordinate()
self:F2()
local Coordinate = COORDINATE:NewFromVec2( self:GetRandomVec2() )
self:T2( Coordinate )
return Coordinate
end
--- Get the bounding square the zone.
-- @param #ZONE_POLYGON_BASE self
-- @return #ZONE_POLYGON_BASE.BoundingSquare The bounding square.

View File

@@ -181,8 +181,21 @@ function AIRBASEPOLICE_BASE:_AirbaseMonitor()
Client:Message( "You are speeding on the taxiway! Slow down or you will be removed from this airbase! Your current velocity is " .. string.format( "%2.0f km/h", Velocity ), 5, "Warning " .. SpeedingWarnings .. " / 3" )
Client:SetState( self, "Warnings", SpeedingWarnings + 1 )
else
MESSAGE:New( "Player " .. Client:GetPlayerName() .. " has been removed from the airbase, due to a speeding violation ...", 10, "Airbase Police" ):ToAll()
Client:Destroy()
MESSAGE:New( "Player " .. Client:GetPlayerName() .. " is being damaged at the airbase, due to a speeding violation ...", 10, "Airbase Police" ):ToAll()
--- @param Wrapper.Client#CLIENT Client
local function DestroyUntilHeavilyDamaged( Client )
local ClientCoord = Client:GetCoordinate()
ClientCoord:Explosion( 100 )
local Damage = Client:GetLife()
local InitialLife = Client:GetLife0()
MESSAGE:New( "Player " .. Client:GetPlayerName() .. " Damage ... " .. Damage, 5, "Airbase Police" ):ToAll()
if ( Damage / InitialLife ) * 100 < 80 then
Client:ScheduleStop( DestroyUntilHeavilyDamaged )
end
end
Client:ScheduleOnce( 1, DestroyUntilHeavilyDamaged, Client )
--Client:ScheduleRepeat( 1, 1, 0, nil, DestroyUntilHeavilyDamaged, Client )
--Client:Destroy()
trigger.action.setUserFlag( "AIRCRAFT_"..Client:GetID(), 100)
Client:SetState( self, "Speeding", false )
Client:SetState( self, "Warnings", 0 )

View File

@@ -853,7 +853,7 @@ do -- DESIGNATE
local Coord = self.Detection:GetDetectedItemCoordinate( DesignateIndex )
local ID = self.Detection:GetDetectedItemID( DesignateIndex )
local MenuText = ID .. ", " .. Coord:ToString( AttackGroup )
local MenuText = ID .. ", " .. Coord:ToStringA2G( AttackGroup )
if Designating == "" then
MenuText = "(-) " .. MenuText
@@ -862,7 +862,7 @@ do -- DESIGNATE
for LaserCode, MenuText in pairs( self.MenuLaserCodes ) do
MENU_GROUP_COMMAND:New( AttackGroup, string.format( MenuText, LaserCode ), DetectedMenu, self.MenuLaseCode, self, DesignateIndex, 60, LaserCode ):SetTime( MenuTime ):SetTag( self.DesignateName )
end
MENU_GROUP_COMMAND:New( AttackGroup, "Lase targets", DetectedMenu, self.MenuLaseOn, self, DesignateIndex, 60 ):SetTime( MenuTime ):SetTag( self.DesignateName )
MENU_GROUP_COMMAND:New( AttackGroup, "Lase with random laser code(s)", DetectedMenu, self.MenuLaseOn, self, DesignateIndex, 60 ):SetTime( MenuTime ):SetTag( self.DesignateName )
MENU_GROUP_COMMAND:New( AttackGroup, "Smoke red", DetectedMenu, self.MenuSmoke, self, DesignateIndex, SMOKECOLOR.Red ):SetTime( MenuTime ):SetTag( self.DesignateName )
MENU_GROUP_COMMAND:New( AttackGroup, "Smoke blue", DetectedMenu, self.MenuSmoke, self, DesignateIndex, SMOKECOLOR.Blue ):SetTime( MenuTime ):SetTag( self.DesignateName )
MENU_GROUP_COMMAND:New( AttackGroup, "Smoke green", DetectedMenu, self.MenuSmoke, self, DesignateIndex, SMOKECOLOR.Green ):SetTime( MenuTime ):SetTag( self.DesignateName )

View File

@@ -1239,9 +1239,11 @@ do -- DETECTION_BASE
DetectedItem.FriendliesNearBy = nil
if DetectedUnit then
-- We need to ensure that the DetectedUnit is alive!
if DetectedUnit and DetectedUnit:IsAlive() then
local InterceptCoord = ReportGroupData.InterceptCoord or DetectedUnit:GetCoordinate()
local DetectedUnitCoord = DetectedUnit:GetCoordinate()
local InterceptCoord = ReportGroupData.InterceptCoord or DetectedUnitCoord
local SphereSearch = {
id = world.VolumeType.SPHERE,
@@ -1336,9 +1338,7 @@ do -- DETECTION_BASE
DetectedItem.FriendliesNearBy = DetectedItem.FriendliesNearBy or {}
DetectedItem.FriendliesNearBy[PlayerUnitName] = PlayerUnit
local CenterCoord = DetectedUnit:GetCoordinate()
local Distance = CenterCoord:Get2DDistance( PlayerUnit:GetCoordinate() )
local Distance = DetectedUnitCoord:Get2DDistance( PlayerUnit:GetCoordinate() )
DetectedItem.FriendliesDistance = DetectedItem.FriendliesDistance or {}
DetectedItem.FriendliesDistance[Distance] = PlayerUnit
@@ -1436,6 +1436,8 @@ do -- DETECTION_BASE
else
return "Unknown"
end
else
return "Unknown"
end
else
return "Dead:" .. DetectedUnit:GetName()
@@ -1644,7 +1646,7 @@ do -- DETECTION_BASE
DetectedItem.Coordinate = Coordinate
DetectedItem.Coordinate:SetHeading( DetectedItemUnit:GetHeading() )
DetectedItem.Coordinate.y = DetectedItemUnit:GetAltitude()
DetectedItem.Coordinate.Speed = DetectedItemUnit:GetVelocityMPS()
DetectedItem.Coordinate:SetVelocity( DetectedItemUnit:GetVelocityMPS() )
end
end
end
@@ -1675,7 +1677,7 @@ do -- DETECTION_BASE
local DetectedSet = DetectedItem.Set
if DetectedItem then
DetectedItem.ThreatLevel = DetectedSet:CalculateThreatLevelA2G()
DetectedItem.ThreatLevel, DetectedItem.ThreatText = DetectedSet:CalculateThreatLevelA2G()
end
end
@@ -1691,10 +1693,10 @@ do -- DETECTION_BASE
local DetectedItem = self:GetDetectedItem( Index )
if DetectedItem then
return DetectedItem.ThreatLevel or 0
return DetectedItem.ThreatLevel or 0, DetectedItem.ThreatText or ""
end
return nil
return nil, ""
end
@@ -2423,9 +2425,9 @@ do -- DETECTION_AREAS
-- @param #DETECTION_BASE.DetectedItem DetectedItem
function DETECTION_AREAS:CalculateIntercept( DetectedItem )
local DetectedSpeed = DetectedItem.Coordinate.Speed
local DetectedHeading = DetectedItem.Coordinate.Heading
local DetectedCoord = DetectedItem.Coordinate
local DetectedSpeed = DetectedCoord:GetVelocity()
local DetectedHeading = DetectedCoord:GetHeading()
if self.Intercept then
local DetectedSet = DetectedItem.Set
@@ -2453,13 +2455,15 @@ do -- DETECTION_AREAS
local DistanceRecce = 1000000000 -- Units are not further than 1000000 km away from an area :-)
for RecceGroupName, RecceGroup in pairs( self.DetectionSetGroup:GetSet() ) do
for RecceUnit, RecceUnit in pairs( RecceGroup:GetUnits() ) do
if RecceUnit:IsActive() then
local RecceUnitCoord = RecceUnit:GetCoordinate()
local Distance = RecceUnitCoord:Get2DDistance( self:GetDetectedItemCoordinate( DetectedItem.Index ) )
if Distance < DistanceRecce then
DistanceRecce = Distance
NearestRecce = RecceUnit
if RecceGroup and RecceGroup:IsAlive() then
for RecceUnit, RecceUnit in pairs( RecceGroup:GetUnits() ) do
if RecceUnit:IsActive() then
local RecceUnitCoord = RecceUnit:GetCoordinate()
local Distance = RecceUnitCoord:Get2DDistance( self:GetDetectedItemCoordinate( DetectedItem.Index ) )
if Distance < DistanceRecce then
DistanceRecce = Distance
NearestRecce = RecceUnit
end
end
end
end

View File

@@ -442,7 +442,7 @@ function MISSILETRAINER._MenuMessages( MenuParameters )
if MenuParameters.Distance ~= nil then
self.Distance = MenuParameters.Distance
MESSAGE:New( "Hit detection distance set to " .. self.Distance * 1000 .. " meters", 15, "Menu" ):ToAll()
MESSAGE:New( "Hit detection distance set to " .. ( self.Distance * 1000 ) .. " meters", 15, "Menu" ):ToAll()
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -608,9 +608,9 @@ function SCORING:_AddPlayerFromUnit( UnitData )
if self.Players[PlayerName].UnitCoalition ~= UnitCoalition then
self.Players[PlayerName].Penalty = self.Players[PlayerName].Penalty + 50
self.Players[PlayerName].PenaltyCoalition = self.Players[PlayerName].PenaltyCoalition + 1
MESSAGE:New( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' changed coalition from " .. _SCORINGCoalition[self.Players[PlayerName].UnitCoalition] .. " to " .. _SCORINGCoalition[UnitCoalition] ..
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' changed coalition from " .. _SCORINGCoalition[self.Players[PlayerName].UnitCoalition] .. " to " .. _SCORINGCoalition[UnitCoalition] ..
"(changed " .. self.Players[PlayerName].PenaltyCoalition .. " times the coalition). 50 Penalty points added.",
2
MESSAGE.Type.Information
):ToAll()
self:ScoreCSV( PlayerName, "", "COALITION_PENALTY", 1, -50, self.Players[PlayerName].UnitName, _SCORINGCoalition[self.Players[PlayerName].UnitCoalition], _SCORINGCategory[self.Players[PlayerName].UnitCategory], self.Players[PlayerName].UnitType,
UnitName, _SCORINGCoalition[UnitCoalition], _SCORINGCategory[UnitCategory], UnitData:GetTypeName() )
@@ -626,16 +626,16 @@ function SCORING:_AddPlayerFromUnit( UnitData )
if self.Players[PlayerName].Penalty > self.Fratricide * 0.50 then
if self.Players[PlayerName].PenaltyWarning < 1 then
MESSAGE:New( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "': WARNING! If you continue to commit FRATRICIDE and have a PENALTY score higher than " .. self.Fratricide .. ", you will be COURT MARTIALED and DISMISSED from this mission! \nYour total penalty is: " .. self.Players[PlayerName].Penalty,
30
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "': WARNING! If you continue to commit FRATRICIDE and have a PENALTY score higher than " .. self.Fratricide .. ", you will be COURT MARTIALED and DISMISSED from this mission! \nYour total penalty is: " .. self.Players[PlayerName].Penalty,
MESSAGE.Type.Information
):ToAll()
self.Players[PlayerName].PenaltyWarning = self.Players[PlayerName].PenaltyWarning + 1
end
end
if self.Players[PlayerName].Penalty > self.Fratricide then
MESSAGE:New( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' committed FRATRICIDE, he will be COURT MARTIALED and is DISMISSED from this mission!",
10
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' committed FRATRICIDE, he will be COURT MARTIALED and is DISMISSED from this mission!",
MESSAGE.Type.Information
):ToAll()
UnitData:GetGroup():Destroy()
end
@@ -668,7 +668,7 @@ function SCORING:AddGoalScore( PlayerUnit, GoalTag, Text, Score )
PlayerData.Goals[GoalTag].Score = PlayerData.Goals[GoalTag].Score + Score
PlayerData.Score = PlayerData.Score + Score
MESSAGE:New( self.DisplayMessagePrefix .. Text, 30 ):ToAll()
MESSAGE:NewType( self.DisplayMessagePrefix .. Text, MESSAGE.Type.Information ):ToAll()
self:ScoreCSV( PlayerName, "", "GOAL_" .. string.upper( GoalTag ), 1, Score, PlayerUnit:GetName() )
end
@@ -704,7 +704,7 @@ function SCORING:_AddMissionTaskScore( Mission, PlayerUnit, Text, Score )
PlayerData.Score = self.Players[PlayerName].Score + Score
PlayerData.Mission[MissionName].ScoreTask = self.Players[PlayerName].Mission[MissionName].ScoreTask + Score
MESSAGE:New( self.DisplayMessagePrefix .. MissionName .. " : " .. Text .. " Score: " .. Score, 30 ):ToAll()
MESSAGE:NewType( self.DisplayMessagePrefix .. MissionName .. " : " .. Text .. " Score: " .. Score, MESSAGE.Type.Information ):ToAll()
self:ScoreCSV( PlayerName, "", "TASK_" .. MissionName:gsub( ' ', '_' ), 1, Score, PlayerUnit:GetName() )
end
@@ -732,9 +732,9 @@ function SCORING:_AddMissionScore( Mission, Text, Score )
PlayerData.Score = PlayerData.Score + Score
PlayerData.Mission[MissionName].ScoreMission = PlayerData.Mission[MissionName].ScoreMission + Score
MESSAGE:New( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' has " .. Text .. " in Mission '" .. MissionName .. "'. " ..
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' has " .. Text .. " in Mission '" .. MissionName .. "'. " ..
Score .. " mission score!",
60 ):ToAll()
MESSAGE.Type.Information ):ToAll()
self:ScoreCSV( PlayerName, "", "MISSION_" .. MissionName:gsub( ' ', '_' ), 1, Score )
end
@@ -902,19 +902,19 @@ function SCORING:_EventOnHit( Event )
if TargetPlayerName ~= nil then -- It is a player hitting another player ...
MESSAGE
:New( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit friendly player '" .. TargetPlayerName .. "' " ..
:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit friendly player '" .. TargetPlayerName .. "' " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.PenaltyHit .. " times. " ..
"Penalty: -" .. PlayerHit.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty,
2
MESSAGE.Type.Update
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
else
MESSAGE
:New( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit friendly target " ..
:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit friendly target " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.PenaltyHit .. " times. " ..
"Penalty: -" .. PlayerHit.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty,
2
MESSAGE.Type.Update
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
@@ -926,19 +926,19 @@ function SCORING:_EventOnHit( Event )
PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1
if TargetPlayerName ~= nil then -- It is a player hitting another player ...
MESSAGE
:New( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit enemy player '" .. TargetPlayerName .. "' " ..
:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit enemy player '" .. TargetPlayerName .. "' " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.ScoreHit .. " times. " ..
"Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty,
2
MESSAGE.Type.Update
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
else
MESSAGE
:New( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit enemy target " ..
:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit enemy target " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.ScoreHit .. " times. " ..
"Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty,
2
MESSAGE.Type.Update
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
@@ -947,8 +947,8 @@ function SCORING:_EventOnHit( Event )
end
else -- A scenery object was hit.
MESSAGE
:New( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit scenery object.",
2
:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit scenery object.",
MESSAGE.Type.Update
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
@@ -1008,10 +1008,10 @@ function SCORING:_EventOnHit( Event )
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1
MESSAGE
:New( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit friendly target " ..
:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit friendly target " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
"Penalty: -" .. PlayerHit.Penalty .. " = " .. Player.Score - Player.Penalty,
2
MESSAGE.Type.Update
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
@@ -1021,10 +1021,10 @@ function SCORING:_EventOnHit( Event )
PlayerHit.Score = PlayerHit.Score + 1
PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1
MESSAGE
:New( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit enemy target " ..
:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit enemy target " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
"Score: +" .. PlayerHit.Score .. " = " .. Player.Score - Player.Penalty,
2
MESSAGE.Type.Update
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
@@ -1032,8 +1032,8 @@ function SCORING:_EventOnHit( Event )
end
else -- A scenery object was hit.
MESSAGE
:New( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit scenery object.",
2
:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit scenery object.",
MESSAGE.Type.Update
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
@@ -1131,19 +1131,19 @@ function SCORING:_EventOnDeadOrCrash( Event )
if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player
MESSAGE
:New( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly player '" .. TargetPlayerName .. "' " ..
:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly player '" .. TargetPlayerName .. "' " ..
TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Penalty: -" .. TargetDestroy.Penalty .. " = " .. Player.Score - Player.Penalty,
15
MESSAGE.Type.Information
)
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
else
MESSAGE
:New( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly target " ..
:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly target " ..
TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Penalty: -" .. TargetDestroy.Penalty .. " = " .. Player.Score - Player.Penalty,
15
MESSAGE.Type.Information
)
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
@@ -1165,19 +1165,19 @@ function SCORING:_EventOnDeadOrCrash( Event )
TargetDestroy.ScoreDestroy = TargetDestroy.ScoreDestroy + 1
if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player
MESSAGE
:New( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy player '" .. TargetPlayerName .. "' " ..
:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy player '" .. TargetPlayerName .. "' " ..
TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Score: +" .. TargetDestroy.Score .. " = " .. Player.Score - Player.Penalty,
15
MESSAGE.Type.Information
)
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
else
MESSAGE
:New( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy " ..
:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy " ..
TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Score: +" .. TargetDestroy.Score .. " = " .. Player.Score - Player.Penalty,
15
MESSAGE.Type.Information
)
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
@@ -1191,9 +1191,9 @@ function SCORING:_EventOnDeadOrCrash( Event )
Player.Score = Player.Score + Score
TargetDestroy.Score = TargetDestroy.Score + Score
MESSAGE
:New( self.DisplayMessagePrefix .. "Special target '" .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. " destroyed! " ..
:NewType( self.DisplayMessagePrefix .. "Special target '" .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. " destroyed! " ..
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! Total: " .. Player.Score - Player.Penalty,
15
MESSAGE.Type.Information
)
:ToAllIf( self:IfMessagesScore() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesScore() and self:IfMessagesToCoalition() )
@@ -1210,10 +1210,10 @@ function SCORING:_EventOnDeadOrCrash( Event )
Player.Score = Player.Score + Score
TargetDestroy.Score = TargetDestroy.Score + Score
MESSAGE
:New( self.DisplayMessagePrefix .. "Target destroyed in zone '" .. ScoreZone:GetName() .. "'." ..
:NewType( self.DisplayMessagePrefix .. "Target destroyed in zone '" .. ScoreZone:GetName() .. "'." ..
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " ..
"Total: " .. Player.Score - Player.Penalty,
15 )
MESSAGE.Type.Information )
:ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() )
self:ScoreCSV( PlayerName, TargetPlayerName, "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
@@ -1232,10 +1232,10 @@ function SCORING:_EventOnDeadOrCrash( Event )
Player.Score = Player.Score + Score
TargetDestroy.Score = TargetDestroy.Score + Score
MESSAGE
:New( self.DisplayMessagePrefix .. "Scenery destroyed in zone '" .. ScoreZone:GetName() .. "'." ..
:NewType( self.DisplayMessagePrefix .. "Scenery destroyed in zone '" .. ScoreZone:GetName() .. "'." ..
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " ..
"Total: " .. Player.Score - Player.Penalty,
15
MESSAGE.Type.Information
)
:ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() )
@@ -1522,7 +1522,7 @@ function SCORING:ReportScoreGroupSummary( PlayerGroup )
PlayerScore,
PlayerPenalty
)
MESSAGE:New( PlayerMessage, 30, "Player '" .. PlayerName .. "'" ):ToGroup( PlayerGroup )
MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Detailed ):ToGroup( PlayerGroup )
end
end
@@ -1579,7 +1579,7 @@ function SCORING:ReportScoreGroupDetailed( PlayerGroup )
ReportGoals,
ReportMissions
)
MESSAGE:New( PlayerMessage, 30, "Player '" .. PlayerName .. "'" ):ToGroup( PlayerGroup )
MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Detailed ):ToGroup( PlayerGroup )
end
end
@@ -1628,7 +1628,7 @@ function SCORING:ReportScoreAllSummary( PlayerGroup )
PlayerScore,
PlayerPenalty
)
MESSAGE:New( PlayerMessage, 30, "Player '" .. PlayerName .. "'" ):ToGroup( PlayerGroup )
MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Overview ):ToGroup( PlayerGroup )
end
end

View File

@@ -200,6 +200,7 @@
-- * @{#SPAWN.SpawnFromStatic}(): Spawn a new group from a structure, taking the position of a @{Static}.
-- * @{#SPAWN.SpawnFromUnit}(): Spawn a new group taking the position of a @{Unit}.
-- * @{#SPAWN.SpawnInZone}(): Spawn a new group in a @{Zone}.
-- * @{#SPAWN.SpawnAtAirbase}(): Spawn a new group at an @{Airbase}, which can be an airdrome, ship or helipad.
--
-- Note that @{#SPAWN.Spawn} and @{#SPAWN.ReSpawn} return a @{GROUP#GROUP.New} object, that contains a reference to the DCSGroup object.
-- You can use the @{GROUP} object to do further actions with the DCSGroup.
@@ -982,20 +983,53 @@ function SPAWN:OnSpawnGroup( SpawnCallBackFunction, ... )
return self
end
--- Will spawn a group at an airbase.
--- Will spawn a group at an @{Airbase}.
-- This method is mostly advisable to be used if you want to simulate spawning units at an airbase.
-- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
-- You can use the returned group to further define the route to be followed.
--
-- The @{Airbase#AIRBASE} object must refer to a valid airbase known in the sim.
-- You can use the following enumerations to search for the pre-defined airbases on the current known maps of DCS:
--
-- * @{Airbase#AIRBASE.Caucasus}: The airbases on the Caucasus map.
-- * @{Airbase#AIRBASE.Nevada}: The airbases on the Nevada (NTTR) map.
-- * @{Airbase#AIRBASE.Normandy}: The airbases on the Normandy map.
--
-- Use the method @{Airbase#AIRBASE.FindByName}() to retrieve the airbase object.
-- The known AIRBASE objects are automatically imported at mission start by MOOSE.
-- Therefore, there isn't any New() constructor defined for AIRBASE objects.
--
-- Ships and Farps are added within the mission, and are therefore not known.
-- For these AIRBASE objects, there isn't an @{Airbase#AIRBASE} enumeration defined.
-- You need to provide the **exact name** of the airbase as the parameter to the @{Airbase#AIRBASE.FindByName}() method!
--
-- @param #SPAWN self
-- @param Wrapper.Airbase#AIRBASE Airbase The @{Airbase} where to spawn the group.
-- @param Wrapper.Airbase#AIRBASE SpawnAirbase The @{Airbase} where to spawn the group.
-- @param #SPAWN.Takeoff Takeoff (optional) The location and takeoff method. Default is Hot.
-- @param #number TakeoffAltitude (optional) The altitude above the ground.
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil Nothing was spawned.
function SPAWN:SpawnAtAirbase( Airbase, Takeoff, TakeoffAltitude ) -- R2.2
self:E( { self.SpawnTemplatePrefix, Airbase, Takeoff, TakeoffAltitude } )
-- @usage
-- Spawn_Plane = SPAWN:New( "Plane" )
-- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Cold )
-- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Hot )
-- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Runway )
--
-- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( "Carrier" ), SPAWN.Takeoff.Cold )
--
-- Spawn_Heli = SPAWN:New( "Heli")
--
-- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "FARP Cold" ), SPAWN.Takeoff.Cold )
-- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "FARP Hot" ), SPAWN.Takeoff.Hot )
-- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "FARP Runway" ), SPAWN.Takeoff.Runway )
-- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "FARP Air" ), SPAWN.Takeoff.Air )
--
-- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "Carrier" ), SPAWN.Takeoff.Cold )
--
function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude ) -- R2.2
self:E( { self.SpawnTemplatePrefix, SpawnAirbase, Takeoff, TakeoffAltitude } )
local PointVec3 = Airbase:GetPointVec3()
local PointVec3 = SpawnAirbase:GetPointVec3()
self:T2(PointVec3)
Takeoff = Takeoff or SPAWN.Takeoff.Hot
@@ -1006,42 +1040,87 @@ function SPAWN:SpawnAtAirbase( Airbase, Takeoff, TakeoffAltitude ) -- R2.2
if SpawnTemplate then
self:T( { "Current point of ", self.SpawnTemplatePrefix, Airbase } )
self:T( { "Current point of ", self.SpawnTemplatePrefix, SpawnAirbase } )
local SpawnPoint = SpawnTemplate.route.points[1]
-- These are only for ships.
SpawnPoint.linkUnit = nil
SpawnPoint.helipadId = nil
SpawnPoint.airdromeId = nil
local AirbaseID = SpawnAirbase:GetID()
local AirbaseCategory = SpawnAirbase:GetDesc().category
self:F( { AirbaseCategory = AirbaseCategory } )
if AirbaseCategory == Airbase.Category.SHIP then
SpawnPoint.linkUnit = AirbaseID
SpawnPoint.helipadId = AirbaseID
elseif AirbaseCategory == Airbase.Category.HELIPAD then
SpawnPoint.linkUnit = AirbaseID
SpawnPoint.helipadId = AirbaseID
elseif AirbaseCategory == Airbase.Category.AIRDROME then
SpawnPoint.airdromeId = AirbaseID
end
SpawnPoint.alt = 0
SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type
SpawnPoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action
-- Translate the position of the Group Template to the Vec3.
for UnitID = 1, #SpawnTemplate.units do
self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
-- These cause a lot of confusion.
local UnitTemplate = SpawnTemplate.units[UnitID]
UnitTemplate.parking = nil
UnitTemplate.parking_id = nil
UnitTemplate.alt = 0
local SX = UnitTemplate.x
local SY = UnitTemplate.y
local BX = SpawnTemplate.route.points[1].x
local BY = SpawnTemplate.route.points[1].y
local BX = SpawnPoint.x
local BY = SpawnPoint.y
local TX = PointVec3.x + ( SX - BX )
local TY = PointVec3.z + ( SY - BY )
SpawnTemplate.units[UnitID].x = TX
SpawnTemplate.units[UnitID].y = TY
UnitTemplate.x = TX
UnitTemplate.y = TY
if Takeoff == GROUP.Takeoff.Air then
SpawnTemplate.units[UnitID].alt = PointVec3.y + ( TakeoffAltitude or 200 )
else
SpawnTemplate.units[UnitID].alt = PointVec3.y + 10
UnitTemplate.alt = PointVec3.y + ( TakeoffAltitude or 200 )
--else
-- UnitTemplate.alt = PointVec3.y + 10
end
self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y )
end
SpawnTemplate.route.points[1].x = PointVec3.x
SpawnTemplate.route.points[1].y = PointVec3.z
SpawnPoint.x = PointVec3.x
SpawnPoint.y = PointVec3.z
if Takeoff == GROUP.Takeoff.Air then
SpawnTemplate.route.points[1].alt = PointVec3.y + ( TakeoffAltitude or 200 )
else
SpawnTemplate.route.points[1].alt = PointVec3.y + 10
SpawnTemplate.route.points[1].airdromeId = Airbase:GetID()
SpawnPoint.alt = PointVec3.y + ( TakeoffAltitude or 200 )
--else
-- SpawnPoint.alt = PointVec3.y + 10
end
SpawnTemplate.route.points[1].type = GROUPTEMPLATE.Takeoff[Takeoff]
SpawnTemplate.x = PointVec3.x
SpawnTemplate.y = PointVec3.z
return self:SpawnWithIndex( self.SpawnIndex )
local GroupSpawned = self:SpawnWithIndex( self.SpawnIndex )
-- When spawned in the air, we need to generate a Takeoff Event
if Takeoff == GROUP.Takeoff.Air then
for UnitID, UnitSpawned in pairs( GroupSpawned:GetUnits() ) do
SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() } , 1 )
end
end
return GroupSpawned
end
end

View File

@@ -358,10 +358,20 @@ end
-- @param #COMMANDCENTER self
-- @param #string Message
-- @param Wrapper.Group#GROUP TaskGroup
-- @param #sring Name (optional) The name of the Group used as a prefix for the message to the Group. If not provided, there will be nothing shown.
function COMMANDCENTER:MessageToGroup( Message, TaskGroup )
self:GetPositionable():MessageToGroup( Message , 15, TaskGroup, self:GetName() )
self:GetPositionable():MessageToGroup( Message, 15, TaskGroup, self:GetName() )
end
--- Send a CC message of a specified type to a GROUP.
-- @param #COMMANDCENTER self
-- @param #string Message
-- @param Wrapper.Group#GROUP TaskGroup
-- @param Core.Message#MESSAGE.MessageType MessageType The type of the message, resulting in automatic time duration and prefix of the message.
function COMMANDCENTER:MessageTypeToGroup( Message, TaskGroup, MessageType )
self:GetPositionable():MessageTypeToGroup( Message, MessageType, TaskGroup, self:GetName() )
end
@@ -377,6 +387,20 @@ function COMMANDCENTER:MessageToCoalition( Message )
end
--- Send a CC message of a specified type to the coalition of the CC.
-- @param #COMMANDCENTER self
-- @param #string Message The message.
-- @param Core.Message#MESSAGE.MessageType MessageType The type of the message, resulting in automatic time duration and prefix of the message.
function COMMANDCENTER:MessageTypeToCoalition( Message, MessageType )
local CCCoalition = self:GetPositionable():GetCoalition()
--TODO: Fix coalition bug!
self:GetPositionable():MessageTypeToCoalition( Message, MessageType, CCCoalition )
end
--- Report the status of all MISSIONs to a GROUP.
-- Each Mission is listed, with an indication how many Tasks are still to be completed.
-- @param #COMMANDCENTER self

View File

@@ -264,14 +264,14 @@ function MISSION:New( CommandCenter, MissionName, MissionPriority, MissionBriefi
end
-- FSM function for a MISSION
--- FSM function for a MISSION
-- @param #MISSION self
-- @param #string From
-- @param #string Event
-- @param #string To
function MISSION:onenterCOMPLETED( From, Event, To )
self:GetCommandCenter():MessageToCoalition( self:GetName() .. " has been completed! Good job guys!" )
self:GetCommandCenter():MessageTypeToCoalition( self:GetName() .. " has been completed! Good job guys!", MESSAGE.Type.Information )
end
--- Gets the mission name.
@@ -888,6 +888,8 @@ end
-- @return #string
function MISSION:ReportOverview( ReportGroup, TaskStatus )
self:F( { TaskStatus = TaskStatus } )
local Report = REPORT:New()
-- List the name of the mission.
@@ -958,7 +960,7 @@ function MISSION:MenuReportBriefing( ReportGroup )
local Report = self:ReportBriefing()
self:GetCommandCenter():MessageToGroup( Report, ReportGroup )
self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Briefing )
end
@@ -970,7 +972,7 @@ function MISSION:MenuReportTasksSummary( ReportGroup )
local Report = self:ReportSummary( ReportGroup )
self:GetCommandCenter():MessageToGroup( Report, ReportGroup )
self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Overview )
end
@@ -983,7 +985,7 @@ function MISSION:MenuReportTasksPerStatus( ReportGroup, TaskStatus )
local Report = self:ReportOverview( ReportGroup, TaskStatus )
self:GetCommandCenter():MessageToGroup( Report, ReportGroup )
self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Overview )
end
@@ -993,7 +995,7 @@ function MISSION:MenuReportPlayersPerTask( ReportGroup )
local Report = self:ReportPlayersPerTask()
self:GetCommandCenter():MessageToGroup( Report, ReportGroup )
self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Overview )
end
--- @param #MISSION self
@@ -1002,7 +1004,7 @@ function MISSION:MenuReportPlayersProgress( ReportGroup )
local Report = self:ReportPlayersProgress()
self:GetCommandCenter():MessageToGroup( Report, ReportGroup )
self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Overview )
end

View File

@@ -746,7 +746,8 @@ function TASK:SetPlannedMenuForGroup( TaskGroup, MenuTime )
if not Mission:IsGroupAssigned( TaskGroup ) then
self:F( { "Replacing Join Task menu" } )
local JoinTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Join Task" ), TaskTypeMenu, self.MenuAssignToGroup, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ):SetRemoveParent( true )
local JoinTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Join Task" ), TaskTypeMenu, self.MenuAssignToGroup, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ):SetRemoveParent( true )
local MarkTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Mark Task on Map" ), TaskTypeMenu, self.MenuMarkToGroup, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ):SetRemoveParent( true )
end
return self
@@ -854,11 +855,62 @@ end
-- @param Wrapper.Group#GROUP TaskGroup
function TASK:MenuAssignToGroup( TaskGroup )
self:E( "Assigned menu selected")
self:E( "Join Task menu selected")
self:AssignToGroup( TaskGroup )
end
--- @param #TASK self
-- @param Wrapper.Group#GROUP TaskGroup
function TASK:MenuMarkToGroup( TaskGroup )
self:E( "Mark Task menu selected")
self:UpdateTaskInfo()
local Report = REPORT:New():SetIndent( 0 )
-- List the name of the Task.
local Name = self:GetName()
Report:Add( Name .. ": " .. self:GetTaskBriefing() )
for TaskInfoID, TaskInfo in pairs( self.TaskInfo, function( t, a, b ) return t[a].TaskInfoOrder < t[b].TaskInfoOrder end ) do
local TaskInfoIDText = "" --string.format( "%s: ", TaskInfoID )
if type( TaskInfo.TaskInfoText ) == "string" then
if TaskInfoID == "Targets" then
else
Report:Add( TaskInfoIDText .. TaskInfo.TaskInfoText )
end
elseif type( TaskInfo ) == "table" then
if TaskInfoID == "Coordinates" then
--local ToCoordinate = TaskInfo.TaskInfoText -- Core.Point#COORDINATE
--Report:Add( TaskInfoIDText .. ToCoordinate:ToString() )
else
end
end
end
local Coordinate = self:GetInfo( "Coordinates" ) -- Core.Point#COORDINATE
local Velocity = self.TargetSetUnit:GetVelocity()
local Heading = self.TargetSetUnit:GetHeading()
Coordinate:SetHeading( Heading )
Coordinate:SetVelocity( Velocity )
Report:Add( "Targets are" .. Coordinate:GetMovingText() .. "." )
local MarkText = Report:Text( ", " )
self:F( { Coordinate = Coordinate, MarkText = MarkText } )
Coordinate:MarkToGroup( MarkText, TaskGroup )
--Coordinate:MarkToAll( Briefing )
end
--- Report the task status.
-- @param #TASK self
function TASK:MenuTaskStatus( TaskGroup )
@@ -866,7 +918,7 @@ function TASK:MenuTaskStatus( TaskGroup )
local ReportText = self:ReportDetails( TaskGroup )
self:T( ReportText )
self:GetMission():GetCommandCenter():MessageToGroup( ReportText, TaskGroup )
self:GetMission():GetCommandCenter():MessageTypeToGroup( ReportText, TaskGroup, MESSAGE.Type.Detailed )
end
@@ -1033,6 +1085,17 @@ function TASK:SetInfo( TaskInfo, TaskInfoText, TaskInfoOrder )
self.TaskInfo[TaskInfo].TaskInfoOrder = TaskInfoOrder
end
--- Gets the Information of the Task
-- @param #TASK self
-- @param #string TaskInfo The key and title of the task information.
-- @return #string TaskInfoText The Task info text.
function TASK:GetInfo( TaskInfo )
self.TaskInfo = self.TaskInfo or {}
self.TaskInfo[TaskInfo] = self.TaskInfo[TaskInfo] or {}
return self.TaskInfo[TaskInfo].TaskInfoText
end
--- Gets the Type of the Task
-- @param #TASK self
-- @return #string TaskType
@@ -1502,6 +1565,8 @@ function TASK:ReportDetails( ReportGroup )
-- Determine the status of the Task.
local Status = "<" .. self:GetState() .. ">"
Report:Add( "Task: " .. Name .. " - " .. Status .. " - Detailed Report" )
-- Loop each Unit active in the Task, and find Player Names.
local PlayerNames = self:GetPlayerNames()
@@ -1510,10 +1575,11 @@ function TASK:ReportDetails( ReportGroup )
PlayerReport:Add( "Group " .. PlayerGroup:GetCallsign() .. ": " .. PlayerName )
end
local Players = PlayerReport:Text()
Report:Add( "Task: " .. Name .. " - " .. Status .. " - Detailed Report" )
Report:Add( " - Players:" )
Report:AddIndent( Players )
if Players ~= "" then
Report:Add( " - Players assigned:" )
Report:AddIndent( Players )
end
for TaskInfoID, TaskInfo in pairs( self.TaskInfo, function( t, a, b ) return t[a].TaskInfoOrder < t[b].TaskInfoOrder end ) do
@@ -1525,15 +1591,23 @@ function TASK:ReportDetails( ReportGroup )
if TaskInfoID == "Coordinates" then
local FromCoordinate = ReportGroup:GetUnit(1):GetCoordinate()
local ToCoordinate = TaskInfo.TaskInfoText -- Core.Point#COORDINATE
Report:Add( TaskInfoIDText )
Report:AddIndent( ToCoordinate:ToStringBRA( FromCoordinate ) .. ", " .. TaskInfo.TaskInfoText:ToStringAspect( FromCoordinate ) )
Report:AddIndent( ToCoordinate:ToStringBULLS( ReportGroup:GetCoalition() ) )
Report:Add( TaskInfoIDText .. ToCoordinate:ToString( ReportGroup:GetUnit(1), nil, self ) )
else
end
end
end
local Coordinate = self:GetInfo( "Coordinates" ) -- Core.Point#COORDINATE
local Velocity = self.TargetSetUnit:GetVelocity()
local Heading = self.TargetSetUnit:GetHeading()
Coordinate:SetHeading( Heading )
Coordinate:SetVelocity( Velocity )
Report:Add( "Targets are" .. Coordinate:GetMovingText() .. "." )
return Report:Text()
end

View File

@@ -141,9 +141,9 @@ do -- TASK_A2G
else
local TargetUnit = Task.TargetSetUnit:GetFirst() -- Wrapper.Unit#UNIT
if TargetUnit then
local Coordinate = TargetUnit:GetCoordinate()
local Coordinate = TargetUnit:GetPointVec3()
self:T( { TargetCoordinate = Coordinate, Coordinate:GetX(), Coordinate:GetY(), Coordinate:GetZ() } )
Task:SetTargetCoordinate( TargetUnit:GetCoordinate(), TaskUnit )
Task:SetTargetCoordinate( Coordinate, TaskUnit )
end
self:__RouteToTargetPoint( 0.1 )
end
@@ -325,11 +325,9 @@ do -- TASK_A2G_SEAD
self:SetBriefing(
TaskBriefing or
"Execute a Suppression of Enemy Air Defenses.\n"
"Execute a Suppression of Enemy Air Defenses."
)
self:UpdateTaskInfo()
return self
end
@@ -339,7 +337,13 @@ do -- TASK_A2G_SEAD
local TargetCoordinate = self.Detection and self.Detection:GetDetectedItemCoordinate( self.DetectedItemIndex ) or self.TargetSetUnit:GetFirst():GetCoordinate()
self:SetInfo( "Coordinates", TargetCoordinate, 0 )
self:SetInfo( "Threat", "[" .. string.rep( "", self.Detection and self.Detection:GetDetectedItemThreatLevel( self.DetectedItemIndex ) or self.TargetSetUnit:CalculateThreatLevelA2G() ) .. "]", 11 )
local ThreatLevel, ThreatText
if self.Detection then
ThreatLevel, ThreatText = self.Detection:GetDetectedItemThreatLevel( self.DetectedItemIndex )
else
ThreatLevel, ThreatText = self.TargetSetUnit:CalculateThreatLevelA2G()
end
self:SetInfo( "Threat", ThreatText .. " [" .. string.rep( "", ThreatLevel ) .. "]", 11 )
if self.Detection then
local DetectedItemsCount = self.TargetSetUnit:Count()
@@ -362,7 +366,8 @@ do -- TASK_A2G_SEAD
end
function TASK_A2G_SEAD:ReportOrder( ReportGroup )
local Coordinate = self.TaskInfo.Coordinates.TaskInfoText
local Coordinate = self:GetInfo( "Coordinates" )
--local Coordinate = self.TaskInfo.Coordinates.TaskInfoText
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
return Distance
@@ -468,10 +473,8 @@ do -- TASK_A2G_BAI
self:SetBriefing(
TaskBriefing or
"Execute a Battlefield Air Interdiction of a group of enemy targets.\n"
"Execute a Battlefield Air Interdiction of a group of enemy targets."
)
self:UpdateTaskInfo()
return self
end
@@ -483,7 +486,13 @@ do -- TASK_A2G_BAI
local TargetCoordinate = self.Detection and self.Detection:GetDetectedItemCoordinate( self.DetectedItemIndex ) or self.TargetSetUnit:GetFirst():GetCoordinate()
self:SetInfo( "Coordinates", TargetCoordinate, 0 )
self:SetInfo( "Threat", "[" .. string.rep( "", self.Detection and self.Detection:GetDetectedItemThreatLevel( self.DetectedItemIndex ) or self.TargetSetUnit:CalculateThreatLevelA2G() ) .. "]", 11 )
local ThreatLevel, ThreatText
if self.Detection then
ThreatLevel, ThreatText = self.Detection:GetDetectedItemThreatLevel( self.DetectedItemIndex )
else
ThreatLevel, ThreatText = self.TargetSetUnit:CalculateThreatLevelA2G()
end
self:SetInfo( "Threat", ThreatText .. " [" .. string.rep( "", ThreatLevel ) .. "]", 11 )
if self.Detection then
local DetectedItemsCount = self.TargetSetUnit:Count()
@@ -507,7 +516,8 @@ do -- TASK_A2G_BAI
function TASK_A2G_BAI:ReportOrder( ReportGroup )
local Coordinate = self.TaskInfo.Coordinates.TaskInfoText
local Coordinate = self:GetInfo( "Coordinates" )
--local Coordinate = self.TaskInfo.Coordinates.TaskInfoText
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
return Distance
@@ -612,21 +622,26 @@ do -- TASK_A2G_CAS
self:SetBriefing(
TaskBriefing or
"Execute a Close Air Support for a group of enemy targets.\n" ..
"Beware of friendlies at the vicinity!\n"
"Execute a Close Air Support for a group of enemy targets. " ..
"Beware of friendlies at the vicinity! "
)
self:UpdateTaskInfo()
return self
end
function TASK_A2G_CAS:UpdateTaskInfo()
local TargetCoordinate = self.Detection and self.Detection:GetDetectedItemCoordinate( self.DetectedItemIndex ) or self.TargetSetUnit:GetFirst():GetCoordinate()
local TargetCoordinate = ( self.Detection and self.Detection:GetDetectedItemCoordinate( self.DetectedItemIndex ) ) or self.TargetSetUnit:GetFirst():GetCoordinate()
self:SetInfo( "Coordinates", TargetCoordinate, 0 )
self:SetInfo( "Threat", "[" .. string.rep( "", self.Detection and self.Detection:GetDetectedItemThreatLevel( self.DetectedItemIndex ) or self.TargetSetUnit:CalculateThreatLevelA2G() ) .. "]", 11 )
local ThreatLevel, ThreatText
if self.Detection then
ThreatLevel, ThreatText = self.Detection:GetDetectedItemThreatLevel( self.DetectedItemIndex )
else
ThreatLevel, ThreatText = self.TargetSetUnit:CalculateThreatLevelA2G()
end
self:SetInfo( "Threat", ThreatText .. " [" .. string.rep( "", ThreatLevel ) .. "]", 11 )
if self.Detection then
local DetectedItemsCount = self.TargetSetUnit:Count()
@@ -648,8 +663,10 @@ do -- TASK_A2G_CAS
end
function TASK_A2G_CAS:ReportOrder( ReportGroup )
local Coordinate = self.TaskInfo.Coordinates.TaskInfoText
--- @param #TASK_A2G_CAS self
function TASK_A2G_CAS:ReportOrder( ReportGroup )
local Coordinate = self:GetInfo( "Coordinates" )
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
return Distance

View File

@@ -372,6 +372,7 @@ do -- TASK_A2G_DISPATCHER
self.Tasks[TaskIndex] = Task
Task:SetTargetZone( DetectedZone )
Task:SetDispatcher( self )
Task:UpdateTaskInfo()
Mission:AddTask( Task )
TaskReport:Add( Task:GetName() )

View File

@@ -31,7 +31,9 @@ FLARECOLOR = trigger.flareColor -- #FLARECOLOR
--- Utilities static class.
-- @type UTILS
UTILS = {}
UTILS = {
_MarkID = 1
}
--- Function to infer instance of an object
--
@@ -395,3 +397,28 @@ function UTILS.spairs( t, order )
end
end
end
-- get a new mark ID for markings
function UTILS.GetMarkID()
UTILS._MarkID = UTILS._MarkID + 1
return UTILS._MarkID
end
-- Test if a Vec2 is in a radius of another Vec2
function UTILS.IsInRadius( InVec2, Vec2, Radius )
local InRadius = ( ( InVec2.x - Vec2.x ) ^2 + ( InVec2.y - Vec2.y ) ^2 ) ^ 0.5 <= Radius
return InRadius
end
-- Test if a Vec3 is in the sphere of another Vec3
function UTILS.IsInSphere( InVec3, Vec3, Radius )
local InSphere = ( ( InVec3.x - Vec3.x ) ^2 + ( InVec3.y - Vec3.y ) ^2 + ( InVec3.z - Vec3.z ) ^2 ) ^ 0.5 <= Radius
return InSphere
end

View File

@@ -107,7 +107,7 @@
-- ### Tasks at Waypoints
--
-- Special Task methods are available to set tasks at certain waypoints.
-- The method @{#CONTROLLABLE.SetTaskAtWaypoint}() helps preparing a Route, embedding a Task at the Waypoint of the Route.
-- The method @{#CONTROLLABLE.SetTaskWaypoint}() helps preparing a Route, embedding a Task at the Waypoint of the Route.
--
-- This creates a Task element, with an action to call a function as part of a Wrapped Task.
--
@@ -368,18 +368,23 @@ function CONTROLLABLE:SetTask( DCSTask, WaitTime )
if DCSControllable then
local DCSControllableName = self:GetName()
-- When a controllable SPAWNs, it takes about a second to get the controllable in the simulator. Setting tasks to unspawned controllables provides unexpected results.
-- Therefore we schedule the functions to set the mission and options for the Controllable.
-- Controller.setTask( Controller, DCSTask )
local function SetTask( Controller, DCSTask )
local Controller = self:_GetController()
Controller:setTask( DCSTask )
if self and self:IsAlive() then
local Controller = self:_GetController()
Controller:setTask( DCSTask )
else
BASE:E( DCSControllableName .. " is not alive anymore. Cannot set DCSTask " .. DCSTask )
end
end
if not WaitTime or WaitTime == 0 then
SetTask( DCSTask )
SetTask( self, DCSTask )
else
self.TaskScheduler:Schedule( self, SetTask, { DCSTask }, WaitTime )
end
@@ -1535,7 +1540,7 @@ function CONTROLLABLE:TaskEmbarkToTransport( Point, Radius )
end
--- This creates a Task element, with an action to call a function as part of a Wrapped Task.
-- This Task can then be embedded at a Waypoint by calling the method @{#CONTROLLABLE.SetTaskAtWaypoint}.
-- This Task can then be embedded at a Waypoint by calling the method @{#CONTROLLABLE.SetTaskWaypoint}.
-- @param #CONTROLLABLE self
-- @param #string FunctionString The function name embedded as a string that will be called.
-- @param ... The variable arguments passed to the function when called! These arguments can be of any type!
@@ -1574,7 +1579,7 @@ end
--
-- local TaskRouteToZone = Vehicle:TaskFunction( "RouteToZone", RandomZone )
--
-- Vehicle:SetTaskAtWaypoint( Route, #Route, TaskRouteToZone ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
-- Vehicle:SetTaskWaypoint( Route, #Route, TaskRouteToZone ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
--
-- Vehicle:Route( Route, math.random( 10, 20 ) ) -- Move after a random seconds to the Route. See the Route method for details.
--
@@ -1628,6 +1633,140 @@ function CONTROLLABLE:TaskMission( TaskMission )
return DCSTask
end
do -- Patrol methods
--- (GROUND) Patrol iteratively using the waypoints the for the (parent) group.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE
function CONTROLLABLE:PatrolRoute()
local PatrolGroup = self -- Wrapper.Group#GROUP
if not self:IsInstanceOf( "GROUP" ) then
PatrolGroup = self:GetGroup() -- Wrapper.Group#GROUP
end
self:E( { PatrolGroup = PatrolGroup:GetName() } )
if PatrolGroup:IsGround() or PatrolGroup:IsShip() then
local Waypoints = PatrolGroup:GetTemplateRoutePoints()
-- Calculate the new Route.
local FromCoord = PatrolGroup:GetCoordinate()
local From = FromCoord:WaypointGround( 120 )
table.insert( Waypoints, 1, From )
local TaskRoute = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRoute" )
self:E({Waypoints = Waypoints})
local Waypoint = Waypoints[#Waypoints]
PatrolGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
PatrolGroup:Route( Waypoints ) -- Move after a random seconds to the Route. See the Route method for details.
end
end
--- (GROUND) Patrol randomly to the waypoints the for the (parent) group.
-- A random waypoint will be picked and the group will move towards that point.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE
function CONTROLLABLE:PatrolRouteRandom( Speed, Formation, ToWaypoint )
local PatrolGroup = self -- Wrapper.Group#GROUP
if not self:IsInstanceOf( "GROUP" ) then
PatrolGroup = self:GetGroup() -- Wrapper.Group#GROUP
end
self:E( { PatrolGroup = PatrolGroup:GetName() } )
if PatrolGroup:IsGround() or PatrolGroup:IsShip() then
local Waypoints = PatrolGroup:GetTemplateRoutePoints()
-- Calculate the new Route.
local FromCoord = PatrolGroup:GetCoordinate()
local FromWaypoint = 1
if ToWaypoint then
FromWaypoint = ToWaypoint
end
-- Loop until a waypoint has been found that is not the same as the current waypoint.
-- Otherwise the object zon't move or drive in circles and the algorithm would not do exactly
-- what it is supposed to do, which is making groups drive around.
local ToWaypoint
repeat
-- Select a random waypoint and check if it is not the same waypoint as where the object is about.
ToWaypoint = math.random( 1, #Waypoints )
until( ToWaypoint ~= FromWaypoint )
self:E( { FromWaypoint = FromWaypoint, ToWaypoint = ToWaypoint } )
local Waypoint = Waypoints[ToWaypoint] -- Select random waypoint.
local ToCoord = COORDINATE:NewFromVec2( { x = Waypoint.x, y = Waypoint.y } )
-- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task
local Route = {}
Route[#Route+1] = FromCoord:WaypointGround( 0 )
Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation )
local TaskRouteToZone = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRouteRandom", Speed, Formation, ToWaypoint )
PatrolGroup:SetTaskWaypoint( Route[#Route], TaskRouteToZone ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
PatrolGroup:Route( Route, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
end
end
--- (GROUND) Patrol randomly to the waypoints the for the (parent) group.
-- A random waypoint will be picked and the group will move towards that point.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE
function CONTROLLABLE:PatrolZones( ZoneList, Speed, Formation )
if not type( ZoneList ) == "table" then
ZoneList = { ZoneList }
end
local PatrolGroup = self -- Wrapper.Group#GROUP
if not self:IsInstanceOf( "GROUP" ) then
PatrolGroup = self:GetGroup() -- Wrapper.Group#GROUP
end
self:E( { PatrolGroup = PatrolGroup:GetName() } )
if PatrolGroup:IsGround() or PatrolGroup:IsShip() then
local Waypoints = PatrolGroup:GetTemplateRoutePoints()
local Waypoint = Waypoints[math.random( 1, #Waypoints )] -- Select random waypoint.
-- Calculate the new Route.
local FromCoord = PatrolGroup:GetCoordinate()
-- Select a random Zone and get the Coordinate of the new Zone.
local RandomZone = ZoneList[ math.random( 1, #ZoneList ) ] -- Core.Zone#ZONE
local ToCoord = RandomZone:GetRandomCoordinate( 10 )
-- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task
local Route = {}
Route[#Route+1] = FromCoord:WaypointGround( 120 )
Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation )
local TaskRouteToZone = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolZones", ZoneList, Speed, Formation )
PatrolGroup:SetTaskWaypoint( Route[#Route], TaskRouteToZone ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
PatrolGroup:Route( Route, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
end
end
end
--- Return a Misson task to follow a given route defined by Points.
-- @param #CONTROLLABLE self
-- @param #table Points A table of route points.

View File

@@ -104,10 +104,10 @@ GROUP.Takeoff = {
GROUPTEMPLATE = {}
GROUPTEMPLATE.Takeoff = {
[GROUP.Takeoff.Air] = "Turning Point",
[GROUP.Takeoff.Runway] = "TakeOff",
[GROUP.Takeoff.Hot] = "TakeOffParkingHot",
[GROUP.Takeoff.Cold] = "TakeOffParking",
[GROUP.Takeoff.Air] = { "Turning Point", "Turning Point" },
[GROUP.Takeoff.Runway] = { "TakeOff", "From Runway" },
[GROUP.Takeoff.Hot] = { "TakeOffParkingHot", "From Parking Area Hot" },
[GROUP.Takeoff.Cold] = { "TakeOffParking", "From Parking Area" }
}
--- Create a new GROUP from a DCSGroup
@@ -938,10 +938,19 @@ end
-- @return #table
function GROUP:GetTemplate()
local GroupName = self:GetName()
self:E( GroupName )
return _DATABASE:GetGroupTemplate( GroupName )
return UTILS.DeepCopy( _DATABASE:GetGroupTemplate( GroupName ) )
end
--- Returns the group template route.points[] (the waypoints) from the @{DATABASE} (_DATABASE object).
-- @param #GROUP self
-- @return #table
function GROUP:GetTemplateRoutePoints()
local GroupName = self:GetName()
return UTILS.DeepCopy( _DATABASE:GetGroupTemplate( GroupName ).route.points )
end
--- Sets the controlled status in a Template.
-- @param #GROUP self
-- @param #boolean Controlled true is controlled, false is uncontrolled.
@@ -1161,9 +1170,9 @@ do -- Event Handling
-- @param Core.Event#EVENTS Event
-- @param #function EventFunction (optional) The function to be called when the event occurs for the GROUP.
-- @return #GROUP
function GROUP:HandleEvent( Event, EventFunction )
function GROUP:HandleEvent( Event, EventFunction, ... )
self:EventDispatcher():OnEventForGroup( self:GetName(), EventFunction, self, Event )
self:EventDispatcher():OnEventForGroup( self:GetName(), EventFunction, self, Event, ... )
return self
end

View File

@@ -166,6 +166,7 @@ function POSITIONABLE:GetCoordinate()
local PositionableCoordinate = COORDINATE:NewFromVec3( PositionableVec3 )
PositionableCoordinate:SetHeading( self:GetHeading() )
PositionableCoordinate:SetVelocity( self:GetVelocityMPS() )
self:T2( PositionableCoordinate )
return PositionableCoordinate
@@ -441,6 +442,23 @@ function POSITIONABLE:GetMessage( Message, Duration, Name ) --R2.1 changed calls
return nil
end
--- Returns a message of a specified type with the callsign embedded (if there is one).
-- @param #POSITIONABLE self
-- @param #string Message The message text
-- @param Core.Message#MESSAGE MessageType MessageType The message type.
-- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable.
-- @return Core.Message#MESSAGE
function POSITIONABLE:GetMessageType( Message, MessageType, Name ) -- R2.2 changed callsign and name and using GetMessageText
local DCSObject = self:GetDCSObject()
if DCSObject then
local MessageText = self:GetMessageText( Message, Name )
return MESSAGE:NewType( MessageText, MessageType )
end
return nil
end
--- Send a message to all coalitions.
-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message.
-- @param #POSITIONABLE self
@@ -484,6 +502,32 @@ function POSITIONABLE:MessageToCoalition( Message, Duration, MessageCoalition )
end
--- Send a message to a coalition.
-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message.
-- @param #POSITIONABLE self
-- @param #string Message The message text
-- @param Core.Message#MESSAGE.Type MessageType The message type that determines the duration.
-- @param Dcs.DCScoalition#coalition MessageCoalition The Coalition receiving the message.
function POSITIONABLE:MessageTypeToCoalition( Message, MessageType, MessageCoalition )
self:F2( { Message, MessageType } )
local Name = ""
local DCSObject = self:GetDCSObject()
if DCSObject then
if MessageCoalition == coalition.side.BLUE then
Name = "Blue coalition"
end
if MessageCoalition == coalition.side.RED then
Name = "Red coalition"
end
self:GetMessageType( Message, MessageType, Name ):ToCoalition( MessageCoalition )
end
return nil
end
--- Send a message to the red coalition.
-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message.
-- @param #POSITIONABLE self
@@ -556,6 +600,26 @@ function POSITIONABLE:MessageToGroup( Message, Duration, MessageGroup, Name )
return nil
end
--- Send a message of a message type to a @{Group}.
-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message.
-- @param #POSITIONABLE self
-- @param #string Message The message text
-- @param Core.Message#MESSAGE.Type MessageType The message type that determines the duration.
-- @param Wrapper.Group#GROUP MessageGroup The GROUP object receiving the message.
-- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable.
function POSITIONABLE:MessageTypeToGroup( Message, MessageType, MessageGroup, Name )
self:F2( { Message, MessageType } )
local DCSObject = self:GetDCSObject()
if DCSObject then
if DCSObject:isExist() then
self:GetMessageType( Message, MessageType, Name ):ToGroup( MessageGroup )
end
end
return nil
end
--- Send a message to a @{Set#SET_GROUP}.
-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message.
-- @param #POSITIONABLE self

View File

@@ -40,6 +40,7 @@ Functional/MissileTrainer.lua
Functional/AirbasePolice.lua
Functional/Detection.lua
Functional/Designate.lua
Functional/RAT.lua
AI/AI_Balancer.lua
AI/AI_A2A.lua

View File

@@ -1,89 +1,85 @@
env.info( '*** MOOSE DYNAMIC INCLUDE START *** ' )
env.info( 'Moose Generation Timestamp: 20170811_0800' )
local base = _G
__Moose = {}
__Moose.Include = function( IncludeFile )
if not __Moose.Includes[ IncludeFile ] then
__Moose.Includes[IncludeFile] = IncludeFile
local f = assert( base.loadfile( __Moose.ProgramPath .. IncludeFile ) )
if f == nil then
error ("Moose: Could not load Moose file " .. IncludeFile )
else
env.info( "Moose: " .. IncludeFile .. " dynamically loaded from " .. __Moose.ProgramPath )
return f()
end
end
env.info('*** MOOSE DYNAMIC INCLUDE START *** ')
env.info('Moose Generation Timestamp: 20170926_1846')
local base=_G
__Moose={}
__Moose.Include=function(IncludeFile)
if not __Moose.Includes[IncludeFile]then
__Moose.Includes[IncludeFile]=IncludeFile
local f=assert(base.loadfile(__Moose.ProgramPath..IncludeFile))
if f==nil then
error("Moose: Could not load Moose file "..IncludeFile)
else
env.info("Moose: "..IncludeFile.." dynamically loaded from "..__Moose.ProgramPath)
return f()
end
__Moose.ProgramPath = "Scripts/Moose/"
__Moose.Includes = {}
__Moose.Include( 'Utilities/Routines.lua' )
__Moose.Include( 'Utilities/Utils.lua' )
__Moose.Include( 'Core/Base.lua' )
__Moose.Include( 'Core/Report.lua' )
__Moose.Include( 'Core/Scheduler.lua' )
__Moose.Include( 'Core/ScheduleDispatcher.lua' )
__Moose.Include( 'Core/Event.lua' )
__Moose.Include( 'Core/Settings.lua' )
__Moose.Include( 'Core/Menu.lua' )
__Moose.Include( 'Core/Zone.lua' )
__Moose.Include( 'Core/Database.lua' )
__Moose.Include( 'Core/Set.lua' )
__Moose.Include( 'Core/Point.lua' )
__Moose.Include( 'Core/Message.lua' )
__Moose.Include( 'Core/Fsm.lua' )
__Moose.Include( 'Core/Radio.lua' )
__Moose.Include( 'Core/SpawnStatic.lua' )
__Moose.Include( 'Core/Cargo.lua' )
__Moose.Include( 'Core/Spot.lua' )
__Moose.Include( 'Wrapper/Object.lua' )
__Moose.Include( 'Wrapper/Identifiable.lua' )
__Moose.Include( 'Wrapper/Positionable.lua' )
__Moose.Include( 'Wrapper/Controllable.lua' )
__Moose.Include( 'Wrapper/Group.lua' )
__Moose.Include( 'Wrapper/Unit.lua' )
__Moose.Include( 'Wrapper/Client.lua' )
__Moose.Include( 'Wrapper/Static.lua' )
__Moose.Include( 'Wrapper/Airbase.lua' )
__Moose.Include( 'Wrapper/Scenery.lua' )
__Moose.Include( 'Functional/Scoring.lua' )
__Moose.Include( 'Functional/CleanUp.lua' )
__Moose.Include( 'Functional/Spawn.lua' )
__Moose.Include( 'Functional/Movement.lua' )
__Moose.Include( 'Functional/Sead.lua' )
__Moose.Include( 'Functional/Escort.lua' )
__Moose.Include( 'Functional/MissileTrainer.lua' )
__Moose.Include( 'Functional/AirbasePolice.lua' )
__Moose.Include( 'Functional/Detection.lua' )
__Moose.Include( 'Functional/Designate.lua' )
__Moose.Include( 'AI/AI_Balancer.lua' )
__Moose.Include( 'AI/AI_A2A.lua' )
__Moose.Include( 'AI/AI_A2A_Patrol.lua' )
__Moose.Include( 'AI/AI_A2A_Cap.lua' )
__Moose.Include( 'AI/AI_A2A_Gci.lua' )
__Moose.Include( 'AI/AI_A2A_Dispatcher.lua' )
__Moose.Include( 'AI/AI_Patrol.lua' )
__Moose.Include( 'AI/AI_Cap.lua' )
__Moose.Include( 'AI/AI_Cas.lua' )
__Moose.Include( 'AI/AI_Bai.lua' )
__Moose.Include( 'AI/AI_Formation.lua' )
__Moose.Include( 'Actions/Act_Assign.lua' )
__Moose.Include( 'Actions/Act_Route.lua' )
__Moose.Include( 'Actions/Act_Account.lua' )
__Moose.Include( 'Actions/Act_Assist.lua' )
__Moose.Include( 'Tasking/CommandCenter.lua' )
__Moose.Include( 'Tasking/Mission.lua' )
__Moose.Include( 'Tasking/Task.lua' )
__Moose.Include( 'Tasking/DetectionManager.lua' )
__Moose.Include( 'Tasking/Task_A2G_Dispatcher.lua' )
__Moose.Include( 'Tasking/Task_A2G.lua' )
__Moose.Include( 'Tasking/Task_A2A_Dispatcher.lua' )
__Moose.Include( 'Tasking/Task_A2A.lua' )
__Moose.Include( 'Tasking/Task_Cargo.lua' )
__Moose.Include( 'Moose.lua' )
BASE:TraceOnOff( true )
env.info( '*** MOOSE INCLUDE END *** ' )
end
end
__Moose.ProgramPath="Scripts/Moose/"
__Moose.Includes={}
__Moose.Include('Utilities/Routines.lua')
__Moose.Include('Utilities/Utils.lua')
__Moose.Include('Core/Base.lua')
__Moose.Include('Core/Report.lua')
__Moose.Include('Core/Scheduler.lua')
__Moose.Include('Core/ScheduleDispatcher.lua')
__Moose.Include('Core/Event.lua')
__Moose.Include('Core/Settings.lua')
__Moose.Include('Core/Menu.lua')
__Moose.Include('Core/Zone.lua')
__Moose.Include('Core/Database.lua')
__Moose.Include('Core/Set.lua')
__Moose.Include('Core/Point.lua')
__Moose.Include('Core/Message.lua')
__Moose.Include('Core/Fsm.lua')
__Moose.Include('Core/Radio.lua')
__Moose.Include('Core/SpawnStatic.lua')
__Moose.Include('Core/Cargo.lua')
__Moose.Include('Core/Spot.lua')
__Moose.Include('Wrapper/Object.lua')
__Moose.Include('Wrapper/Identifiable.lua')
__Moose.Include('Wrapper/Positionable.lua')
__Moose.Include('Wrapper/Controllable.lua')
__Moose.Include('Wrapper/Group.lua')
__Moose.Include('Wrapper/Unit.lua')
__Moose.Include('Wrapper/Client.lua')
__Moose.Include('Wrapper/Static.lua')
__Moose.Include('Wrapper/Airbase.lua')
__Moose.Include('Wrapper/Scenery.lua')
__Moose.Include('Functional/Scoring.lua')
__Moose.Include('Functional/CleanUp.lua')
__Moose.Include('Functional/Spawn.lua')
__Moose.Include('Functional/Movement.lua')
__Moose.Include('Functional/Sead.lua')
__Moose.Include('Functional/Escort.lua')
__Moose.Include('Functional/MissileTrainer.lua')
__Moose.Include('Functional/AirbasePolice.lua')
__Moose.Include('Functional/Detection.lua')
__Moose.Include('Functional/Designate.lua')
__Moose.Include('Functional/RAT.lua')
__Moose.Include('AI/AI_Balancer.lua')
__Moose.Include('AI/AI_A2A.lua')
__Moose.Include('AI/AI_A2A_Patrol.lua')
__Moose.Include('AI/AI_A2A_Cap.lua')
__Moose.Include('AI/AI_A2A_Gci.lua')
__Moose.Include('AI/AI_A2A_Dispatcher.lua')
__Moose.Include('AI/AI_Patrol.lua')
__Moose.Include('AI/AI_Cap.lua')
__Moose.Include('AI/AI_Cas.lua')
__Moose.Include('AI/AI_Bai.lua')
__Moose.Include('AI/AI_Formation.lua')
__Moose.Include('Actions/Act_Assign.lua')
__Moose.Include('Actions/Act_Route.lua')
__Moose.Include('Actions/Act_Account.lua')
__Moose.Include('Actions/Act_Assist.lua')
__Moose.Include('Tasking/CommandCenter.lua')
__Moose.Include('Tasking/Mission.lua')
__Moose.Include('Tasking/Task.lua')
__Moose.Include('Tasking/DetectionManager.lua')
__Moose.Include('Tasking/Task_A2G_Dispatcher.lua')
__Moose.Include('Tasking/Task_A2G.lua')
__Moose.Include('Tasking/Task_A2A_Dispatcher.lua')
__Moose.Include('Tasking/Task_A2A.lua')
__Moose.Include('Tasking/Task_Cargo.lua')
__Moose.Include('Moose.lua')
BASE:TraceOnOff(true)
env.info('*** MOOSE INCLUDE END *** ')

View File

@@ -1,362 +0,0 @@
[SIZE=7]MOOSE Release 2.1.0[/SIZE]
Finally it is here, release 2.1.0 of MOOSE!
It took some time to prepare this release, as it was a lot of work to get the building blocks of the framework developed and tested. You'll find in this release a lot of new features as well as a couple of important bug fixes.
Release 2.1.0 is now published into the [B]master-release-2.1[/B] branch of this repository on github.
You can download the file moose.lua below to use MOOSE in your missions.
The moose.lua file is also located [URL="https://github.com/FlightControl-Master/MOOSE/blob/master-release-2.1/Moose%20Mission%20Setup/Moose.lua"]here[/URL] in the [B]master-release-2.1[/B] branch.
Those who are using the [B]master[/B] branch can continue to beta test, as new bleeding edge features will be added soon in preparation for release 2.2.0! There are many topics on the agenda to be added.
[B]This release would not have been possible without the help and contribution of many members of this community. THANK YOU![/B]
[SIZE=6]In summary:[/SIZE]
This release brings you [B]an improved tasking mechanism[/B].
Tasking is the system in MOOSE that allows to:
* Execute [B]co-op[/B] missions and tasks
* [B]Detect[/B] targets dynamically
* Define new tasks [B]dynamically[/B]
* Execute the tasks
* Complete the mission [B]goals[/B]
* Extensive menu system and briefings/reports for [B]player interaction[/B]
* Improved Scoring of mission goal achievements, and task achievements.
On top, release brings you new functionality by the introduction of new classes to:
* [B]Designate targets[/B] (lase, smoke or illuminate targets) by AI, assisting your attack. Allows to drop laser guides bombs.
* A new [B]tasking[/B] system to [B]transport cargo[/B] of various types
* Dynamically [B]spawn static objects[/B]
* Improved [B]coordinate system[/B]
* Build [B]large formations[/B], like bombers flying to a target area
[SIZE=6]1. TASKING SYSTEM![/SIZE]
A lot of work has been done in improving the tasking framework within MOOSE.
**The tasking system comes with TASK DISPATCHING mechanisms, that DYNAMICALLY
allocate new tasks based on the tactical or strategical situation in the mission!!!
These tasks can then be engaged upon by the players!!!**
The [URL="http://flightcontrol-master.github.io/MOOSE/Documentation/Task_A2G_Dispatcher.html"]TASK_A2G_DISPATCHER[/URL] class implements the dynamic dispatching of tasks upon groups of detected units determined a Set of FAC (groups). The FAC will detect units, will group them, and will dispatch Tasks to groups of players. Depending on the type of target detected, different tasks will be dispatched. Find a summary below describing for which situation a task type is created:
* [B]CAS Task[/B]: Is created when there are enemy ground units within range of the FAC, while there are friendly units in the FAC perimeter.
* [B]BAI Task[/B]: Is created when there are enemy ground units within range of the FAC, while there are NO other friendly units within the FAC perimeter.
* [B]SEAD Task[/B]: Is created when there are enemy ground units wihtin range of the FAC, with air search radars.
More TASK_... dispatcher classes are to come in the future, like A2A, G2G, etc...
Improvements on the TASKING are in summary:
* A COMMANDCENTER has a dedicated menu.
* A MISSION has a dedicated menu system.
* A MISSION has a briefing report.
* A MISSION has dedicated status reports.
* A MISSION has for each TASK TYPE a menu.
* A MISSION has for each TASK TYPE a dedicated menu system for each TASK defined.
* A MISSION has an "assigned" task menu that contains menu actions relevant to the assigned task.
* A TASK (of various types) has a dedicated menu system.
* A TASK has a briefing report.
* A TASK has dedicated status reports.
* Player reports can be retrieved that explain which player is at which task.
* ...
TASKING is vast, and at the moment there is too much to explain.
[B]The best way to explore the TASKING is to TRY it...[/B]
I suggest you have a look at the [URL="https://www.youtube.com/watch?v=v2Us8SS1-44&t=1070s"]GORI Valley Mission - Iteration 3[/URL].
Many people have contributed in the testing of the mechanism, especially:
@baluballa, @doom, @whiplash
[SIZE=6]2. New MOOSE classes have been added.[/SIZE]
MOOSE 2.1.0 comes with new classes that extends the functionality of the MOOSE framework and allow you to do new things in your missions:
[SIZE=5]2.1. Target designation by laser, smoke or illumination.[/SIZE]
[URL="http://flightcontrol-master.github.io/MOOSE/Documentation/Designate.html"]DESIGNATE[/URL] is orchestrating the designation of potential targets executed by a Recce group,
and communicates these to a dedicated attacking group of players,
so that following a dynamically generated menu system,
each detected set of potential targets can be lased or smoked...
Targets can be:
* [B]Lased[/B] for a period of time.
* [B]Smoked[/B]. Artillery or airplanes with Illuminatino ordonance need to be present. (WIP, but early demo ready.)
* [B]Illuminated[/B] through an illumination bomb. Artillery or airplanes with Illuminatino ordonance need to be present. (WIP, but early demo ready.
This class was made with the help of @EasyEB and many others.
[URL="https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0dQ9UKQMb7YL8z2sKSqemH"]DESIGNATE is demonstrated on youtube[/URL]
DESIGNATE demonstration missions:
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/DES%20-%20Designation"]DES - Designation[/URL]
[SIZE=5]2.2. Transport cargo of different types to various locations as a human task within a mission.[/SIZE]
The Moose framework provides various CARGO classes that allow DCS physical or logical objects to be transported or sling loaded by Carriers.
The CARGO_ classes, as part of the moose core, are able to Board, Load, UnBoard and UnLoad cargo between Carrier units.
This collection of classes in this module define tasks for human players to handle these cargo objects.
Cargo can be transported, picked-up, deployed and sling-loaded from and to other places.
[URL="http://flightcontrol-master.github.io/MOOSE/Documentation/Task_Cargo.html#TASK_CARGO_TRANSPORT"]TASK_CARGO_TRANSPORT[/URL] defines a task for a human player to transport a set of cargo between various zones.
It is the first class that forms part of the TASK_CARGO classes suite.
The TASK_CARGO classes provide you with a flexible tasking sytem,
that allows you to transport cargo of various types between various locations
and various dedicated deployment zones.
A human player can join the battle field in a client airborne slot or a ground vehicle within the CA module (ALT-J).
The player needs to accept the task from the task overview list within the mission, using the radio menus.
Once the TASK_CARGO_TRANSPORT is assigned to the player and accepted by the player, the player will obtain
an extra [B]Cargo Handling Radio Menu[/B] that contains the CARGO objects that need to be transported.
Cargo can be transported towards different [B]Deployment Zones[/B], but can also be deployed anywhere within the battle field.
The Cargo Handling Radio Menu system allows to execute [B]various actions[/B] to handle the cargo.
In the menu, you'll find for each CARGO, that is part of the scope of the task, various actions that can be completed.
Depending on the location of your Carrier unit, the menu options will vary.
The [URL="http://flightcontrol-master.github.io/MOOSE/Documentation/Cargo.html#CARGO_GROUP"]CARGO_GROUP[/URL] class defines a
cargo that is represented by a GROUP object within the simulator, and can be transported by a carrier.
The [URL="http://flightcontrol-master.github.io/MOOSE/Documentation/Cargo.html#CARGO_UNIT"]CARGO_UNIT[/URL] class defines a
cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier.
Mission designers can use the [URL="http://flightcontrol-master.github.io/MOOSE/Documentation/Set.html#SET_CARGO"]SET_CARGO[/URL]
class to build sets of cargos.
Note 1: [B]Various other CARGO classes are defined and are WIP[/B].
Now that the foundation for Cargo handling is getting form, future releases will bring other types of CARGO handling
classes to the MOOSE framework quickly. Sling-loading, package, beacon and other types of CARGO will be released soon.
Note 2: [B]AI_CARGO has been renamed to CARGO and now forms part of the Core or MOOSE[/B].
If you were using AI_CARGO in your missions, please rename AI_CARGO with CARGO...
TASK_TRANSPORT_CARGO is demonstrated at the [URL="https://www.youtube.com/watch?v=v2Us8SS1-44&t=1070s"]GORI Valley Mission - Iteration 4[/URL]
TASK_TRANSPORT_CARGO demonstration missions:
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/TSK%20-%20Task%20Modelling/TSK-110%20-%20Ground%20-%20Transport%20Cargo%20Group"]TSK-110 - Ground - Transport Cargo Group[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/TSK%20-%20Task%20Modelling/TSK-210%20-%20Helicopter%20-%20Transport%20Cargo%20Group"]TSK-210 - Helicopter - Transport Cargo Group[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/TSK%20-%20Task%20Modelling/TSK-211%20-%20Helicopter%20-%20Transport%20Multiple%20Cargo%20Groups"]TSK-211 - Helicopter - Transport Multiple Cargo Groups[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/TSK%20-%20Task%20Modelling/TSK-212%20-%20Helicopter%20-%20Cargo%20handle%20PickedUp%20and%20Deployed%20events"]TSK-212 - Helicopter - Cargo handle PickedUp and Deployed events[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/TSK%20-%20Task%20Modelling/TSK-213%20-%20Helicopter%20-%20Cargo%20Group%20Destroyed"]TSK-213 - Helicopter - Cargo Group Destroyed[/URL]
[SIZE=5]2.3. Dynamically spawn STATIC objects into your mission.[/SIZE]
The [URL="http://flightcontrol-master.github.io/MOOSE/Documentation/SpawnStatic.html#SPAWNSTATIC"]SPAWNSTATIC[/URL] class allows to spawn dynamically new Statics.
By creating a copy of an existing static object template as defined in the Mission Editor (ME), SPAWNSTATIC can retireve the properties of the defined static object template (like type, category etc), and "copy" these properties to create a new static object and place it at the desired coordinate.
New spawned Statics get the same name as the name of the template Static, or gets the given name when a new name is provided at the Spawn method.
SPAWNSTATIC demonstration missions:
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/SPS%20-%20Spawning%20Statics/SPS-100%20-%20Simple%20Spawning"]SPS-100 - Simple Spawning[/URL]
[SIZE=5]2.4. Better coordinate management in MGRS or LLor LLDecimal.[/SIZE]
The [URL="http://flightcontrol-master.github.io/MOOSE/Documentation/Point.html#COORDINATE"]COORDINATE[/URL] class
defines a 2D coordinate in the simulator. A COORDINATE can be expressed in LL or in MGRS.
[SIZE=5]2.5. Improved scoring system[/SIZE]
Scoring is implemented throught the [URL="http://flightcontrol-master.github.io/MOOSE/Documentation/Scoring.html"]SCORING[/URL] class.
The scoring system has been improved a lot! Now, the scoring is correctly counting scores on normal units, statics and scenary objects.
Specific scores can be registered for specific targets. The scoring works together with the tasking system, so players can achieve
additional scores when they achieve goals!
SCORING demonstration missions:
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCO%20-%20Scoring/SCO-100%20-%20Scoring%20of%20Statics"]SCO-100 - Scoring of Statics[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCO%20-%20Scoring/SCO-101%20-%20Scoring%20Client%20to%20Client"]SCO-101 - Scoring Client to Client[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCO%20-%20Scoring/SCO-500%20-%20Scoring%20Multi%20Player%20Demo%20Mission%201"]SCO-500 - Scoring Multi Player Demo Mission 1[/URL]
[SIZE=5]2.6. Beacons and Radio[/SIZE]
The Radio contains 2 classes : RADIO and BEACON
What are radio communications in DCS ?
* Radio transmissions consist of [B]sound files[/B] that are broadcasted on a specific [B]frequency[/B] (e.g. 115MHz) and [B]modulation[/B] (e.g. AM),
* They can be [B]subtitled[/B] for a specific [B]duration[/B], the [B]power[/B] in Watts of the transmiter's antenna can be set, and the transmission can be [B]looped[/B].
These classes are the work of @Grey-Echo.
RADIO and BEACON demonstration missions:
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/RAD%20-%20Radio/RAD-000%20-%20Transmission%20from%20Static"]RAD-000 - Transmission from Static[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/RAD%20-%20Radio/RAD-001%20-%20Transmission%20from%20UNIT%20or%20GROUP"]RAD-001 - Transmission from UNIT or GROUP[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/RAD%20-%20Radio/RAD-002%20-%20Transmission%20Tips%20and%20Tricks"]RAD-002 - Transmission Tips and Tricks[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/RAD%20-%20Radio/RAD-010%20-%20Beacons"] RAD-010 - Beacons[/URL]
[SIZE=5]2.7. Build large formations of AI.[/SIZE]
[URL="http://flightcontrol-master.github.io/MOOSE/Documentation/AI_Formation.html"]AI_FORMATION[/URL] makes AI @{GROUP}s fly in formation of various compositions.
The AI_FORMATION class models formations in a different manner than the internal DCS formation logic!!!
The purpose of the class is to:
* Make formation building a process that can be managed while in flight, rather than a task.
* Human players can guide formations, consisting of larget planes.
* Build large formations (like a large bomber field).
* Form formations that DCS does not support off the shelve.
AI_FORMATION Demo Missions: [URL=""]FOR - AI Group Formation[/URL]
AI_FORMATION demonstration missions:
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-100%20-%20Bomber%20Left%20Line%20Formation"]FOR-100 - Bomber Left Line Formation[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-101%20-%20Bomber%20Right%20Line%20Formation"]FOR-101 - Bomber Right Line Formation[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-102%20-%20Bomber%20Left%20Wing%20Formation"]FOR-102 - Bomber Left Wing Formation[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-103%20-%20Bomber%20Right%20Wing%20Formation"]FOR-103 - Bomber Right Wing Formation[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-104%20-%20Bomber%20Center%20Wing%20Formation"]FOR-104 - Bomber Center Wing Formation[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-105%20-%20Bomber%20Trail%20Formation"]FOR-105 - Bomber Trail Formation[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-106%20-%20Bomber%20Box%20Formation"]FOR-106 - Bomber Box Formation[/URL]
Note: The AI_FORMATION is currently a first version showing the potential, a "building block". From this class, further classes will be derived and the class will be fine-tuned.
[SIZE=6]3. A lot of components have been reworked and bugs have been fixed.[/SIZE]
[SIZE=5]3.1. Better event handling and event dispatching.[/SIZE]
The underlying mechanisms to handle DCS events has been improved. Bugs have been fixed.
The MISSION_END event is now also supported.
[SIZE=5]2.2. Cargo handling has been made much better now.[/SIZE]
As a result, some of the WIP cargo classes that were defined earlier are still WIP.
But as mentioned earlier, new CARGO classes can be published faster now.
The framework is now more consistent internally.
[SIZE=6]3. A lot of new methods have been defined in several existing or new classes.[/SIZE]
AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefing ) --R2.1
AI_FORMATION:TestSmokeDirectionVector( SmokeDirection ) --R2.1
AI_FORMATION:onafterFormationLine( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationTrail( FollowGroupSet, From , Event , To, XStart, XSpace, YStart ) --R2.1
AI_FORMATION:onafterFormationStack( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace ) --R2.1
AI_FORMATION:onafterFormationLeftLine( FollowGroupSet, From , Event , To, XStart, YStart, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationRightLine( FollowGroupSet, From , Event , To, XStart, YStart, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationLeftWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationRightWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationCenterWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationVic( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationBox( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace, ZLevels ) --R2.1
AI_FORMATION:SetFlightRandomization( FlightRandomization ) --R2.1
AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1
CARGO:GetName()
CARGO:GetObjectName()
DATABASE:ForEachStatic( IteratorFunction, FinalizeFunction, ... )
EVENT:Reset( EventObject ) --R2.1
POINT_VEC3:IsLOS( ToPointVec3 ) --R2.1
COORDINATE:New( x, y, LandHeightAdd ) --R2.1 Fixes issue #424.
COORDINATE:NewFromVec2( Vec2, LandHeightAdd ) --R2.1 Fixes issue #424.
COORDINATE:NewFromVec3( Vec3 ) --R2.1 Fixes issue #424.
COORDINATE:ToStringLL( LL_Accuracy, LL_DMS ) --R2.1 Fixes issue #424.
COORDINATE:ToStringMGRS( MGRS_Accuracy ) --R2.1 Fixes issue #424.
COORDINATE:ToString() --R2.1 Fixes issue #424.
COORDINATE:CoordinateMenu( RootMenu ) --R2.1 Fixes issue #424.
COORDINATE:MenuSystem( System ) --R2.1 Fixes issue #424.
COORDINATE:MenuLL_Accuracy( LL_Accuracy ) --R2.1 Fixes issue #424.
COORDINATE:MenuLL_DMS( LL_DMS ) --R2.1 Fixes issue #424.
COORDINATE:MenuMGRS_Accuracy( MGRS_Accuracy ) --R2.1 Fixes issue #424.
SET_BASE:FilterDeads() --R2.1 allow deads to be filtered to automatically handle deads in the collection.
SET_BASE:FilterCrashes() --R2.1 allow crashes to be filtered to automatically handle crashes in the collection.
SET_UNIT:ForEachUnitPerThreatLevel( FromThreatLevel, ToThreatLevel, IteratorFunction, ... ) --R2.1 Threat Level implementation
SET_CARGO:New() --R2.1
SET_CARGO:AddCargosByName( AddCargoNames ) --R2.1
SET_CARGO:RemoveCargosByName( RemoveCargoNames ) --R2.1
SET_CARGO:FindCargo( CargoName ) --R2.1
SET_CARGO:FilterCoalitions( Coalitions ) --R2.1
SET_CARGO:FilterTypes( Types ) --R2.1
SET_CARGO:FilterCountries( Countries ) --R2.1
SET_CARGO:FilterPrefixes( Prefixes ) --R2.1
SET_CARGO:FilterStart() --R2.1
SET_CARGO:AddInDatabase( Event ) --R2.1
SET_CARGO:FindInDatabase( Event ) --R2.1
SET_CARGO:ForEachCargo( IteratorFunction, ... ) --R2.1
SET_CARGO:FindNearestCargoFromPointVec2( PointVec2 ) --R2.1
SET_CARGO:IsIncludeObject( MCargo ) --R2.1
SET_CARGO:OnEventNewCargo( EventData ) --R2.1
SET_CARGO:OnEventDeleteCargo( EventData ) --R2.1 SpawnStatic.lua (5 matches)
SPAWNSTATIC:NewFromStatic( SpawnTemplatePrefix, CountryID ) --R2.1
SPAWNSTATIC:NewFromType( SpawnTypeName, SpawnShapeName, SpawnCategory, CountryID ) --R2.1
SPAWNSTATIC:SpawnFromPointVec2( PointVec2, Heading, NewName ) --R2.1
SPAWNSTATIC:SpawnFromZone( Zone, Heading, NewName ) --R2.1
ZONE_BASE:GetCoordinate( Height ) --R2.1
DESIGNATE:SetFlashStatusMenu( FlashMenu ) --R2.1
DESIGNATE:SetLaserCodes( LaserCodes ) --R2.1
DESIGNATE:GenerateLaserCodes() --R2.1
DESIGNATE:SetAutoLase( AutoLase ) --R2.1
DESIGNATE:SetThreatLevelPrioritization( Prioritize ) --R2.1
DETECTION_BASE:CleanDetectionItems() --R2.1 Clean the DetectionItems list
DETECTION_BASE:GetDetectedItemID( Index ) --R2.1
DETECTION_BASE:GetDetectedID( Index ) --R2.1
DETECTION_AREAS:DetectedReportDetailed() --R2.1 Fixed missing report
REPORT:HasText() --R2.1
REPORT:SetIndent( Indent ) --R2.1
REPORT:AddIndent( Text ) --R2.1
MISSION:GetMenu( TaskGroup ) -- R2.1 -- Changed Menu Structure
TASK:SetMenu( MenuTime ) --R2.1 Mission Reports and Task Reports added. Fixes issue #424.
TASK:ReportSummary() --R2.1 fixed report. Now nicely formatted and contains the info required.
TASK:ReportOverview() --R2.1 fixed report. Now nicely formatted and contains the info required.
TASK:GetPlayerCount() --R2.1 Get a count of the players.
TASK:GetPlayerNames() --R2.1 Get a map of the players.
TASK:ReportDetails() --R2.1 fixed report. Now nicely formatted and contains the info required.
UTILS.tostringMGRS = function(MGRS, acc) --R2.1
POSITIONABLE:GetBoundingBox() --R2.1
POSITIONABLE:GetHeight() --R2.1
POSITIONABLE:GetMessageText( Message, Name ) --R2.1 added
POSITIONABLE:GetMessage( Message, Duration, Name ) --R2.1 changed callsign and name and using GetMessageText
POSITIONABLE:MessageToSetGroup( Message, Duration, MessageSetGroup, Name ) --R2.1
POSITIONABLE:GetRadio() --R2.1
POSITIONABLE:GetBeacon() --R2.1
POSITIONABLE:LaseUnit( Target, LaserCode, Duration ) --R2.1
POSITIONABLE:LaseOff() --R2.1
POSITIONABLE:IsLasing() --R2.1
POSITIONABLE:GetSpot() --R2.1
POSITIONABLE:GetLaserCode() --R2.1
UNIT:IsDetected( TargetUnit ) --R2.1
UNIT:IsLOS( TargetUnit ) --R2.1

View File

@@ -1,363 +0,0 @@
# MOOSE Release 2.1.0
Finally it is here, release 2.1.0 of MOOSE!
It took some time to prepare this release, as it was a lot of work to get the building blocks of the framework developed and tested. You'll find in this release a lot of new features as well as a couple of important bug fixes.
Release 2.1.0 is now published into the **master-release-2.1** branch of this repository on github.
You can download the file moose.lua below to use MOOSE in your missions.
The moose.lua file is also located [here](https://github.com/FlightControl-Master/MOOSE/blob/master-release-2.1/Moose%20Mission%20Setup/Moose.lua) in the **master-release-2.1** branch.
Those who are using the **master** branch can continue to beta test, as new bleeding edge features will be added soon in preparation for release 2.2.0! There are many topics on the agenda to be added.
**This release would not have been possible without the help and contribution of many
members of this community. THANK YOU!**
## In summary:
This release brings you **an improved tasking mechanism**.
Tasking is the system in MOOSE that allows to:
* Execute **co-op** missions and tasks
* **Detect** targets dynamically
* Define new tasks **dynamically**
* Execute the tasks
* Complete the mission **goals**
* Extensive menu system and briefings/reports for **player interaction**
* Improved Scoring of mission goal achievements, and task achievements.
On top, release brings you new functionality by the introduction of new classes to:
* **Designate targets** (lase, smoke or illuminate targets) by AI, assisting your attack. Allows to drop laser guides bombs.
* A new **tasking** system to **transport cargo** of various types
* Dynamically **spawn static objects**
* Improved **coordinate system**
* Build **large formations**, like bombers flying to a target area
## 1. TASKING SYSTEM!
A lot of work has been done in improving the tasking framework within MOOSE.
**The tasking system comes with TASK DISPATCHING mechanisms, that DYNAMICALLY
allocate new tasks based on the tactical or strategical situation in the mission!!!
These tasks can then be engaged upon by the players!!!**
The [TASK\_A2G\_DISPATCHER](http://flightcontrol-master.github.io/MOOSE/Documentation/Task_A2G_Dispatcher.html) class implements the dynamic dispatching of tasks upon groups of detected units determined a Set of FAC (groups). The FAC will detect units, will group them, and will dispatch Tasks to groups of players. Depending on the type of target detected, different tasks will be dispatched. Find a summary below describing for which situation a task type is created:
* **CAS Task**: Is created when there are enemy ground units within range of the FAC, while there are friendly units in the FAC perimeter.
* **BAI Task**: Is created when there are enemy ground units within range of the FAC, while there are NO other friendly units within the FAC perimeter.
* **SEAD Task**: Is created when there are enemy ground units wihtin range of the FAC, with air search radars.
More TASK_... dispatcher classes are to come in the future, like A2A, G2G, etc...
Improvements on the TASKING are in summary:
* A COMMANDCENTER has a dedicated menu.
* A MISSION has a dedicated menu system.
* A MISSION has a briefing report.
* A MISSION has dedicated status reports.
* A MISSION has for each TASK TYPE a menu.
* A MISSION has for each TASK TYPE a dedicated menu system for each TASK defined.
* A MISSION has an "assigned" task menu that contains menu actions relevant to the assigned task.
* A TASK (of various types) has a dedicated menu system.
* A TASK has a briefing report.
* A TASK has dedicated status reports.
* Player reports can be retrieved that explain which player is at which task.
* ...
TASKING is vast, and at the moment there is too much to explain.
**The best way to explore the TASKING is to TRY it...**
I suggest you have a look at the [GORI Valley Mission - Iteration 3](https://www.youtube.com/watch?v=v2Us8SS1-44&t=1070s).
Many people have contributed in the testing of the mechanism, especially:
@baluballa, @doom, @whiplash
## 2. New MOOSE classes have been added.
MOOSE 2.1.0 comes with new classes that extends the functionality of the MOOSE framework and allow you to do new things in your missions:
### 2.1. Target designation by laser, smoke or illumination.
[DESIGNATE](http://flightcontrol-master.github.io/MOOSE/Documentation/Designate.html) is orchestrating the designation of potential targets executed by a Recce group,
and communicates these to a dedicated attacking group of players,
so that following a dynamically generated menu system,
each detected set of potential targets can be lased or smoked...
Targets can be:
* **Lased** for a period of time.
* **Smoked**. Artillery or airplanes with Illuminatino ordonance need to be present. (WIP, but early demo ready.)
* **Illuminated** through an illumination bomb. Artillery or airplanes with Illuminatino ordonance need to be present. (WIP, but early demo ready.
This class was made with the help of @EasyEB and many others.
[DESIGNATE is demonstrated on youtube](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0dQ9UKQMb7YL8z2sKSqemH)
DESIGNATE demonstration missions:
* [DES - Designation](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/DES%20-%20Designation)
### 2.2. Transport cargo of different types to various locations as a human task within a mission.
The Moose framework provides various CARGO classes that allow DCS physical or logical objects to be transported or sling loaded by Carriers.
The CARGO_ classes, as part of the moose core, are able to Board, Load, UnBoard and UnLoad cargo between Carrier units.
This collection of classes in this module define tasks for human players to handle these cargo objects.
Cargo can be transported, picked-up, deployed and sling-loaded from and to other places.
[TASK\_CARGO\_TRANSPORT](http://flightcontrol-master.github.io/MOOSE/Documentation/Task_Cargo.html#TASK_CARGO_TRANSPORT) defines a task for a human player to transport a set of cargo between various zones.
It is the first class that forms part of the TASK_CARGO classes suite.
The TASK_CARGO classes provide you with a flexible tasking sytem,
that allows you to transport cargo of various types between various locations
and various dedicated deployment zones.
A human player can join the battle field in a client airborne slot or a ground vehicle within the CA module (ALT-J).
The player needs to accept the task from the task overview list within the mission, using the radio menus.
Once the TASK\_CARGO\_TRANSPORT is assigned to the player and accepted by the player, the player will obtain
an extra **Cargo Handling Radio Menu** that contains the CARGO objects that need to be transported.
Cargo can be transported towards different **Deployment Zones**, but can also be deployed anywhere within the battle field.
The Cargo Handling Radio Menu system allows to execute **various actions** to handle the cargo.
In the menu, you'll find for each CARGO, that is part of the scope of the task, various actions that can be completed.
Depending on the location of your Carrier unit, the menu options will vary.
The [CARGO_GROUP](http://flightcontrol-master.github.io/MOOSE/Documentation/Cargo.html#CARGO_GROUP) class defines a
cargo that is represented by a GROUP object within the simulator, and can be transported by a carrier.
The [CARGO_UNIT](http://flightcontrol-master.github.io/MOOSE/Documentation/Cargo.html#CARGO_UNIT) class defines a
cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier.
Mission designers can use the [SET_CARGO](http://flightcontrol-master.github.io/MOOSE/Documentation/Set.html#SET_CARGO)
class to build sets of cargos.
Note 1: **Various other CARGO classes are defined and are WIP**.
Now that the foundation for Cargo handling is getting form, future releases will bring other types of CARGO handling
classes to the MOOSE framework quickly. Sling-loading, package, beacon and other types of CARGO will be released soon.
Note 2: **AI_CARGO has been renamed to CARGO and now forms part of the Core or MOOSE**.
If you were using AI_CARGO in your missions, please rename AI_CARGO with CARGO...
TASK\_TRANSPORT\_CARGO is demonstrated at the [GORI Valley Mission - Iteration 4](https://www.youtube.com/watch?v=v2Us8SS1-44&t=1070s)
TASK_TRANSPORT_CARGO demonstration missions:
* [TSK-110 - Ground - Transport Cargo Group](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/TSK%20-%20Task%20Modelling/TSK-110%20-%20Ground%20-%20Transport%20Cargo%20Group)
* [TSK-210 - Helicopter - Transport Cargo Group](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/TSK%20-%20Task%20Modelling/TSK-210%20-%20Helicopter%20-%20Transport%20Cargo%20Group)
* [TSK-211 - Helicopter - Transport Multiple Cargo Groups](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/TSK%20-%20Task%20Modelling/TSK-211%20-%20Helicopter%20-%20Transport%20Multiple%20Cargo%20Groups)
* [TSK-212 - Helicopter - Cargo handle PickedUp and Deployed events](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/TSK%20-%20Task%20Modelling/TSK-212%20-%20Helicopter%20-%20Cargo%20handle%20PickedUp%20and%20Deployed%20events)
* [TSK-213 - Helicopter - Cargo Group Destroyed](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/TSK%20-%20Task%20Modelling/TSK-213%20-%20Helicopter%20-%20Cargo%20Group%20Destroyed)
### 2.3. Dynamically spawn STATIC objects into your mission.
The [SPAWNSTATIC](http://flightcontrol-master.github.io/MOOSE/Documentation/SpawnStatic.html#SPAWNSTATIC) class allows to spawn dynamically new Statics.
By creating a copy of an existing static object template as defined in the Mission Editor (ME), SPAWNSTATIC can retireve the properties of the defined static object template (like type, category etc), and "copy" these properties to create a new static object and place it at the desired coordinate.
New spawned Statics get the same name as the name of the template Static, or gets the given name when a new name is provided at the Spawn method.
SPAWNSTATIC demonstration missions:
* [SPS-100 - Simple Spawning](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/SPS%20-%20Spawning%20Statics/SPS-100%20-%20Simple%20Spawning)
### 2.4. Better coordinate management in MGRS or LLor LLDecimal.
The [COORDINATE](http://flightcontrol-master.github.io/MOOSE/Documentation/Point.html#COORDINATE) class
defines a 2D coordinate in the simulator. A COORDINATE can be expressed in LL or in MGRS.
### 2.5. Improved scoring system
Scoring is implemented throught the [SCORING](http://flightcontrol-master.github.io/MOOSE/Documentation/Scoring.html) class.
The scoring system has been improved a lot! Now, the scoring is correctly counting scores on normal units, statics and scenary objects.
Specific scores can be registered for specific targets. The scoring works together with the tasking system, so players can achieve
additional scores when they achieve goals!
SCORING demonstration missions:
* [SCO-100 - Scoring of Statics](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCO%20-%20Scoring/SCO-100%20-%20Scoring%20of%20Statics)
* [SCO-101 - Scoring Client to Client](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCO%20-%20Scoring/SCO-101%20-%20Scoring%20Client%20to%20Client)
* [SCO-500 - Scoring Multi Player Demo Mission 1](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCO%20-%20Scoring/SCO-500%20-%20Scoring%20Multi%20Player%20Demo%20Mission%201)
### 2.6. Beacons and Radio
The Radio contains 2 classes : RADIO and BEACON
What are radio communications in DCS ?
* Radio transmissions consist of **sound files** that are broadcasted on a specific **frequency** (e.g. 115MHz) and **modulation** (e.g. AM),
* They can be **subtitled** for a specific **duration**, the **power** in Watts of the transmiter's antenna can be set, and the transmission can be **looped**.
These classes are the work of @Grey-Echo.
RADIO and BEACON demonstration missions:
* [RAD-000 - Transmission from Static](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/RAD%20-%20Radio/RAD-000%20-%20Transmission%20from%20Static)
* [RAD-001 - Transmission from UNIT or GROUP](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/RAD%20-%20Radio/RAD-001%20-%20Transmission%20from%20UNIT%20or%20GROUP)
* [RAD-002 - Transmission Tips and Tricks](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/RAD%20-%20Radio/RAD-002%20-%20Transmission%20Tips%20and%20Tricks)
* [ RAD-010 - Beacons](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/RAD%20-%20Radio/RAD-010%20-%20Beacons)
### 2.7. Build large formations of AI.
[AI_FORMATION](http://flightcontrol-master.github.io/MOOSE/Documentation/AI_Formation.html) makes AI @{GROUP}s fly in formation of various compositions.
The AI_FORMATION class models formations in a different manner than the internal DCS formation logic!!!
The purpose of the class is to:
* Make formation building a process that can be managed while in flight, rather than a task.
* Human players can guide formations, consisting of larget planes.
* Build large formations (like a large bomber field).
* Form formations that DCS does not support off the shelve.
AI_FORMATION Demo Missions: [FOR - AI Group Formation]()
AI\_FORMATION demonstration missions:
* [FOR-100 - Bomber Left Line Formation](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-100%20-%20Bomber%20Left%20Line%20Formation)
* [FOR-101 - Bomber Right Line Formation](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-101%20-%20Bomber%20Right%20Line%20Formation)
* [FOR-102 - Bomber Left Wing Formation](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-102%20-%20Bomber%20Left%20Wing%20Formation)
* [FOR-103 - Bomber Right Wing Formation](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-103%20-%20Bomber%20Right%20Wing%20Formation)
* [FOR-104 - Bomber Center Wing Formation](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-104%20-%20Bomber%20Center%20Wing%20Formation)
* [FOR-105 - Bomber Trail Formation](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-105%20-%20Bomber%20Trail%20Formation)
* [FOR-106 - Bomber Box Formation](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-106%20-%20Bomber%20Box%20Formation)
Note: The AI_FORMATION is currently a first version showing the potential, a "building block". From this class, further classes will be derived and the class will be fine-tuned.
## 3. A lot of components have been reworked and bugs have been fixed.
### 3.1. Better event handling and event dispatching.
The underlying mechanisms to handle DCS events has been improved. Bugs have been fixed.
The MISSION_END event is now also supported.
### 2.2. Cargo handling has been made much better now.
As a result, some of the WIP cargo classes that were defined earlier are still WIP.
But as mentioned earlier, new CARGO classes can be published faster now.
The framework is now more consistent internally.
## 3. A lot of new methods have been defined in several existing or new classes.
AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefing ) --R2.1
AI_FORMATION:TestSmokeDirectionVector( SmokeDirection ) --R2.1
AI_FORMATION:onafterFormationLine( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationTrail( FollowGroupSet, From , Event , To, XStart, XSpace, YStart ) --R2.1
AI_FORMATION:onafterFormationStack( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace ) --R2.1
AI_FORMATION:onafterFormationLeftLine( FollowGroupSet, From , Event , To, XStart, YStart, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationRightLine( FollowGroupSet, From , Event , To, XStart, YStart, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationLeftWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationRightWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationCenterWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationVic( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationBox( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace, ZLevels ) --R2.1
AI_FORMATION:SetFlightRandomization( FlightRandomization ) --R2.1
AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1
CARGO:GetName()
CARGO:GetObjectName()
DATABASE:ForEachStatic( IteratorFunction, FinalizeFunction, ... )
EVENT:Reset( EventObject ) --R2.1
POINT_VEC3:IsLOS( ToPointVec3 ) --R2.1
COORDINATE:New( x, y, LandHeightAdd ) --R2.1 Fixes issue #424.
COORDINATE:NewFromVec2( Vec2, LandHeightAdd ) --R2.1 Fixes issue #424.
COORDINATE:NewFromVec3( Vec3 ) --R2.1 Fixes issue #424.
COORDINATE:ToStringLL( LL_Accuracy, LL_DMS ) --R2.1 Fixes issue #424.
COORDINATE:ToStringMGRS( MGRS_Accuracy ) --R2.1 Fixes issue #424.
COORDINATE:ToString() --R2.1 Fixes issue #424.
COORDINATE:CoordinateMenu( RootMenu ) --R2.1 Fixes issue #424.
COORDINATE:MenuSystem( System ) --R2.1 Fixes issue #424.
COORDINATE:MenuLL_Accuracy( LL_Accuracy ) --R2.1 Fixes issue #424.
COORDINATE:MenuLL_DMS( LL_DMS ) --R2.1 Fixes issue #424.
COORDINATE:MenuMGRS_Accuracy( MGRS_Accuracy ) --R2.1 Fixes issue #424.
SET_BASE:FilterDeads() --R2.1 allow deads to be filtered to automatically handle deads in the collection.
SET_BASE:FilterCrashes() --R2.1 allow crashes to be filtered to automatically handle crashes in the collection.
SET_UNIT:ForEachUnitPerThreatLevel( FromThreatLevel, ToThreatLevel, IteratorFunction, ... ) --R2.1 Threat Level implementation
SET_CARGO:New() --R2.1
SET_CARGO:AddCargosByName( AddCargoNames ) --R2.1
SET_CARGO:RemoveCargosByName( RemoveCargoNames ) --R2.1
SET_CARGO:FindCargo( CargoName ) --R2.1
SET_CARGO:FilterCoalitions( Coalitions ) --R2.1
SET_CARGO:FilterTypes( Types ) --R2.1
SET_CARGO:FilterCountries( Countries ) --R2.1
SET_CARGO:FilterPrefixes( Prefixes ) --R2.1
SET_CARGO:FilterStart() --R2.1
SET_CARGO:AddInDatabase( Event ) --R2.1
SET_CARGO:FindInDatabase( Event ) --R2.1
SET_CARGO:ForEachCargo( IteratorFunction, ... ) --R2.1
SET_CARGO:FindNearestCargoFromPointVec2( PointVec2 ) --R2.1
SET_CARGO:IsIncludeObject( MCargo ) --R2.1
SET_CARGO:OnEventNewCargo( EventData ) --R2.1
SET_CARGO:OnEventDeleteCargo( EventData ) --R2.1 SpawnStatic.lua (5 matches)
SPAWNSTATIC:NewFromStatic( SpawnTemplatePrefix, CountryID ) --R2.1
SPAWNSTATIC:NewFromType( SpawnTypeName, SpawnShapeName, SpawnCategory, CountryID ) --R2.1
SPAWNSTATIC:SpawnFromPointVec2( PointVec2, Heading, NewName ) --R2.1
SPAWNSTATIC:SpawnFromZone( Zone, Heading, NewName ) --R2.1
ZONE_BASE:GetCoordinate( Height ) --R2.1
DESIGNATE:SetFlashStatusMenu( FlashMenu ) --R2.1
DESIGNATE:SetLaserCodes( LaserCodes ) --R2.1
DESIGNATE:GenerateLaserCodes() --R2.1
DESIGNATE:SetAutoLase( AutoLase ) --R2.1
DESIGNATE:SetThreatLevelPrioritization( Prioritize ) --R2.1
DETECTION_BASE:CleanDetectionItems() --R2.1 Clean the DetectionItems list
DETECTION_BASE:GetDetectedItemID( Index ) --R2.1
DETECTION_BASE:GetDetectedID( Index ) --R2.1
DETECTION_AREAS:DetectedReportDetailed() --R2.1 Fixed missing report
REPORT:HasText() --R2.1
REPORT:SetIndent( Indent ) --R2.1
REPORT:AddIndent( Text ) --R2.1
MISSION:GetMenu( TaskGroup ) -- R2.1 -- Changed Menu Structure
TASK:SetMenu( MenuTime ) --R2.1 Mission Reports and Task Reports added. Fixes issue #424.
TASK:ReportSummary() --R2.1 fixed report. Now nicely formatted and contains the info required.
TASK:ReportOverview() --R2.1 fixed report. Now nicely formatted and contains the info required.
TASK:GetPlayerCount() --R2.1 Get a count of the players.
TASK:GetPlayerNames() --R2.1 Get a map of the players.
TASK:ReportDetails() --R2.1 fixed report. Now nicely formatted and contains the info required.
UTILS.tostringMGRS = function(MGRS, acc) --R2.1
POSITIONABLE:GetBoundingBox() --R2.1
POSITIONABLE:GetHeight() --R2.1
POSITIONABLE:GetMessageText( Message, Name ) --R2.1 added
POSITIONABLE:GetMessage( Message, Duration, Name ) --R2.1 changed callsign and name and using GetMessageText
POSITIONABLE:MessageToSetGroup( Message, Duration, MessageSetGroup, Name ) --R2.1
POSITIONABLE:GetRadio() --R2.1
POSITIONABLE:GetBeacon() --R2.1
POSITIONABLE:LaseUnit( Target, LaserCode, Duration ) --R2.1
POSITIONABLE:LaseOff() --R2.1
POSITIONABLE:IsLasing() --R2.1
POSITIONABLE:GetSpot() --R2.1
POSITIONABLE:GetLaserCode() --R2.1
UNIT:IsDetected( TargetUnit ) --R2.1
UNIT:IsLOS( TargetUnit ) --R2.1

BIN
Release Notes 2.2.0.docx Normal file

Binary file not shown.

BIN
Release Notes 2.2.0.pdf Normal file

Binary file not shown.

565
Utils/DCS_ControlAPI.txt Normal file
View File

@@ -0,0 +1,565 @@
DCS Simulation Control User Scripts
====================================
The behaviour of the DCS can be altered using the *GameGUI.lua scripts.
You define the hooks to the DCS events, and then do what you want using the provided API.
===================================================================================================
When loading, DCS searches for Saved Games\DCS\Scripts\*GameGUI.lua files,
sorts them by name and then loads into the GUI Lua-state.
Each user script is loaded into an isolated environment, so the only
thing they share is the state of the simulator.
Each script defines a set of callbacks to the DCS events and sets them with the call
DCS.setUserCallbacks(cb_table)
For each callback type the hooks of all user scripts will be called in order of loading.
For callbacks which are supposed to returning a value, currently there are 3 of them:
onPlayerTryConnect
onPlayerTrySendChat
onPlayerTryChangeSlot
returning a value means breaking the hook call chain.
Returning nothing (or nil) means continuing the hook chain, which ends with the default allow-all handlers.
The example user script 'testGameGUI.lua':
----------------------------------------------------------------------------------------------
local test = {}
function test.onPlayerTryConnect(ipaddr, name, ucid, playerID)
print('onPlayerTryConnect(%s, %s, %s, %d)', ipaddr, name, ucid, playerID)
-- if you want to gently intercept the call, allowing other user scripts to get it,
-- you better return nothing here
return true -- allow the player to connect
end
function test.onSimulationStart()
print('Current mission is '..DCS.getMissionName())
end
DCS.setUserCallbacks(test) -- here we set our callbacks
----------------------------------------------------------------------------------------------
The available API are documented below.
The full list of the callbacks is at the end of this document.
In addition, all standard lua 5.1 libraries are available as well, namely:
base api, like print, etc,
math.*
table.*
string.*
io.*
os.*
debug.*
===================================================================================================
Lua File System (lfs) API
-------------------------------
lfs.currentdir() -> string
Returns the path of the DCS install folder
lfs.writedir() -> string
Returns the path of the current 'Saved Games\DCS' folder.
lfs.tempdir() -> string
Returns the pat of the DCS Temp folder (AppData\Local\Temp\DCS).
lfs.mkdir()
lfs.rmdir()
lfs.attributes()
lfs.dir()
lfs.normpath()
lfs.realpath()
DCS Control API, table 'DCS.*'
-------------------------------
DCS.setPause(bool)
Pauses/resumes the simulation. Server-side only.
DCS.getPause() -> bool
true if simulation is paused
DCS.stopMission()
stops current mission
DCS.exitProcess()
Exits the DCS process.
DCS.isMultiplayer() -> bool
True when running in the multiplayer mode.
DCS.isServer() -> bool
True when running as a server or in the single-player mode.
DCS.getModelTime() -> number
returns current DCS simulation time in seconds.
DCS.getRealTime() -> number
returns current DCS real time in seconds relative to the DCS start time.
DCS.getMissionOptions() -> table
Returns the value of 'mission.options'
DCS.getMissionDescription() -> string
translated mission.descriptionText string
DCS.getAvailableCoalitions() -> table {
[coalition_id] = { name = "coalition name", }
...
}
Returns a list of coalitions which have available slots.
DCS.getAvailableSlots(coalitionID) -> array of {unitId, type, role, callsign, groupName, country}
Returns the list of available slots.
NOTE: the returned unitID is actually a slotID, which for multi-seat units is 'unitID_seatID'
DCS.getCurrentMission() -> table with the currently loaded mission
NOTE: to get valid mission.options use DCS.getMissionOptions()
DCS.getMissionName() -> string
Returns the name of the current mission
DCS.getMissionFilename() -> string
Returns the file name of the current mission (returns nil when acting as a multiplayer client).
DCS.getMissionResult(string side) -> integer [0, 100]
Gets missin result for either 'red' or 'blue'
DCS.getUnitProperty(missionId, propertyId) -> string
propertyId:
DCS.UNIT_RUNTIME_ID, // unique within runtime mission. int
DCS.UNIT_MISSION_ID, // unique within mission file. int>0
DCS.UNIT_NAME, // unit name, as assigned by mission designer.
DCS.UNIT_TYPE, // unit type (Ural, ZU-23, etc)
DCS.UNIT_CATEGORY,
DCS.UNIT_GROUP_MISSION_ID, // group ID, unique within mission file. int>0
DCS.UNIT_GROUPNAME, // group name, as assigned by mission designer.
DCS.UNIT_GROUPCATEGORY,
DCS.UNIT_CALLSIGN,
DCS.UNIT_HIDDEN,// ME hiding
DCS.UNIT_COALITION,// "blue", "red" or "unknown"
DCS.UNIT_COUNTRY_ID,
DCS.UNIT_TASK, //"unit.group.task"
DCS.UNIT_PLAYER_NAME, // valid for network "humanable" units
DCS.UNIT_ROLE,//"artillery_commander", "instructor", etc
DCS.UNIT_INVISIBLE_MAP_ICON,//ME invisible map icon
DCS.getUnitType(missionId) -> typeId
a shortcut for DCS.getUnitProperty(missionId, DCS.UNIT_TYPE)
DCS.getUnitTypeAttribute(typeId, attr) -> string
Returns a value from Database: Objects[typeId][attr],
for example DCS.getUnitTypeAttribute("Ural", "DisplayName")
DCS.writeDebriefing(str)
Writes a custom string to the debriefing file
DCS.setUserCallbacks(cb_table)
Hooks the callbacks using the handlers from the provided table.
See: "GameGUI scripts" section.
Logging API 'log.*'
------------------------
Logging works as follows:
a) each log message is accompanied with 2 attributes: a subsystem, and level.
b) after each messages gets into a logger it passes (asynchronously) through
a series of output filters which decide where the message will be written to.
Writing to log is done by:
log.write(SUBSYSTEM_NAME, LOG_LEVEL, message, ...)
if there are any arguments after 'message',
the actual string is formed as string.format(message, ...)
SUBSYSTEM_NAME is a string
LOG_LEVEL is one of the values, listed below
see log.set_output()
log.set_output(log_file_name_wo_ext, rule_subsystem_name, rule_level_mask, rule_output_mode)
the args:
log_file_name_wo_ext: resulting log will be written to $WRITE_DIR/Logs/<log_file_name_wo_ext>.log
rule_subsytem_name: the name of the subsystem whose messages to write or empty string to match all subsystems
rule_level_mask: a sum of log-level bit flags to match messages
valid flags are:
log.ALERT
log.ERROR
log.WARNING
log.INFO
log.DEBUG
log.ALL - includes all of the above
log.TRACE - a special level which is excluded from dcs.log file
rule_output_mode: a sum of output flags:
log.MESSAGE
log.TIME
log.MODULE - this is a 'subsystem', not a dlc
log.LEVEL
log.FULL - all of the above
So, in order to save net.trace(msg) messages to a file, you should issue a call:
log.set_output('lua-net', 'LuaNET', log.TRACE, log.MESSAGE + log.TIME)
This will write to a Logs/lua-net.log file
Or, to save everything lua-network-related:
log.set_output('lua-net', 'LuaNET', log.TRACE + log.ALL, log.MESSAGE + log.TIME + log.LEVEL)
To close the log file, you must use
log.set_output('lua-net', '', 0, 0)
log.* API is available in the 'Saved Games\DCS\Config\autoexec.cfg' file as well so you can control log output in you local machine.
Network specific API, available through the table 'net.'
----------------------------------------------------------------
net.log(msg) -- equivalent to log.write('LuaNET', log.INFO, msg)
net.trace(msg) -- equivalent to log.write('LuaNET', log.TRACE, msg)
What is the difference: log() always writes to dcs.log, but may lose messages if the output rate is too high.
trace() output never appears in the dcs.log file, it must be explicitly directed to a log file.
It never loses messages when there's an active output, but it may block if output rate is higher than writing to the log file.
To control logger output you can use $WRITE_DIR/Config/autoexec.cfg file, or call this from your network script
(log.* API, see above)
net.dostring_in(state, string) -> string
Executes a lua-string in a given internal lua-state and returns a string result
Valid state names are:
'config': the state in which $INSTALL_DIR/Config/main.cfg is executed, as well as $WRITE_DIR/Config/autoexec.cfg
used for configuration settings
'mission': holds current mission
'export': runs $WRITE_DIR/Scripts/Export.lua and the relevant export API
net.send_chat(string message, bool all)
Send chat message. If not all, then send to my coalition (side) only.
net.send_chat_to(string message, playerID to)
Send direct chat message to a player
Server-side only:
net.send_chat_to(string message, playerID to[, playerID from])
net.recv_chat(message[, int from=0])
Receive chat message locally[, pretending it was sent by another player].
from = 0 means from the system
net.load_mission(miz_filename)
Loads a specified mission, temporarily overriding the server mission list.
SERVER ONLY
net.load_next_mission() -> bool
Load the next mission from the server mission list. Returns false if list end is reached
SERVER ONLY
net.get_player_list() -> array of playerID
Returns the list of currently connected players
net.get_my_player_id() -> playerID
Returns the playerID of the local player. Currently always 1 for the server.
net.get_server_id() -> playerID
Returns playerID of the server. Currently, always 1.
net.get_player_info(playerID) -> table
Returns a table of all player attributes or nil if playerID is invalid
net.get_player_info(playerID, attrName) -> value
Returns a value of a given attribute for the playerID.
Currently defined attributes are:
'id': playerID
'name': player name
'side': 0 - spectators, 1 - red, 2 - blue
'slot': slotID of the player or ''
'ping': ping of the player in ms
'ipaddr': IP address of the player, SERVER ONLY
'ucid': Unique Client Identifier, SERVER ONLY
net.kick(id, message)
Kick a player.
net.get_stat(playerID, statID) -> integer
Get statistics for player. statIDs are:
net.PS_PING (0) - ping (in ms)
net.PS_CRASH (1) - number of crashes
net.PS_CAR (2) - number of destroyed vehicles
net.PS_PLANE (3) - ... planes/helicopters
net.PS_SHIP (4) - ... ships
net.PS_SCORE (5) - total score
net.PS_LAND (6) - number of landings
net.PS_EJECT (7) - of ejects
net.get_name(playerID) -> string
The same as net.get_player_info(playerID, 'name')
FIXME: implement in ServMan_compat.lua ?
net.get_slot(playerID) -> sideID, slotID
The same as:
net.get_player_info(playerID, 'side'), net.get_player_info(playerID, 'slot')
FIXME: implement in ServMan_compat.lua ?
net.set_slot(sideID, slotID)
Try to set the local player's slot. Empty slotID ('') puts the player into spectators.
net.force_player_slot(playerID, sideID, slotID) -> boolean
Forces a player to occupy a set slot. Slot '' means no slot (moves player to spectators)
SideID: 0 - spectators, 1 - red, 2 - blue
net.set_name(playerID, name) -- OBSOLETE, works only locally
net.lua2json(value) -> string
Convert a Lua value to JSON string
net.json2lua(json_string) -> value
Convert JSON string to a Lua value
LuaExport API 'Export.Lo*'
----------------------------------------------------------------
See Scripts/Export.lua for the documentation. Note that all export
API functions are available here in the Export. namespace, not the global one.
In multiplayer the availability of the API on clients depends on the server setting.
The calls to check export capabilities:
Export.LoIsObjectExportAllowed() -- returns the value of server.advanced.allow_object_export
Export.LoIsSensorExportAllowed() -- returns the value of server.advanced.allow_sensor_export
Export.LoIsOwnshipExportAllowed() -- returns the value of server.advanced.allow_ownship_export
These calls are only available on clients when LoIsObjectExportAllowed() is true:
Export.LoGetObjectById
Export.LoGetWorldObjects
These calls are only available on clients when LoIsSensorExportAllowed() is true:
Export.LoGetTWSInfo
Export.LoGetTargetInformation
Export.LoGetLockedTargetInformation
Export.LoGetF15_TWS_Contacts
Export.LoGetSightingSystemInfo
Export.LoGetWingTargets
These calls are only available on clients when LoIsOwnshipExportAllowed() is true:
Export.LoGetPlayerPlaneId
Export.LoGetIndicatedAirSpeed
Export.LoGetAngleOfAttack
Export.LoGetAngleOfSideSlip
Export.LoGetAccelerationUnits
Export.LoGetVerticalVelocity
Export.LoGetADIPitchBankYaw
Export.LoGetTrueAirSpeed
Export.LoGetAltitudeAboveSeaLevel
Export.LoGetAltitudeAboveGroundLevel
Export.LoGetMachNumber
Export.LoGetRadarAltimeter
Export.LoGetMagneticYaw
Export.LoGetGlideDeviation
Export.LoGetSideDeviation
Export.LoGetSlipBallPosition
Export.LoGetBasicAtmospherePressure
Export.LoGetControlPanel_HSI
Export.LoGetEngineInfo
Export.LoGetSelfData
Export.LoGetCameraPosition
Export.LoSetCameraPosition
Export.LoSetCommand
Export.LoGetMCPState
Export.LoGetRoute
Export.LoGetNavigationInfo
Export.LoGetPayloadInfo
Export.LoGetWingInfo
Export.LoGetMechInfo
Export.LoGetRadioBeaconsStatus
Export.LoGetVectorVelocity
Export.LoGetVectorWindVelocity
Export.LoGetSnares
Export.LoGetAngularVelocity
Export.LoGetHeightWithObjects
Export.LoGetFMData
These functions are always available:
Export.LoGetPilotName
Export.LoGetAltitude
Export.LoGetNameByType
Export.LoGeoCoordinatesToLoCoordinates
Export.LoCoordinatesToGeoCoordinates
Export.LoGetVersionInfo
Export.LoGetWindAtPoint
Export.LoGetModelTime
Export.LoGetMissionStartTime
These are not available in the *GameGUI state:
-- Export.LoSetSharedTexture
-- Export.LoRemoveSharedTexture
-- Export.LoUpdateSharedTexture
-------------------------------------------------------------------------------------------
--- The Callbacks.
-------------------------------------------------------------------------------------------
function onMissionLoadBegin()
end
function onMissionLoadProgress(progress, message)
end
function onMissionLoadEnd()
end
function onSimulationStart()
end
function onSimulationStop()
end
function onSimulationFrame()
end
function onSimulationPause()
end
function onSimulationResume()
end
function onGameEvent(eventName,arg1,arg2,arg3,arg4)
--"friendly_fire", playerID, weaponName, victimPlayerID
--"mission_end", winner, msg
--"kill", killerPlayerID, killerUnitType, killerSide, victimPlayerID, victimUnitType, victimSide, weaponName
--"self_kill", playerID
--"change_slot", playerID, slotID, prevSide
--"connect", playerID, name
--"disconnect", playerID, name, playerSide, reason_code
--"crash", playerID, unit_missionID
--"eject", playerID, unit_missionID
--"takeoff", playerID, unit_missionID, airdromeName
--"landing", playerID, unit_missionID, airdromeName
--"pilot_death", playerID, unit_missionID
end
function onNetConnect(localPlayerID)
end
function onNetMissionChanged(newMissionName)
end
function onNetDisconnect(reason_msg, err_code)
end
-- disconnect reason codes:
net.ERR_INVALID_ADDRESS
net.ERR_CONNECT_FAILED
net.ERR_WRONG_VERSION
net.ERR_PROTOCOL_ERROR
net.ERR_TAINTED_CLIENT
net.ERR_INVALID_PASSWORD
net.ERR_BANNED
net.ERR_BAD_CALLSIGN
net.ERR_TIMEOUT
net.ERR_KICKED
function onPlayerConnect(id)
end
function onPlayerDisconnect(id, err_code)
-- this is never called for local playerID
end
function onPlayerStart(id)
-- a player entered the simulation
-- this is never called for local playerID
end
function onPlayerStop(id)
-- a player left the simulation (happens right before a disconnect, if player exited by desire)
-- this is never called for local playerID
end
function onPlayerChangeSlot(id)
-- a player successfully changed the slot
-- this will also come as onGameEvent('change_slot', playerID, slotID),
-- if allowed by server.advanced.event_Connect setting
end
--- These 3 functions are different from the rest:
--- 1. they are called directly from the network code, so try to make them as fast as possible
--- 2. they return a result
-- The code shows the default implementations.
function onPlayerTryConnect(addr, name, ucid, playerID) --> true | false, "disconnect reason"
return true
end
function onPlayerTrySendChat(playerID, msg, all) -- -> filteredMessage | "" - empty string drops the message
return msg
end
function onPlayerTryChangeSlot(playerID, side, slotID) -- -> true | false
return true
end
-- GUI callbacks
function onChatMessage(message, from)
-- this one may be useful for chat archiving
end
function onShowRadioMenu(a_h)
end
function onShowPool()
end
function onShowGameMenu()
end
function onShowBriefing()
end
function onShowChatAll()
end
function onShowChatTeam()
end
function onShowChatRead()
end
function onShowMessage(a_text, a_duration)
end
function onTriggerMessage(message, duration, clearView)
end
function onRadioMessage(message, duration)
end
function onRadioCommand(command_message)
end
===================================================================================================
Happy hacking!
Sincerely,
dsb at eagle dot ru

5
Utils/Generate_Moose.bat Normal file
View File

@@ -0,0 +1,5 @@
%~dp0luarocks\lua5.1.exe %1 %2 %3 %4 %5
call %~dp0LuaSrcDiet.bat --basic --opt-emptylines %5\Moose.lua
del %5\Moose.lua
copy %5\Moose_.lua %5\Moose.lua
del Moose_.lua

Binary file not shown.

View File

@@ -0,0 +1,178 @@
#!/usr/bin/lua
--------------------------------------------------------------------------------
-- Copyright (c) 2012-2014 Sierra Wireless.
-- All rights reserved. This program and the accompanying materials
-- are made available under the terms of the Eclipse Public License v1.0
-- which accompanies this distribution, and is available at
-- http://www.eclipse.org/legal/epl-v10.html
--
-- Contributors:
-- Kevin KIN-FOO <kkinfoo@sierrawireless.com>
-- - initial API and implementation and initial documentation
--------------------------------------------------------------------------------
-- Check interpreter version
if _VERSION ~= "Lua 5.1" then
print("Luadocumentor is only compatible with Lua 5.1")
return
end
--
-- Defining help message.
--
-- This message is compliant to 'lapp', which will match options and arguments
-- from command line.
local help = [[luadocumentor v0.1.4: tool for Lua Documentation Language
-f, --format (default doc) Define output format :
* doc: Will produce HTML documentation from specified file(s) or directories.
* api: Will produce API file(s) from specified file(s) or directories.
-d, --dir (default docs) Define an output directory. If the given directory doesn't exist, it will be created.
-h, --help Display the help.
-n, --noheuristic Do not use code analysis, use only comments to generate documentation.
-s, --style (default !) The path of your own css file, if you don't want to use the default one. (usefull only for the doc format)
[directories|files] Define the paths or the directories of inputs files. Only Lua or C files containing a @module tag will be considered.
]]
local docgenerator = require 'docgenerator'
local lddextractor = require 'lddextractor'
local lapp = require 'pl.lapp'
local args = lapp( help )
if not args or #args < 1 then
print('No directory provided')
return
elseif args.help then
-- Just print help
print( help )
return
end
--
-- define css file name
--
local cssfilename = "stylesheet.css"
--
-- Parse files from given folders
--
-- Check if all folders exist
local fs = require 'fs.lfs'
local allpresent, missing = fs.checkdirectory(args)
-- Some of given directories are absent
if missing then
-- List missing directories
print 'Unable to open'
for _, file in ipairs( missing ) do
print('\t'.. file)
end
return
end
-- Get files from given directories
local filestoparse, error = fs.filelist( args )
if not filestoparse then
print ( error )
return
end
--
-- Generate documentation only files
--
if args.format == 'api' then
for _, filename in ipairs( filestoparse ) do
-- Loading file content
print('Dealing with "'..filename..'".')
local file, error = io.open(filename, 'r')
if not file then
print ('Unable to open "'..filename.."'.\n"..error)
else
local code = file:read('*all')
file:close()
--
-- Creating comment file
--
local commentfile, error = lddextractor.generatecommentfile(filename, code)
-- Getting module name
-- Optimize me
local module, moduleerror = lddextractor.generateapimodule(filename, code)
if not commentfile then
print('Unable to create documentation file for "'..filename..'"\n'..error)
elseif not module or not module.name then
local error = moduleerror and '\n'..moduleerror or ''
print('Unable to compute module name for "'..filename..'".'..error)
else
--
-- Flush documentation file on disk
--
local path = args.dir..fs.separator..module.name..'.lua'
local status, err = fs.fill(path, commentfile)
if not status then
print(err)
end
end
end
end
print('Done')
return
end
-- Deal only supported output types
if args.format ~= 'doc' then
print ('"'..args.format..'" format is not handled.')
return
end
-- Generate html form files
local parsedfiles, unparsed = docgenerator.generatedocforfiles(filestoparse, cssfilename,args.noheuristic)
-- Show warnings on unparsed files
if #unparsed > 0 then
for _, faultyfile in ipairs( unparsed ) do
print( faultyfile )
end
end
-- This loop is just for counting parsed files
-- TODO: Find a more elegant way to do it
local parsedfilescount = 0
for _, p in pairs(parsedfiles) do
parsedfilescount = parsedfilescount + 1
end
print (parsedfilescount .. ' file(s) parsed.')
-- Create html files
local generated = 0
for _, apifile in pairs ( parsedfiles ) do
local status, err = fs.fill(args.dir..fs.separator..apifile.name..'.html', apifile.body)
if status then
generated = generated + 1
else
print( 'Unable to create '..apifile.name..'.html on disk.')
end
end
print (generated .. ' file(s) generated.')
-- Copying css
local csscontent
if args.style == '!' then
csscontent = require 'defaultcss'
else
local css, error = io.open(args.style, 'r')
if not css then
print('Unable to open "'..args.style .. '".\n'..error)
return
end
csscontent = css:read("*all")
css:close()
end
local status, error = fs.fill(args.dir..fs.separator..cssfilename, csscontent)
if not status then
print(error)
return
end
print('Adding css')
print('Done')

View File

@@ -0,0 +1,198 @@
Eclipse Public License - v 1.0
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
1. DEFINITIONS
"Contribution" means:
a) in the case of the initial Contributor, the initial code and documentation
distributed under this Agreement, and
b) in the case of each subsequent Contributor:
i) changes to the Program, and
ii) additions to the Program;
where such changes and/or additions to the Program originate from and are
distributed by that particular Contributor. A Contribution 'originates' from
a Contributor if it was added to the Program by such Contributor itself or
anyone acting on such Contributor's behalf. Contributions do not include
additions to the Program which: (i) are separate modules of software
distributed in conjunction with the Program under their own license
agreement, and (ii) are not derivative works of the Program.
"Contributor" means any person or entity that distributes the Program.
"Licensed Patents" mean patent claims licensable by a Contributor which are
necessarily infringed by the use or sale of its Contribution alone or when
combined with the Program.
"Program" means the Contributions distributed in accordance with this Agreement.
"Recipient" means anyone who receives the Program under this Agreement,
including all Contributors.
2. GRANT OF RIGHTS
a) Subject to the terms of this Agreement, each Contributor hereby grants
Recipient a non-exclusive, worldwide, royalty-free copyright license to
reproduce, prepare derivative works of, publicly display, publicly perform,
distribute and sublicense the Contribution of such Contributor, if any, and
such derivative works, in source code and object code form.
b) Subject to the terms of this Agreement, each Contributor hereby grants
Recipient a non-exclusive, worldwide, royalty-free patent license under
Licensed Patents to make, use, sell, offer to sell, import and otherwise
transfer the Contribution of such Contributor, if any, in source code and
object code form. This patent license shall apply to the combination of the
Contribution and the Program if, at the time the Contribution is added by
the Contributor, such addition of the Contribution causes such combination
to be covered by the Licensed Patents. The patent license shall not apply
to any other combinations which include the Contribution. No hardware per
se is licensed hereunder.
c) Recipient understands that although each Contributor grants the licenses to
its Contributions set forth herein, no assurances are provided by any
Contributor that the Program does not infringe the patent or other
intellectual property rights of any other entity. Each Contributor
disclaims any liability to Recipient for claims brought by any other entity
based on infringement of intellectual property rights or otherwise. As a
condition to exercising the rights and licenses granted hereunder, each
Recipient hereby assumes sole responsibility to secure any other
intellectual property rights needed, if any. For example, if a third party
patent license is required to allow Recipient to distribute the Program, it
is Recipient's responsibility to acquire that license before distributing
the Program.
d) Each Contributor represents that to its knowledge it has sufficient
copyright rights in its Contribution, if any, to grant the copyright
license set forth in this Agreement.
3. REQUIREMENTS
A Contributor may choose to distribute the Program in object code form under its
own license agreement, provided that:
a) it complies with the terms and conditions of this Agreement; and
b) its license agreement:
i) effectively disclaims on behalf of all Contributors all warranties and
conditions, express and implied, including warranties or conditions of
title and non-infringement, and implied warranties or conditions of
merchantability and fitness for a particular purpose;
ii) effectively excludes on behalf of all Contributors all liability for
damages, including direct, indirect, special, incidental and
consequential damages, such as lost profits;
iii) states that any provisions which differ from this Agreement are offered
by that Contributor alone and not by any other party; and
iv) states that source code for the Program is available from such
Contributor, and informs licensees how to obtain it in a reasonable
manner on or through a medium customarily used for software exchange.
When the Program is made available in source code form:
a) it must be made available under this Agreement; and
b) a copy of this Agreement must be included with each copy of the Program.
Contributors may not remove or alter any copyright notices contained within
the Program.
Each Contributor must identify itself as the originator of its Contribution, if
any, in a manner that reasonably allows subsequent Recipients to identify the
originator of the Contribution.
4. COMMERCIAL DISTRIBUTION
Commercial distributors of software may accept certain responsibilities with
respect to end users, business partners and the like. While this license is
intended to facilitate the commercial use of the Program, the Contributor who
includes the Program in a commercial product offering should do so in a manner
which does not create potential liability for other Contributors. Therefore, if
a Contributor includes the Program in a commercial product offering, such
Contributor ("Commercial Contributor") hereby agrees to defend and indemnify
every other Contributor ("Indemnified Contributor") against any losses, damages
and costs (collectively "Losses") arising from claims, lawsuits and other legal
actions brought by a third party against the Indemnified Contributor to the
extent caused by the acts or omissions of such Commercial Contributor in
connection with its distribution of the Program in a commercial product
offering. The obligations in this section do not apply to any claims or Losses
relating to any actual or alleged intellectual property infringement. In order
to qualify, an Indemnified Contributor must: a) promptly notify the Commercial
Contributor in writing of such claim, and b) allow the Commercial Contributor to
control, and cooperate with the Commercial Contributor in, the defense and any
related settlement negotiations. The Indemnified Contributor may participate in
any such claim at its own expense.
For example, a Contributor might include the Program in a commercial product
offering, Product X. That Contributor is then a Commercial Contributor. If that
Commercial Contributor then makes performance claims, or offers warranties
related to Product X, those performance claims and warranties are such
Commercial Contributor's responsibility alone. Under this section, the
Commercial Contributor would have to defend claims against the other
Contributors related to those performance claims and warranties, and if a court
requires any other Contributor to pay any damages as a result, the Commercial
Contributor must pay those damages.
5. NO WARRANTY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
Recipient is solely responsible for determining the appropriateness of using and
distributing the Program and assumes all risks associated with its exercise of
rights under this Agreement , including but not limited to the risks and costs
of program errors, compliance with applicable laws, damage to or loss of data,
programs or equipment, and unavailability or interruption of operations.
6. DISCLAIMER OF LIABILITY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS
GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
7. GENERAL
If any provision of this Agreement is invalid or unenforceable under applicable
law, it shall not affect the validity or enforceability of the remainder of the
terms of this Agreement, and without further action by the parties hereto, such
provision shall be reformed to the minimum extent necessary to make such
provision valid and enforceable.
If Recipient institutes patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Program itself
(excluding combinations of the Program with other software or hardware)
infringes such Recipient's patent(s), then such Recipient's rights granted under
Section 2(b) shall terminate as of the date such litigation is filed.
All Recipient's rights under this Agreement shall terminate if it fails to
comply with any of the material terms or conditions of this Agreement and does
not cure such failure in a reasonable period of time after becoming aware of
such noncompliance. If all Recipient's rights under this Agreement terminate,
Recipient agrees to cease use and distribution of the Program as soon as
reasonably practicable. However, Recipient's obligations under this Agreement
and any licenses granted by Recipient relating to the Program shall continue and
survive.
Everyone is permitted to copy and distribute copies of this Agreement, but in
order to avoid inconsistency the Agreement is copyrighted and may only be
modified in the following manner. The Agreement Steward reserves the right to
publish new versions (including revisions) of this Agreement from time to time.
No one other than the Agreement Steward has the right to modify this Agreement.
The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation
may assign the responsibility to serve as the Agreement Steward to a suitable
separate entity. Each new version of the Agreement will be given a
distinguishing version number. The Program (including Contributions) may always
be distributed subject to the version of the Agreement under which it was
received. In addition, after a new version of the Agreement is published,
Contributor may elect to distribute the Program (including its Contributions)
under the new version. Except as expressly stated in Sections 2(a) and 2(b)
above, Recipient receives no rights or licenses to the intellectual property of
any Contributor under this Agreement, whether expressly, by implication,
estoppel or otherwise. All rights in the Program not expressly granted under
this Agreement are reserved.
This Agreement is governed by the laws of the State of New York and the
intellectual property laws of the United States of America. No party to this
Agreement will bring a legal action under this Agreement more than one year
after the cause of action arose. Each party waives its rights to a jury trial in
any resulting litigation.

View File

@@ -0,0 +1,7 @@
# Lua Documentor
LuaDocumentor allow users to generate HTML and API files from code documented
using Lua documentation language.
Documentation is
[available here](http://wiki.eclipse.org/Koneki/LDT/User_Area/LuaDocumentor).

View File

@@ -0,0 +1,57 @@
package = 'LuaDocumentor'
version = '0.1.5-1'
description = {
summary = 'LuaDocumentor allow users to generate HTML and API files from code documented using Lua documentation language.',
detailed = [[
This is an example for the LuaRocks tutorial.
Here we would put a detailed, typically
paragraph-long description.
]],
homepage = 'http://wiki.eclipse.org/Koneki/LDT/User_Area/LuaDocumentor',
license = 'EPL'
}
source = {
url = 'git://github.com/LuaDevelopmentTools/luadocumentor.git',
tag = 'v0.1.5-1'
}
dependencies = {
'lua ~> 5.1',
'luafilesystem ~> 1.6',
'markdown ~> 0.32',
'metalua-compiler ~> 0.7',
'penlight ~> 0.9'
}
build = {
type = 'builtin',
install = {
bin = {
luadocumentor = 'luadocumentor.lua'
},
lua = {
['models.internalmodelbuilder'] = 'models/internalmodelbuilder.mlua'
}
},
modules = {
defaultcss = 'defaultcss.lua',
docgenerator = 'docgenerator.lua',
extractors = 'extractors.lua',
lddextractor = 'lddextractor.lua',
templateengine = 'templateengine.lua',
['fs.lfs'] = 'fs/lfs.lua',
['models.apimodel'] = 'models/apimodel.lua',
['models.apimodelbuilder'] = 'models/apimodelbuilder.lua',
['models.internalmodel'] = 'models/internalmodel.lua',
['models.ldparser'] = 'models/ldparser.lua',
['template.file'] = 'template/file.lua',
['template.index'] = 'template/index.lua',
['template.index.recordtypedef'] = 'template/index/recordtypedef.lua',
['template.item'] = 'template/item.lua',
['template.page'] = 'template/page.lua',
['template.recordtypedef'] = 'template/recordtypedef.lua',
['template.usage'] = 'template/usage.lua',
['template.utils'] = 'template/utils.lua',
}
}

View File

@@ -0,0 +1,39 @@
rock_manifest = {
bin = {
luadocumentor = "bc5cc07f56db2cf1dbe80f0827332873"
},
doc = {
LICENSE = "52a21f73ac77fd790dc40dc5acda0fc2",
["README.md"] = "fcef1f43c69f3559b347d854b2626deb"
},
lua = {
["defaultcss.lua"] = "dd9b2b89e5080972bbb52056247c0c65",
["docgenerator.lua"] = "92d0a3947d88226340014d2f033be37f",
["extractors.lua"] = "74191695e5217706ee355925e5ca40fa",
fs = {
["lfs.lua"] = "4d00f9bc942b02a86ccea16544d3e85d"
},
["lddextractor.lua"] = "56edde775a5d57818aa0a07b4f723536",
models = {
["apimodel.lua"] = "3c401de18691b1222b0ad253958260ee",
["apimodelbuilder.lua"] = "4c4a3c0b48b404973542dd99f994eb2c",
["internalmodel.lua"] = "a1a21e50af8db0f0a0b9d164ccc08853",
["internalmodelbuilder.mlua"] = "ff95dfca573ccc1c19a79434e96a492d",
["ldparser.lua"] = "538904a3adbfff4ff83deda029847323"
},
template = {
["file.lua"] = "41f095bc049ef161060d8e3b4ac9de63",
index = {
["recordtypedef.lua"] = "0977ff0048a837389c2ac10285eb1ce1"
},
["index.lua"] = "5a3b3cface3b1fd9cb2d56f1edd5487b",
["item.lua"] = "5d5a6d9bffd8935c4ed283105ede331b",
["page.lua"] = "351f4a7215272f7e448faeece4945bc0",
["recordtypedef.lua"] = "69938e1d60e94eed7f95b0999f1386ca",
["usage.lua"] = "979503deb84877cb221130a5be7c1535",
["utils.lua"] = "ad97fb4e3de9fb6480b25cdd877b50d9"
},
["templateengine.lua"] = "09bfc6350e14f4ab509d14fb0fb295c0"
},
["luadocumentor-0.1.5-1.rockspec"] = "4ba1b88898dce89e7fd8fb6a700496a4"
}

View File

@@ -0,0 +1,212 @@
body {
margin-left: 1em;
margin-right: 1em;
font-family: arial, helvetica, geneva, sans-serif;
background-color:#ffffff; margin:0px;
}
code {
font-family: "Andale Mono", monospace;
}
tt {
font-family: "Andale Mono", monospace;
}
body, td, th { font-size: 11pt; }
h1, h2, h3, h4 { margin-left: 0em; }
textarea, pre, tt { font-size:10pt; }
body, td, th { color:#000000; }
small { font-size:0.85em; }
h1 { font-size:1.5em; }
h2 { font-size:1.25em; }
h3 { font-size:1.15em; }
h4 { font-size:1.06em; }
a:link { font-weight:bold; color: #004080; text-decoration: none; }
a:visited { font-weight:bold; color: #006699; text-decoration: none; }
a:link:hover { text-decoration:underline; }
hr { color:#cccccc }
img { border-width: 0px; }
h3 { padding-top: 1em; }
p { margin-left: 1em; }
p.name {
font-family: "Andale Mono", monospace;
padding-top: 1em;
margin-left: 0em;
}
blockquote { margin-left: 3em; }
.example {
background-color: rgb(245, 245, 245);
border-top-width: 1px;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
border-top-style: solid;
border-right-style: solid;
border-bottom-style: solid;
border-left-style: solid;
border-top-color: silver;
border-right-color: silver;
border-bottom-color: silver;
border-left-color: silver;
padding: 1em;
margin-left: 1em;
margin-right: 1em;
font-family: "Andale Mono", monospace;
font-size: smaller;
}
hr {
margin-left: 0em;
background: #00007f;
border: 0px;
height: 1px;
}
ul { list-style-type: disc; }
table.index { border: 1px #00007f; }
table.index td { text-align: left; vertical-align: top; }
table.index ul { padding-top: 0em; margin-top: 0em; }
table {
border: 1px solid black;
border-collapse: collapse;
margin-left: auto;
margin-right: auto;
}
th {
border: 1px solid black;
padding: 0.5em;
}
td {
border: 1px solid black;
padding: 0.5em;
}
div.header, div.footer { margin-left: 0em; }
#container {
margin-left: 1em;
margin-right: 1em;
background-color: #f0f0f0;
}
#product {
text-align: center;
border-bottom: 1px solid #cccccc;
background-color: #ffffff;
}
#product big {
font-size: 2em;
}
#product_logo {
}
#product_name {
}
#product_description {
}
#main {
background-color: #f0f0f0;
border-left: 2px solid #cccccc;
}
#navigation {
float: left;
width: 12em;
margin: 0;
vertical-align: top;
background-color: #f0f0f0;
overflow:visible;
}
#navigation h1 {
background-color:#e7e7e7;
font-size:1.1em;
color:#000000;
text-align:left;
margin:0px;
padding:0.2em;
border-top:1px solid #dddddd;
border-bottom:1px solid #dddddd;
}
#navigation ul {
font-size:1em;
list-style-type: none;
padding: 0;
margin: 1px;
}
#navigation li {
text-indent: -1em;
margin: 0em 0em 0em 0.5em;
display: block;
padding: 3px 0px 0px 12px;
}
#navigation li li a {
padding: 0px 3px 0px -1em;
}
#content {
margin-left: 12em;
padding: 1em;
border-left: 2px solid #cccccc;
border-right: 2px solid #cccccc;
background-color: #ffffff;
}
#about {
clear: both;
margin: 0;
padding: 5px;
border-top: 2px solid #cccccc;
background-color: #ffffff;
}
@media print {
body {
font: 10pt "Times New Roman", "TimeNR", Times, serif;
}
a {
font-weight:bold; color: #004080; text-decoration: underline;
}
#main {
background-color: #ffffff; border-left: 0px;
}
#container {
margin-left: 2%; margin-right: 2%; background-color: #ffffff;
}
#content {
margin-left: 0px; padding: 1em; border-left: 0px; border-right: 0px; background-color: #ffffff;
}
#navigation {
display: none;
}
#product_logo {
display: none;
}
#about img {
display: none;
}
.example {
font-family: "Andale Mono", monospace;
font-size: 8pt;
page-break-inside: avoid;
}
}

View File

@@ -0,0 +1,103 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>LuaFileSystem</title>
<link rel="stylesheet" href="doc.css" type="text/css"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<div id="container">
<div id="product">
<div id="product_logo">
<a href="http://www.keplerproject.org">
<img alt="LuaFileSystem" src="luafilesystem.png"/>
</a>
</div>
<div id="product_name"><big><strong>LuaFileSystem</strong></big></div>
<div id="product_description">File System Library for the Lua Programming Language</div>
</div> <!-- id="product" -->
<div id="main">
<div id="navigation">
<h1>LuaFileSystem</h1>
<ul>
<li><a href="index.html">Home</a>
<ul>
<li><a href="index.html#overview">Overview</a></li>
<li><a href="index.html#status">Status</a></li>
<li><a href="index.html#download">Download</a></li>
<li><a href="index.html#history">History</a></li>
<li><a href="index.html#credits">Credits</a></li>
<li><a href="index.html#contact">Contact us</a></li>
</ul>
</li>
<li><a href="manual.html">Manual</a>
<ul>
<li><a href="manual.html#introduction">Introduction</a></li>
<li><a href="manual.html#building">Building</a></li>
<li><a href="manual.html#installation">Installation</a></li>
<li><a href="manual.html#reference">Reference</a></li>
</ul>
</li>
<li><strong>Examples</strong></li>
<li><a href="https://github.com/keplerproject/luafilesystem">Project</a>
<ul>
<li><a href="https://github.com/keplerproject/luafilesystem/issues">Bug Tracker</a></li>
<li><a href="https://github.com/keplerproject/luafilesystem">Git</a></li>
</ul>
</li>
<li><a href="license.html">License</a></li>
</ul>
</div> <!-- id="navigation" -->
<div id="content">
<h2><a name="example"></a>Examples</h2>
<h3>Directory iterator</h3>
<p>The following example iterates over a directory and recursively lists the
attributes for each file inside it.</p>
<pre class="example">
local lfs = require"lfs"
function attrdir (path)
for file in lfs.dir(path) do
if file ~= "." and file ~= ".." then
local f = path..'/'..file
print ("\t "..f)
local attr = lfs.attributes (f)
assert (type(attr) == "table")
if attr.mode == "directory" then
attrdir (f)
else
for name, value in pairs(attr) do
print (name, value)
end
end
end
end
end
attrdir (".")
</pre>
</div> <!-- id="content" -->
</div> <!-- id="main" -->
<div id="about">
<p><a href="http://validator.w3.org/check?uri=referer">Valid XHTML 1.0!</a></p>
<p><small>$Id: examples.html,v 1.8 2007/12/14 15:28:04 carregal Exp $</small></p>
</div> <!-- id="about" -->
</div> <!-- id="container" -->
</body>
</html>

View File

@@ -0,0 +1,218 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>LuaFileSystem</title>
<link rel="stylesheet" href="doc.css" type="text/css"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<div id="container">
<div id="product">
<div id="product_logo">
<a href="http://www.keplerproject.org">
<img alt="LuaFileSystem" src="luafilesystem.png"/>
</a>
</div>
<div id="product_name"><big><strong>LuaFileSystem</strong></big></div>
<div id="product_description">File System Library for the Lua Programming Language</div>
</div> <!-- id="product" -->
<div id="main">
<div id="navigation">
<h1>LuaFileSystem</h1>
<ul>
<li><strong>Home</strong>
<ul>
<li><a href="index.html#overview">Overview</a></li>
<li><a href="index.html#status">Status</a></li>
<li><a href="index.html#download">Download</a></li>
<li><a href="index.html#history">History</a></li>
<li><a href="index.html#credits">Credits</a></li>
<li><a href="index.html#contact">Contact us</a></li>
</ul>
</li>
<li><a href="manual.html">Manual</a>
<ul>
<li><a href="manual.html#introduction">Introduction</a></li>
<li><a href="manual.html#building">Building</a></li>
<li><a href="manual.html#installation">Installation</a></li>
<li><a href="manual.html#reference">Reference</a></li>
</ul>
</li>
<li><a href="examples.html">Examples</a></li>
<li><a href="https://github.com/keplerproject/luafilesystem">Project</a>
<ul>
<li><a href="https://github.com/keplerproject/luafilesystem/issues">Bug Tracker</a></li>
<li><a href="https://github.com/keplerproject/luafilesystem">Git</a></li>
</ul>
</li>
<li><a href="license.html">License</a></li>
</ul>
</div> <!-- id="navigation" -->
<div id="content">
<h2><a name="overview"></a>Overview</h2>
<p>LuaFileSystem is a <a href="http://www.lua.org">Lua</a> library
developed to complement the set of functions related to file
systems offered by the standard Lua distribution.</p>
<p>LuaFileSystem offers a portable way to access
the underlying directory structure and file attributes.</p>
<p>LuaFileSystem is free software and uses the same
<a href="license.html">license</a> as Lua 5.1.</p>
<h2><a name="status"></a>Status</h2>
<p>Current version is 1.6.3. It works with Lua 5.1, 5.2 and 5.3.</p>
<h2><a name="download"></a>Download</h2>
<p>LuaFileSystem source can be downloaded from its
<a href="http://github.com/keplerproject/luafilesystem">Github</a>
page.</p>
<h2><a name="history"></a>History</h2>
<dl class="history">
<dt><strong>Version 1.6.3</strong> [15/Jan/2015]</dt>
<dd><ul>
<li>Lua 5.3 support.</li>
<li>Assorted bugfixes.</li>
</ul></dd>
<dt><strong>Version 1.6.2</strong> [??/Oct/2012]</dt>
<dd><ul>
<li>Full Lua 5.2 compatibility (with Lua 5.1 fallbacks)</li>
</ul></dd>
<dt><strong>Version 1.6.1</strong> [01/Oct/2012]</dt>
<dd><ul>
<li>fix build for Lua 5.2</li>
</ul></dd>
<dt><strong>Version 1.6.0</strong> [26/Sep/2012]</dt>
<dd><ul>
<li>getcwd fix for Android</li>
<li>support for Lua 5.2</li>
<li>add lfs.link</li>
<li>other bug fixes</li>
</ul></dd>
<dt><strong>Version 1.5.0</strong> [20/Oct/2009]</dt>
<dd><ul>
<li>Added explicit next and close methods to second return value of lfs.dir
(the directory object), for explicit iteration or explicit closing.</li>
<li>Added directory locking via lfs.lock_dir function (see the <a href="manual.html">manual</a>).</li>
</ul></dd>
<dt><strong>Version 1.4.2</strong> [03/Feb/2009]</dt>
<dd>
<ul>
<li>fixed bug [<a href="http://luaforge.net/tracker/?func=detail&amp;group_id=66&amp;aid=13198&amp;atid=356">#13198</a>]
lfs.attributes(filename, 'size') overflow on files > 2 Gb again (bug report and patch by KUBO Takehiro).</li>
<li>fixed bug [<a href="http://luaforge.net/tracker/?group_id=66&amp;atid=356&amp;func=detail&amp;aid=39794">#39794</a>]
Compile error on Solaris 10 (bug report and patch by Aaron B).</li>
<li>fixed compilation problems with Borland C.</li>
</ul>
</dd>
<dt><strong>Version 1.4.1</strong> [07/May/2008]</dt>
<dd>
<ul>
<li>documentation review</li>
<li>fixed Windows compilation issues</li>
<li>fixed bug in the Windows tests (patch by Shmuel Zeigerman)</li>
<li>fixed bug [<a href="http://luaforge.net/tracker/?func=detail&amp;group_id=66&amp;aid=2185&amp;atid=356">#2185</a>]
<code>lfs.attributes(filename, 'size')</code> overflow on files > 2 Gb
</li>
</ul>
</dd>
<dt><strong>Version 1.4.0</strong> [13/Feb/2008]</dt>
<dd>
<ul>
<li>added function
<a href="manual.html#setmode"><code>lfs.setmode</code></a>
(works only in Windows systems).</li>
<li><a href="manual.html#attributes"><code>lfs.attributes</code></a>
raises an error if attribute does not exist</li>
</ul>
</dd>
<dt><strong>Version 1.3.0</strong> [26/Oct/2007]</dt>
<dd>
<ul>
<li>added function
<a href="manual.html#symlinkattributes"><code>lfs.symlinkattributes</code></a>
(works only in non Windows systems).</li>
</ul>
</dd>
<dt><strong>Version 1.2.1</strong> [08/May/2007]</dt>
<dd>
<ul>
<li>compatible only with Lua 5.1 (Lua 5.0 support was dropped)</li>
</ul>
</dd>
<dt><strong>Version 1.2</strong> [15/Mar/2006]</dt>
<dd>
<ul>
<li>added optional argument to
<a href="manual.html#attributes"><code>lfs.attributes</code></a></li>
<li>added function
<a href="manual.html#rmdir"><code>lfs.rmdir</code></a></li>
<li>bug correction on <a href="manual.html#dir"><code>lfs.dir</code></a></li>
</ul>
</dd>
<dt><strong>Version 1.1</strong> [30/May/2005]</dt>
<dd>
<ul>
<li>added function <a href="manual.html#touch"><code>lfs.touch</code></a>.</li>
</ul>
</dd>
<dt><strong>Version 1.0</strong> [21/Jan/2005]</dt>
<dd />
<dt><strong>Version 1.0 Beta</strong> [10/Nov/2004]</dt>
<dd />
</dl>
<h2><a name="credits"></a>Credits</h2>
<p>LuaFileSystem was designed by Roberto Ierusalimschy,
Andr&eacute; Carregal and Tom&aacute;s Guisasola as part of the
<a href="http://www.keplerproject.org">Kepler Project</a>,
which holds its copyright. LuaFileSystem is currently maintained by F&aacute;bio Mascarenhas.</p>
<h2><a name="contact"></a>Contact us</h2>
<p>For more information please
<a href="mailto:info-NO-SPAM-THANKS@keplerproject.org">contact us</a>.
Comments are welcome!</p>
<p>You can also reach other Kepler developers and users on the Kepler Project
<a href="http://luaforge.net/mail/?group_id=104">mailing list</a>.</p>
</div> <!-- id="content" -->
</div> <!-- id="main" -->
<div id="about">
<p><a href="http://validator.w3.org/check?uri=referer">Valid XHTML 1.0!</a></p>
<p><small>$Id: index.html,v 1.44 2009/02/04 21:21:33 carregal Exp $</small></p>
</div> <!-- id="about" -->
</div> <!-- id="container" -->
</body>
</html>

View File

@@ -0,0 +1,122 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>LuaFileSystem</title>
<link rel="stylesheet" href="doc.css" type="text/css"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<div id="container">
<div id="product">
<div id="product_logo">
<a href="http://www.keplerproject.org">
<img alt="LuaFileSystem" src="luafilesystem.png"/>
</a>
</div>
<div id="product_name"><big><strong>LuaFileSystem</strong></big></div>
<div id="product_description">File System Library for the Lua Programming Language</div>
</div> <!-- id="product" -->
<div id="main">
<div id="navigation">
<h1>LuaFileSystem</h1>
<ul>
<li><a href="index.html">Home</a>
<ul>
<li><a href="index.html#overview">Overview</a></li>
<li><a href="index.html#status">Status</a></li>
<li><a href="index.html#download">Download</a></li>
<li><a href="index.html#history">History</a></li>
<li><a href="index.html#credits">Credits</a></li>
<li><a href="index.html#contact">Contact us</a></li>
</ul>
</li>
<li><a href="manual.html">Manual</a>
<ul>
<li><a href="manual.html#introduction">Introduction</a></li>
<li><a href="manual.html#building">Building</a></li>
<li><a href="manual.html#installation">Installation</a></li>
<li><a href="manual.html#reference">Reference</a></li>
</ul>
</li>
<li><a href="examples.html">Examples</a></li>
<li><a href="https://github.com/keplerproject/luafilesystem">Project</a>
<ul>
<li><a href="https://github.com/keplerproject/luafilesystem/issues/">Bug Tracker</a></li>
<li><a href="https://github.com/keplerproject/luafilesystem">Git</a></li>
</ul>
</li>
<li><strong>License</strong></li>
</ul>
</div> <!-- id="navigation" -->
<div id="content">
<h1>License</h1>
<p>
LuaFileSystem is free software: it can be used for both academic
and commercial purposes at absolutely no cost. There are no
royalties or GNU-like "copyleft" restrictions. LuaFileSystem
qualifies as
<a href="http://www.opensource.org/docs/definition.html">Open Source</a>
software.
Its licenses are compatible with
<a href="http://www.gnu.org/licenses/gpl.html">GPL</a>.
LuaFileSystem is not in the public domain and the
<a href="http://www.keplerproject.org">Kepler Project</a>
keep its copyright.
The legal details are below.
</p>
<p>The spirit of the license is that you are free to use
LuaFileSystem for any purpose at no cost without having to ask us.
The only requirement is that if you do use LuaFileSystem, then you
should give us credit by including the appropriate copyright notice
somewhere in your product or its documentation.</p>
<p>The LuaFileSystem library is designed and implemented by Roberto
Ierusalimschy, Andr&eacute; Carregal and Tom&aacute;s Guisasola.
The implementation is not derived from licensed software.</p>
<hr/>
<p>Copyright &copy; 2003 Kepler Project.</p>
<p>Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:</p>
<p>The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.</p>
<p>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.</p>
</div> <!-- id="content" -->
</div> <!-- id="main" -->
<div id="about">
<p><a href="http://validator.w3.org/check?uri=referer">Valid XHTML 1.0!</a></p>
<p><small>$Id: license.html,v 1.13 2008/02/11 22:42:21 carregal Exp $</small></p>
</div><!-- id="about" -->
</div><!-- id="container" -->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -0,0 +1,280 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>LuaFileSystem</title>
<link rel="stylesheet" href="doc.css" type="text/css"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<div id="container">
<div id="product">
<div id="product_logo">
<a href="http://www.keplerproject.org"><img alt="LuaFileSystem" src="luafilesystem.png"/></a>
</div>
<div id="product_name"><big><strong>LuaFileSystem</strong></big></div>
<div id="product_description">File System Library for the Lua Programming Language</div>
</div> <!-- id="product" -->
<div id="main">
<div id="navigation">
<h1>LuaFileSystem</h1>
<ul>
<li><a href="index.html">Home</a>
<ul>
<li><a href="index.html#overview">Overview</a></li>
<li><a href="index.html#status">Status</a></li>
<li><a href="index.html#download">Download</a></li>
<li><a href="index.html#history">History</a></li>
<li><a href="index.html#credits">Credits</a></li>
<li><a href="index.html#contact">Contact us</a></li>
</ul>
</li>
<li><strong>Manual</strong>
<ul>
<li><a href="manual.html#introduction">Introduction</a></li>
<li><a href="manual.html#building">Building</a></li>
<li><a href="manual.html#installation">Installation</a></li>
<li><a href="manual.html#reference">Reference</a></li>
</ul>
</li>
<li><a href="examples.html">Examples</a></li>
<li><a href="https://github.com/keplerproject/luafilesystem">Project</a>
<ul>
<li><a href="https://github.com/keplerproject/luafilesystem/issues">Bug Tracker</a></li>
<li><a href="https://github.com/keplerproject/luafilesystem">Git</a></li>
</ul>
</li>
<li><a href="license.html">License</a></li>
</ul>
</div> <!-- id="navigation" -->
<div id="content">
<h2><a name="introduction"></a>Introduction</h2>
<p>LuaFileSystem is a <a href="http://www.lua.org">Lua</a> library
developed to complement the set of functions related to file
systems offered by the standard Lua distribution.</p>
<p>LuaFileSystem offers a portable way to access
the underlying directory structure and file attributes.</p>
<h2><a name="building"></a>Building</h2>
<p>
LuaFileSystem should be built with Lua 5.1 so the language library
and header files for the target version must be installed properly.
</p>
<p>
LuaFileSystem offers a Makefile and a separate configuration file,
<code>config</code>,
which should be edited to suit your installation before running
<code>make</code>.
The file has some definitions like paths to the external libraries,
compiler options and the like.
</p>
<p>On Windows, the C runtime used to compile LuaFileSystem must be the same
runtime that Lua uses, or some LuaFileSystem functions will not work.</p>
<h2><a name="installation"></a>Installation</h2>
<p>The easiest way to install LuaFileSystem is to use LuaRocks:</p>
<pre class="example">
luarocks install luafilesystem
</pre>
<p>If you prefer to install LuaFileSystem manually, the compiled binary should be copied to a directory in your
<a href="http://www.lua.org/manual/5.1/manual.html#pdf-package.cpath">C path</a>.</p>
<h2><a name="reference"></a>Reference</h2>
<p>
LuaFileSystem offers the following functions:
</p>
<dl class="reference">
<dt><a name="attributes"></a><strong><code>lfs.attributes (filepath [, aname])</code></strong></dt>
<dd>Returns a table with the file attributes corresponding to
<code>filepath</code> (or <code>nil</code> followed by an error message
in case of error).
If the second optional argument is given, then only the value of the
named attribute is returned (this use is equivalent to
<code>lfs.attributes(filepath).aname</code>, but the table is not created
and only one attribute is retrieved from the O.S.).
The attributes are described as follows;
attribute <code>mode</code> is a string, all the others are numbers,
and the time related attributes use the same time reference of
<a href="http://www.lua.org/manual/5.1/manual.html#pdf-os.time"><code>os.time</code></a>:
<dl>
<dt><strong><code>dev</code></strong></dt>
<dd>on Unix systems, this represents the device that the inode resides on. On Windows systems,
represents the drive number of the disk containing the file</dd>
<dt><strong><code>ino</code></strong></dt>
<dd>on Unix systems, this represents the inode number. On Windows systems this has no meaning</dd>
<dt><strong><code>mode</code></strong></dt>
<dd>string representing the associated protection mode (the values could be
<code>file</code>, <code>directory</code>, <code>link</code>, <code>socket</code>,
<code>named pipe</code>, <code>char device</code>, <code>block device</code> or
<code>other</code>)</dd>
<dt><strong><code>nlink</code></strong></dt>
<dd>number of hard links to the file</dd>
<dt><strong><code>uid</code></strong></dt>
<dd>user-id of owner (Unix only, always 0 on Windows)</dd>
<dt><strong><code>gid</code></strong></dt>
<dd>group-id of owner (Unix only, always 0 on Windows)</dd>
<dt><strong><code>rdev</code></strong></dt>
<dd>on Unix systems, represents the device type, for special file inodes.
On Windows systems represents the same as <code>dev</code></dd>
<dt><strong><code>access</code></strong></dt>
<dd>time of last access</dd>
<dt><strong><code>modification</code></strong></dt>
<dd>time of last data modification</dd>
<dt><strong><code>change</code></strong></dt>
<dd>time of last file status change</dd>
<dt><strong><code>size</code></strong></dt>
<dd>file size, in bytes</dd>
<dt><strong><code>blocks</code></strong></dt>
<dd>block allocated for file; (Unix only)</dd>
<dt><strong><code>blksize</code></strong></dt>
<dd>optimal file system I/O blocksize; (Unix only)</dd>
</dl>
This function uses <code>stat</code> internally thus if the given
<code>filepath</code> is a symbolic link, it is followed (if it points to
another link the chain is followed recursively) and the information
is about the file it refers to.
To obtain information about the link itself, see function
<a href="#symlinkattributes">lfs.symlinkattributes</a>.
</dd>
<dt><a name="chdir"></a><strong><code>lfs.chdir (path)</code></strong></dt>
<dd>Changes the current working directory to the given
<code>path</code>.<br />
Returns <code>true</code> in case of success or <code>nil</code> plus an
error string.</dd>
<dt><a name="chdir"></a><strong><code>lfs.lock_dir(path, [seconds_stale])</code></strong></dt>
<dd>Creates a lockfile (called lockfile.lfs) in <code>path</code> if it does not
exist and returns the lock. If the lock already exists checks if
it's stale, using the second parameter (default for the second
parameter is <code>INT_MAX</code>, which in practice means the lock will never
be stale. To free the the lock call <code>lock:free()</code>. <br/>
In case of any errors it returns nil and the error message. In
particular, if the lock exists and is not stale it returns the
"File exists" message.</dd>
<dt><a name="getcwd"></a><strong><code>lfs.currentdir ()</code></strong></dt>
<dd>Returns a string with the current working directory or <code>nil</code>
plus an error string.</dd>
<dt><a name="dir"></a><strong><code>iter, dir_obj = lfs.dir (path)</code></strong></dt>
<dd>
Lua iterator over the entries of a given directory.
Each time the iterator is called with <code>dir_obj</code> it returns a directory entry's name as a string, or
<code>nil</code> if there are no more entries. You can also iterate by calling <code>dir_obj:next()</code>, and
explicitly close the directory before the iteration finished with <code>dir_obj:close()</code>.
Raises an error if <code>path</code> is not a directory.
</dd>
<dt><a name="lock"></a><strong><code>lfs.lock (filehandle, mode[, start[, length]])</code></strong></dt>
<dd>Locks a file or a part of it. This function works on <em>open files</em>; the
file handle should be specified as the first argument.
The string <code>mode</code> could be either
<code>r</code> (for a read/shared lock) or <code>w</code> (for a
write/exclusive lock). The optional arguments <code>start</code>
and <code>length</code> can be used to specify a starting point and
its length; both should be numbers.<br />
Returns <code>true</code> if the operation was successful; in
case of error, it returns <code>nil</code> plus an error string.
</dd>
<dt><a name="link"></a><strong><code>lfs.link (old, new[, symlink])</code></strong></dt>
<dd>Creates a link. The first argument is the object to link to
and the second is the name of the link. If the optional third
argument is true, the link will by a symbolic link (by default, a
hard link is created).
</dd>
<dt><a name="mkdir"></a><strong><code>lfs.mkdir (dirname)</code></strong></dt>
<dd>Creates a new directory. The argument is the name of the new
directory.<br />
Returns <code>true</code> if the operation was successful;
in case of error, it returns <code>nil</code> plus an error string.
</dd>
<dt><a name="rmdir"></a><strong><code>lfs.rmdir (dirname)</code></strong></dt>
<dd>Removes an existing directory. The argument is the name of the directory.<br />
Returns <code>true</code> if the operation was successful;
in case of error, it returns <code>nil</code> plus an error string.</dd>
<dt><a name="setmode"></a><strong><code>lfs.setmode (file, mode)</code></strong></dt>
<dd>Sets the writing mode for a file. The mode string can be either <code>"binary"</code> or <code>"text"</code>.
Returns <code>true</code> followed the previous mode string for the file, or
<code>nil</code> followed by an error string in case of errors.
On non-Windows platforms, where the two modes are identical,
setting the mode has no effect, and the mode is always returned as <code>binary</code>.
</dd>
<dt><a name="symlinkattributes"></a><strong><code>lfs.symlinkattributes (filepath [, aname])</code></strong></dt>
<dd>Identical to <a href="#attributes">lfs.attributes</a> except that
it obtains information about the link itself (not the file it refers to).
On Windows this function does not yet support links, and is identical to
<code>lfs.attributes</code>.
</dd>
<dt><a name="touch"></a><strong><code>lfs.touch (filepath [, atime [, mtime]])</code></strong></dt>
<dd>Set access and modification times of a file. This function is
a bind to <code>utime</code> function. The first argument is the
filename, the second argument (<code>atime</code>) is the access time,
and the third argument (<code>mtime</code>) is the modification time.
Both times are provided in seconds (which should be generated with
Lua standard function <code>os.time</code>).
If the modification time is omitted, the access time provided is used;
if both times are omitted, the current time is used.<br />
Returns <code>true</code> if the operation was successful;
in case of error, it returns <code>nil</code> plus an error string.
</dd>
<dt><a name="unlock"></a><strong><code>lfs.unlock (filehandle[, start[, length]])</code></strong></dt>
<dd>Unlocks a file or a part of it. This function works on
<em>open files</em>; the file handle should be specified as the first
argument. The optional arguments <code>start</code> and
<code>length</code> can be used to specify a starting point and its
length; both should be numbers.<br />
Returns <code>true</code> if the operation was successful;
in case of error, it returns <code>nil</code> plus an error string.
</dd>
</dl>
</div> <!-- id="content" -->
</div> <!-- id="main" -->
<div id="about">
<p><a href="http://validator.w3.org/check?uri=referer">Valid XHTML 1.0!</a></p>
<p><small>$Id: manual.html,v 1.45 2009/06/03 20:53:55 mascarenhas Exp $</small></p>
</div> <!-- id="about" -->
</div> <!-- id="container" -->
</body>
</html>

View File

@@ -0,0 +1,29 @@
package = "LuaFileSystem"
version = "1.6.3-2"
source = {
url = "git://github.com/keplerproject/luafilesystem",
tag = "v_1_6_3"
}
description = {
summary = "File System Library for the Lua Programming Language",
detailed = [[
LuaFileSystem is a Lua library developed to complement the set of
functions related to file systems offered by the standard Lua
distribution. LuaFileSystem offers a portable way to access the
underlying directory structure and file attributes.
]],
homepage = "http://keplerproject.github.io/luafilesystem",
license = "MIT/X11"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
lfs = "src/lfs.c"
},
copy_directories = {
"doc", "tests"
}
}

View File

@@ -0,0 +1,19 @@
rock_manifest = {
doc = {
us = {
["doc.css"] = "d0a913514fb190240b3b4033d105cbc0",
["examples.html"] = "5832f72021728374cf57b621d62ce0ff",
["index.html"] = "96885bdda963939f0a363b5fa6b16b59",
["license.html"] = "e3a756835cb7c8ae277d5e513c8e32ee",
["luafilesystem.png"] = "81e923e976e99f894ea0aa8b52baff29",
["manual.html"] = "d6473799b73ce486c3ea436586cb3b34"
}
},
lib = {
["lfs.dll"] = "c0e2145e1ef2815ae5fae01454291b66"
},
["luafilesystem-1.6.3-2.rockspec"] = "eb0ef7c190516892eb8357af799eea5f",
tests = {
["test.lua"] = "7b4ddb5bdb7e0b1b1ed0150d473535c9"
}
}

View File

@@ -0,0 +1,175 @@
#!/usr/bin/env lua5.1
local tmp = "/tmp"
local sep = string.match (package.config, "[^\n]+")
local upper = ".."
local lfs = require"lfs"
print (lfs._VERSION)
io.write(".")
io.flush()
function attrdir (path)
for file in lfs.dir(path) do
if file ~= "." and file ~= ".." then
local f = path..sep..file
print ("\t=> "..f.." <=")
local attr = lfs.attributes (f)
assert (type(attr) == "table")
if attr.mode == "directory" then
attrdir (f)
else
for name, value in pairs(attr) do
print (name, value)
end
end
end
end
end
-- Checking changing directories
local current = assert (lfs.currentdir())
local reldir = string.gsub (current, "^.*%"..sep.."([^"..sep.."])$", "%1")
assert (lfs.chdir (upper), "could not change to upper directory")
assert (lfs.chdir (reldir), "could not change back to current directory")
assert (lfs.currentdir() == current, "error trying to change directories")
assert (lfs.chdir ("this couldn't be an actual directory") == nil, "could change to a non-existent directory")
io.write(".")
io.flush()
-- Changing creating and removing directories
local tmpdir = current..sep.."lfs_tmp_dir"
local tmpfile = tmpdir..sep.."tmp_file"
-- Test for existence of a previous lfs_tmp_dir
-- that may have resulted from an interrupted test execution and remove it
if lfs.chdir (tmpdir) then
assert (lfs.chdir (upper), "could not change to upper directory")
assert (os.remove (tmpfile), "could not remove file from previous test")
assert (lfs.rmdir (tmpdir), "could not remove directory from previous test")
end
io.write(".")
io.flush()
-- tries to create a directory
assert (lfs.mkdir (tmpdir), "could not make a new directory")
local attrib, errmsg = lfs.attributes (tmpdir)
if not attrib then
error ("could not get attributes of file `"..tmpdir.."':\n"..errmsg)
end
local f = io.open(tmpfile, "w")
f:close()
io.write(".")
io.flush()
-- Change access time
local testdate = os.time({ year = 2007, day = 10, month = 2, hour=0})
assert (lfs.touch (tmpfile, testdate))
local new_att = assert (lfs.attributes (tmpfile))
assert (new_att.access == testdate, "could not set access time")
assert (new_att.modification == testdate, "could not set modification time")
io.write(".")
io.flush()
-- Change access and modification time
local testdate1 = os.time({ year = 2007, day = 10, month = 2, hour=0})
local testdate2 = os.time({ year = 2007, day = 11, month = 2, hour=0})
assert (lfs.touch (tmpfile, testdate2, testdate1))
local new_att = assert (lfs.attributes (tmpfile))
assert (new_att.access == testdate2, "could not set access time")
assert (new_att.modification == testdate1, "could not set modification time")
io.write(".")
io.flush()
-- Checking link (does not work on Windows)
if lfs.link (tmpfile, "_a_link_for_test_", true) then
assert (lfs.attributes"_a_link_for_test_".mode == "file")
assert (lfs.symlinkattributes"_a_link_for_test_".mode == "link")
assert (lfs.link (tmpfile, "_a_hard_link_for_test_"))
assert (lfs.attributes (tmpfile, "nlink") == 2)
assert (os.remove"_a_link_for_test_")
assert (os.remove"_a_hard_link_for_test_")
end
io.write(".")
io.flush()
-- Checking text/binary modes (only has an effect in Windows)
local f = io.open(tmpfile, "w")
local result, mode = lfs.setmode(f, "binary")
assert(result) -- on non-Windows platforms, mode is always returned as "binary"
result, mode = lfs.setmode(f, "text")
assert(result and mode == "binary")
f:close()
io.write(".")
io.flush()
-- Restore access time to current value
assert (lfs.touch (tmpfile, attrib.access, attrib.modification))
new_att = assert (lfs.attributes (tmpfile))
assert (new_att.access == attrib.access)
assert (new_att.modification == attrib.modification)
io.write(".")
io.flush()
-- Check consistency of lfs.attributes values
local attr = lfs.attributes (tmpfile)
for key, value in pairs(attr) do
assert (value == lfs.attributes (tmpfile, key),
"lfs.attributes values not consistent")
end
-- Remove new file and directory
assert (os.remove (tmpfile), "could not remove new file")
assert (lfs.rmdir (tmpdir), "could not remove new directory")
assert (lfs.mkdir (tmpdir..sep.."lfs_tmp_dir") == nil, "could create a directory inside a non-existent one")
io.write(".")
io.flush()
-- Trying to get attributes of a non-existent file
assert (lfs.attributes ("this couldn't be an actual file") == nil, "could get attributes of a non-existent file")
assert (type(lfs.attributes (upper)) == "table", "couldn't get attributes of upper directory")
io.write(".")
io.flush()
-- Stressing directory iterator
count = 0
for i = 1, 4000 do
for file in lfs.dir (tmp) do
count = count + 1
end
end
io.write(".")
io.flush()
-- Stressing directory iterator, explicit version
count = 0
for i = 1, 4000 do
local iter, dir = lfs.dir(tmp)
local file = dir:next()
while file do
count = count + 1
file = dir:next()
end
assert(not pcall(dir.next, dir))
end
io.write(".")
io.flush()
-- directory explicit close
local iter, dir = lfs.dir(tmp)
dir:close()
assert(not pcall(dir.next, dir))
print"Ok!"

View File

@@ -0,0 +1,653 @@
#!/usr/bin/env lua
---------
-- LuaSrcDiet
--
-- Compresses Lua source code by removing unnecessary characters.
-- For Lua 5.1+ source code.
--
-- **Notes:**
--
-- * Remember to update version and date information below (MSG_TITLE).
-- * TODO: passing data tables around is a horrific mess.
-- * TODO: to implement pcall() to properly handle lexer etc. errors.
-- * TODO: need some automatic testing for a semblance of sanity.
-- * TODO: the plugin module is highly experimental and unstable.
----
local equiv = require "luasrcdiet.equiv"
local fs = require "luasrcdiet.fs"
local llex = require "luasrcdiet.llex"
local lparser = require "luasrcdiet.lparser"
local luasrcdiet = require "luasrcdiet.init"
local optlex = require "luasrcdiet.optlex"
local optparser = require "luasrcdiet.optparser"
local byte = string.byte
local concat = table.concat
local find = string.find
local fmt = string.format
local gmatch = string.gmatch
local match = string.match
local print = print
local rep = string.rep
local sub = string.sub
local plugin
local LUA_VERSION = match(_VERSION, " (5%.[123])$") or "5.1"
-- Is --opt-binequiv available for this Lua version?
local BIN_EQUIV_AVAIL = LUA_VERSION == "5.1" and not package.loaded.jit
---------------------- Messages and textual data ----------------------
local MSG_TITLE = fmt([[
LuaSrcDiet: Puts your Lua 5.1+ source code on a diet
Version %s <%s>
]], luasrcdiet._VERSION, luasrcdiet._HOMEPAGE)
local MSG_USAGE = [[
usage: luasrcdiet [options] [filenames]
example:
>luasrcdiet myscript.lua -o myscript_.lua
options:
-v, --version prints version information
-h, --help prints usage information
-o <file> specify file name to write output
-s <suffix> suffix for output files (default '_')
--keep <msg> keep block comment with <msg> inside
--plugin <module> run <module> in plugin/ directory
- stop handling arguments
(optimization levels)
--none all optimizations off (normalizes EOLs only)
--basic lexer-based optimizations only
--maximum maximize reduction of source
(informational)
--quiet process files quietly
--read-only read file and print token stats only
--dump-lexer dump raw tokens from lexer to stdout
--dump-parser dump variable tracking tables from parser
--details extra info (strings, numbers, locals)
features (to disable, insert 'no' prefix like --noopt-comments):
%s
default settings:
%s]]
-- Optimization options, for ease of switching on and off.
--
-- * Positive to enable optimization, negative (no) to disable.
-- * These options should follow --opt-* and --noopt-* style for now.
local OPTION = [[
--opt-comments,'remove comments and block comments'
--opt-whitespace,'remove whitespace excluding EOLs'
--opt-emptylines,'remove empty lines'
--opt-eols,'all above, plus remove unnecessary EOLs'
--opt-strings,'optimize strings and long strings'
--opt-numbers,'optimize numbers'
--opt-locals,'optimize local variable names'
--opt-entropy,'tries to reduce symbol entropy of locals'
--opt-srcequiv,'insist on source (lexer stream) equivalence'
--opt-binequiv,'insist on binary chunk equivalence (only for PUC Lua 5.1)'
--opt-experimental,'apply experimental optimizations'
]]
-- Preset configuration.
local DEFAULT_CONFIG = [[
--opt-comments --opt-whitespace --opt-emptylines
--opt-numbers --opt-locals
--opt-srcequiv --noopt-binequiv
]]
-- Override configurations: MUST explicitly enable/disable everything.
local BASIC_CONFIG = [[
--opt-comments --opt-whitespace --opt-emptylines
--noopt-eols --noopt-strings --noopt-numbers
--noopt-locals --noopt-entropy
--opt-srcequiv --noopt-binequiv
]]
local MAXIMUM_CONFIG = [[
--opt-comments --opt-whitespace --opt-emptylines
--opt-eols --opt-strings --opt-numbers
--opt-locals --opt-entropy
--opt-srcequiv
]] .. (BIN_EQUIV_AVAIL and ' --opt-binequiv' or ' --noopt-binequiv')
local NONE_CONFIG = [[
--noopt-comments --noopt-whitespace --noopt-emptylines
--noopt-eols --noopt-strings --noopt-numbers
--noopt-locals --noopt-entropy
--opt-srcequiv --noopt-binequiv
]]
local DEFAULT_SUFFIX = "_" -- default suffix for file renaming
local PLUGIN_SUFFIX = "luasrcdiet.plugin." -- relative location of plugins
------------- Startup and initialize option list handling -------------
--- Simple error message handler; change to error if traceback wanted.
--
-- @tparam string msg The message to print.
local function die(msg)
print("LuaSrcDiet (error): "..msg); os.exit(1)
end
--die = error--DEBUG
-- Prepare text for list of optimizations, prepare lookup table.
local MSG_OPTIONS = ""
do
local WIDTH = 24
local o = {}
for op, desc in gmatch(OPTION, "%s*([^,]+),'([^']+)'") do
local msg = " "..op
msg = msg..rep(" ", WIDTH - #msg)..desc.."\n"
MSG_OPTIONS = MSG_OPTIONS..msg
o[op] = true
o["--no"..sub(op, 3)] = true
end
OPTION = o -- replace OPTION with lookup table
end
MSG_USAGE = fmt(MSG_USAGE, MSG_OPTIONS, DEFAULT_CONFIG)
--------- Global variable initialization, option set handling ---------
local suffix = DEFAULT_SUFFIX -- file suffix
local option = {} -- program options
local stat_c, stat_l -- statistics tables
--- Sets option lookup table based on a text list of options.
--
-- Note: additional forced settings for --opt-eols is done in optlex.lua.
--
-- @tparam string CONFIG
local function set_options(CONFIG)
for op in gmatch(CONFIG, "(%-%-%S+)") do
if sub(op, 3, 4) == "no" and -- handle negative options
OPTION["--"..sub(op, 5)] then
option[sub(op, 5)] = false
else
option[sub(op, 3)] = true
end
end
end
-------------------------- Support functions --------------------------
-- List of token types, parser-significant types are up to TTYPE_GRAMMAR
-- while the rest are not used by parsers; arranged for stats display.
local TTYPES = {
"TK_KEYWORD", "TK_NAME", "TK_NUMBER", -- grammar
"TK_STRING", "TK_LSTRING", "TK_OP",
"TK_EOS",
"TK_COMMENT", "TK_LCOMMENT", -- non-grammar
"TK_EOL", "TK_SPACE",
}
local TTYPE_GRAMMAR = 7
local EOLTYPES = { -- EOL names for token dump
["\n"] = "LF", ["\r"] = "CR",
["\n\r"] = "LFCR", ["\r\n"] = "CRLF",
}
--- Reads source code from the file.
--
-- @tparam string fname Path of the file to read.
-- @treturn string Content of the file.
local function load_file(fname)
local data, err = fs.read_file(fname, "rb")
if not data then die(err) end
return data
end
--- Saves source code to the file.
--
-- @tparam string fname Path of the destination file.
-- @tparam string dat The data to write into the file.
local function save_file(fname, dat)
local ok, err = fs.write_file(fname, dat, "wb")
if not ok then die(err) end
end
------------------ Functions to deal with statistics ------------------
--- Initializes the statistics table.
local function stat_init()
stat_c, stat_l = {}, {}
for i = 1, #TTYPES do
local ttype = TTYPES[i]
stat_c[ttype], stat_l[ttype] = 0, 0
end
end
--- Adds a token to the statistics table.
--
-- @tparam string tok The token.
-- @param seminfo
local function stat_add(tok, seminfo)
stat_c[tok] = stat_c[tok] + 1
stat_l[tok] = stat_l[tok] + #seminfo
end
--- Computes totals for the statistics table, returns average table.
--
-- @treturn table
local function stat_calc()
local function avg(c, l) -- safe average function
if c == 0 then return 0 end
return l / c
end
local stat_a = {}
local c, l = 0, 0
for i = 1, TTYPE_GRAMMAR do -- total grammar tokens
local ttype = TTYPES[i]
c = c + stat_c[ttype]; l = l + stat_l[ttype]
end
stat_c.TOTAL_TOK, stat_l.TOTAL_TOK = c, l
stat_a.TOTAL_TOK = avg(c, l)
c, l = 0, 0
for i = 1, #TTYPES do -- total all tokens
local ttype = TTYPES[i]
c = c + stat_c[ttype]; l = l + stat_l[ttype]
stat_a[ttype] = avg(stat_c[ttype], stat_l[ttype])
end
stat_c.TOTAL_ALL, stat_l.TOTAL_ALL = c, l
stat_a.TOTAL_ALL = avg(c, l)
return stat_a
end
----------------------------- Main tasks -----------------------------
--- A simple token dumper, minimal translation of seminfo data.
--
-- @tparam string srcfl Path of the source file.
local function dump_tokens(srcfl)
-- Load file and process source input into tokens.
local z = load_file(srcfl)
local toklist, seminfolist = llex.lex(z)
-- Display output.
for i = 1, #toklist do
local tok, seminfo = toklist[i], seminfolist[i]
if tok == "TK_OP" and byte(seminfo) < 32 then
seminfo = "("..byte(seminfo)..")"
elseif tok == "TK_EOL" then
seminfo = EOLTYPES[seminfo]
else
seminfo = "'"..seminfo.."'"
end
print(tok.." "..seminfo)
end--for
end
--- Dumps globalinfo and localinfo tables.
--
-- @tparam string srcfl Path of the source file.
local function dump_parser(srcfl)
-- Load file and process source input into tokens,
local z = load_file(srcfl)
local toklist, seminfolist, toklnlist = llex.lex(z)
-- Do parser optimization here.
local xinfo = lparser.parse(toklist, seminfolist, toklnlist)
local globalinfo, localinfo = xinfo.globalinfo, xinfo.localinfo
-- Display output.
local hl = rep("-", 72)
print("*** Local/Global Variable Tracker Tables ***")
print(hl.."\n GLOBALS\n"..hl)
-- global tables have a list of xref numbers only
for i = 1, #globalinfo do
local obj = globalinfo[i]
local msg = "("..i..") '"..obj.name.."' -> "
local xref = obj.xref
for j = 1, #xref do msg = msg..xref[j].." " end
print(msg)
end
-- Local tables have xref numbers and a few other special
-- numbers that are specially named: decl (declaration xref),
-- act (activation xref), rem (removal xref).
print(hl.."\n LOCALS (decl=declared act=activated rem=removed)\n"..hl)
for i = 1, #localinfo do
local obj = localinfo[i]
local msg = "("..i..") '"..obj.name.."' decl:"..obj.decl..
" act:"..obj.act.." rem:"..obj.rem
if obj.is_special then
msg = msg.." is_special"
end
msg = msg.." -> "
local xref = obj.xref
for j = 1, #xref do msg = msg..xref[j].." " end
print(msg)
end
print(hl.."\n")
end
--- Reads source file(s) and reports some statistics.
--
-- @tparam string srcfl Path of the source file.
local function read_only(srcfl)
-- Load file and process source input into tokens.
local z = load_file(srcfl)
local toklist, seminfolist = llex.lex(z)
print(MSG_TITLE)
print("Statistics for: "..srcfl.."\n")
-- Collect statistics.
stat_init()
for i = 1, #toklist do
local tok, seminfo = toklist[i], seminfolist[i]
stat_add(tok, seminfo)
end--for
local stat_a = stat_calc()
-- Display output.
local function figures(tt)
return stat_c[tt], stat_l[tt], stat_a[tt]
end
local tabf1, tabf2 = "%-16s%8s%8s%10s", "%-16s%8d%8d%10.2f"
local hl = rep("-", 42)
print(fmt(tabf1, "Lexical", "Input", "Input", "Input"))
print(fmt(tabf1, "Elements", "Count", "Bytes", "Average"))
print(hl)
for i = 1, #TTYPES do
local ttype = TTYPES[i]
print(fmt(tabf2, ttype, figures(ttype)))
if ttype == "TK_EOS" then print(hl) end
end
print(hl)
print(fmt(tabf2, "Total Elements", figures("TOTAL_ALL")))
print(hl)
print(fmt(tabf2, "Total Tokens", figures("TOTAL_TOK")))
print(hl.."\n")
end
--- Processes source file(s), writes output and reports some statistics.
--
-- @tparam string srcfl Path of the source file.
-- @tparam string destfl Path of the destination file where to write optimized source.
local function process_file(srcfl, destfl)
-- handle quiet option
local function print(...) --luacheck: ignore 431
if option.QUIET then return end
_G.print(...)
end
if plugin and plugin.init then -- plugin init
option.EXIT = false
plugin.init(option, srcfl, destfl)
if option.EXIT then return end
end
print(MSG_TITLE) -- title message
-- Load file and process source input into tokens.
local z = load_file(srcfl)
if plugin and plugin.post_load then -- plugin post-load
z = plugin.post_load(z) or z
if option.EXIT then return end
end
local toklist, seminfolist, toklnlist = llex.lex(z)
if plugin and plugin.post_lex then -- plugin post-lex
plugin.post_lex(toklist, seminfolist, toklnlist)
if option.EXIT then return end
end
-- Collect 'before' statistics.
stat_init()
for i = 1, #toklist do
local tok, seminfo = toklist[i], seminfolist[i]
stat_add(tok, seminfo)
end--for
local stat1_a = stat_calc()
local stat1_c, stat1_l = stat_c, stat_l
-- Do parser optimization here.
optparser.print = print -- hack
local xinfo = lparser.parse(toklist, seminfolist, toklnlist)
if plugin and plugin.post_parse then -- plugin post-parse
plugin.post_parse(xinfo.globalinfo, xinfo.localinfo)
if option.EXIT then return end
end
optparser.optimize(option, toklist, seminfolist, xinfo)
if plugin and plugin.post_optparse then -- plugin post-optparse
plugin.post_optparse()
if option.EXIT then return end
end
-- Do lexer optimization here, save output file.
local warn = optlex.warn -- use this as a general warning lookup
optlex.print = print -- hack
toklist, seminfolist, toklnlist
= optlex.optimize(option, toklist, seminfolist, toklnlist)
if plugin and plugin.post_optlex then -- plugin post-optlex
plugin.post_optlex(toklist, seminfolist, toklnlist)
if option.EXIT then return end
end
local dat = concat(seminfolist)
-- Depending on options selected, embedded EOLs in long strings and
-- long comments may not have been translated to \n, tack a warning.
if find(dat, "\r\n", 1, 1) or
find(dat, "\n\r", 1, 1) then
warn.MIXEDEOL = true
end
-- Test source and binary chunk equivalence.
equiv.init(option, llex, warn)
equiv.source(z, dat)
if BIN_EQUIV_AVAIL then
equiv.binary(z, dat)
end
local smsg = "before and after lexer streams are NOT equivalent!"
local bmsg = "before and after binary chunks are NOT equivalent!"
-- for reporting, die if option was selected, else just warn
if warn.SRC_EQUIV then
if option["opt-srcequiv"] then die(smsg) end
else
print("*** SRCEQUIV: token streams are sort of equivalent")
if option["opt-locals"] then
print("(but no identifier comparisons since --opt-locals enabled)")
end
print()
end
if warn.BIN_EQUIV then
if option["opt-binequiv"] then die(bmsg) end
elseif BIN_EQUIV_AVAIL then
print("*** BINEQUIV: binary chunks are sort of equivalent")
print()
end
-- Save optimized source stream to output file.
save_file(destfl, dat)
-- Collect 'after' statistics.
stat_init()
for i = 1, #toklist do
local tok, seminfo = toklist[i], seminfolist[i]
stat_add(tok, seminfo)
end--for
local stat_a = stat_calc()
-- Display output.
print("Statistics for: "..srcfl.." -> "..destfl.."\n")
local function figures(tt)
return stat1_c[tt], stat1_l[tt], stat1_a[tt],
stat_c[tt], stat_l[tt], stat_a[tt]
end
local tabf1, tabf2 = "%-16s%8s%8s%10s%8s%8s%10s",
"%-16s%8d%8d%10.2f%8d%8d%10.2f"
local hl = rep("-", 68)
print("*** lexer-based optimizations summary ***\n"..hl)
print(fmt(tabf1, "Lexical",
"Input", "Input", "Input",
"Output", "Output", "Output"))
print(fmt(tabf1, "Elements",
"Count", "Bytes", "Average",
"Count", "Bytes", "Average"))
print(hl)
for i = 1, #TTYPES do
local ttype = TTYPES[i]
print(fmt(tabf2, ttype, figures(ttype)))
if ttype == "TK_EOS" then print(hl) end
end
print(hl)
print(fmt(tabf2, "Total Elements", figures("TOTAL_ALL")))
print(hl)
print(fmt(tabf2, "Total Tokens", figures("TOTAL_TOK")))
print(hl)
-- Report warning flags from optimizing process.
if warn.LSTRING then
print("* WARNING: "..warn.LSTRING)
elseif warn.MIXEDEOL then
print("* WARNING: ".."output still contains some CRLF or LFCR line endings")
elseif warn.SRC_EQUIV then
print("* WARNING: "..smsg)
elseif warn.BIN_EQUIV then
print("* WARNING: "..bmsg)
end
print()
end
---------------------------- Main functions ---------------------------
local arg = {...} -- program arguments
set_options(DEFAULT_CONFIG) -- set to default options at beginning
--- Does per-file handling, ship off to tasks.
--
-- @tparam {string,...} fspec List of source files.
local function do_files(fspec)
for i = 1, #fspec do
local srcfl = fspec[i]
local destfl
-- Find and replace extension for filenames.
local extb, exte = find(srcfl, "%.[^%.%\\%/]*$")
local basename, extension = srcfl, ""
if extb and extb > 1 then
basename = sub(srcfl, 1, extb - 1)
extension = sub(srcfl, extb, exte)
end
destfl = basename..suffix..extension
if #fspec == 1 and option.OUTPUT_FILE then
destfl = option.OUTPUT_FILE
end
if srcfl == destfl then
die("output filename identical to input filename")
end
-- Perform requested operations.
if option.DUMP_LEXER then
dump_tokens(srcfl)
elseif option.DUMP_PARSER then
dump_parser(srcfl)
elseif option.READ_ONLY then
read_only(srcfl)
else
process_file(srcfl, destfl)
end
end--for
end
--- The main function.
local function main()
local fspec = {}
local argn, i = #arg, 1
if argn == 0 then
option.HELP = true
end
-- Handle arguments.
while i <= argn do
local o, p = arg[i], arg[i + 1]
local dash = match(o, "^%-%-?")
if dash == "-" then -- single-dash options
if o == "-h" then
option.HELP = true; break
elseif o == "-v" then
option.VERSION = true; break
elseif o == "-s" then
if not p then die("-s option needs suffix specification") end
suffix = p
i = i + 1
elseif o == "-o" then
if not p then die("-o option needs a file name") end
option.OUTPUT_FILE = p
i = i + 1
elseif o == "-" then
break -- ignore rest of args
else
die("unrecognized option "..o)
end
elseif dash == "--" then -- double-dash options
if o == "--help" then
option.HELP = true; break
elseif o == "--version" then
option.VERSION = true; break
elseif o == "--keep" then
if not p then die("--keep option needs a string to match for") end
option.KEEP = p
i = i + 1
elseif o == "--plugin" then
if not p then die("--plugin option needs a module name") end
if option.PLUGIN then die("only one plugin can be specified") end
option.PLUGIN = p
plugin = require(PLUGIN_SUFFIX..p)
i = i + 1
elseif o == "--quiet" then
option.QUIET = true
elseif o == "--read-only" then
option.READ_ONLY = true
elseif o == "--basic" then
set_options(BASIC_CONFIG)
elseif o == "--maximum" then
set_options(MAXIMUM_CONFIG)
elseif o == "--none" then
set_options(NONE_CONFIG)
elseif o == "--dump-lexer" then
option.DUMP_LEXER = true
elseif o == "--dump-parser" then
option.DUMP_PARSER = true
elseif o == "--details" then
option.DETAILS = true
elseif OPTION[o] then -- lookup optimization options
set_options(o)
else
die("unrecognized option "..o)
end
else
fspec[#fspec + 1] = o -- potential filename
end
i = i + 1
end--while
if option.HELP then
print(MSG_TITLE..MSG_USAGE); return true
elseif option.VERSION then
print(MSG_TITLE); return true
end
if option["opt-binequiv"] and not BIN_EQUIV_AVAIL then
die("--opt-binequiv is available only for PUC Lua 5.1!")
end
if #fspec > 0 then
if #fspec > 1 and option.OUTPUT_FILE then
die("with -o, only one source file can be specified")
end
do_files(fspec)
return true
else
die("nothing to do!")
end
end
-- entry point -> main() -> do_files()
if not main() then
die("Please run with option -h or --help for usage information")
end

View File

@@ -0,0 +1,300 @@
= Features and Usage
Kein-Hong Man
2011-09-13
== Features
LuaSrcDiet features include the following:
* Predefined default, _--basic_ (token-only) and _--maximum_ settings.
* Avoid deleting a block comment with a certain message with _--keep_; this is for copyright or license texts.
* Special handling for `#!` (shbang) lines and in functions, `self` implicit parameters.
* Dumping of raw information using _--dump-lexer_ and _--dump-parser_.
See the `samples` directory.
* A HTML plugin: outputs files that highlights globals and locals, useful for eliminating globals. See the `samples` directory.
* An SLOC plugin: counts significant lines of Lua code, like SLOCCount.
* Source and binary equivalence testing with _--opt-srcequiv_ and _--opt-binequiv_.
List of optimizations:
* Line endings are always normalized to LF, except those embedded in comments or strings.
* _--opt-comments_: Removal of comments and comment blocks.
* _--opt-whitespace_: Removal of whitespace, excluding end-of-line characters.
* _--opt-emptylines_: Removal of empty lines.
* _--opt-eols_: Removal of unnecessary end-of-line characters.
* _--opt-strings_: Rewrite strings and long strings. See the `samples` directory.
* _--opt-numbers_: Rewrite numbers. See the `samples` directory.
* _--opt-locals_: Rename local variable names. Does not rename field or method names.
* _--opt-entropy_: Tries to improve symbol entropy when renaming locals by calculating actual letter frequencies.
* _--opt-experimental_: Apply experimental optimizations.
LuaSrcDiet tries to allow each option to be enabled or disabled separately, but they are not completely orthogonal.
If comment removal is disabled, LuaSrcDiet only removes trailing whitespace.
Trailing whitespace is not removed in long strings, a warning is generated instead.
If empty line removal is disabled, LuaSrcDiet keeps all significant code on the same lines.
Thus, a user is able to debug using the original sources as a reference since the line numbering is unchanged.
String optimization deals mainly with optimizing escape sequences, but delimiters can be switched between single quotes and double quotes if the source size of the string can be reduced.
For long strings and long comments, LuaSrcDiet also tries to reduce the `=` separators in the
delimiters if possible.
For number optimization, LuaSrcDiet saves space by trying to generate the shortest possible sequence, and in the process it does not produce “proper” scientific notation (e.g. 1.23e5) but does away with the decimal point (e.g. 123e3) instead.
The local variable name optimizer uses a full parser of Lua 5.1 source code, thus it can rename all local variables, including upvalues and function parameters.
It should handle the implicit `self` parameter gracefully.
In addition, local variable names are either renamed into the shortest possible names following English frequent letter usage or are arranged by calculating entropy with the _--opt-entropy_ option.
Variable names are reused whenever possible, reducing the number of unique variable names.
For example, for `LuaSrcDiet.lua` (version 0.11.0), 683 local identifiers representing 88 unique names were optimized into 32 unique names, all which are one character in length, saving over 2600 bytes.
If you need some kind of reassurance that your app will still work at reduced size, see the section on verification below.
== Usage
LuaSrcDiet needs a Lua 5.1.x (preferably Lua 5.1.4) binary to run.
On Unix machines, one can use the following command line:
[source, sh]
LuaSrcDiet myscript.lua -o myscript_.lua
On Windows machines, the above command line can be used on Cygwin, or you can run Lua with the LuaSrcDiet script like this:
[source, sh]
lua LuaSrcDiet.lua myscript.lua -o myscript_.lua
When run without arguments, LuaSrcDiet prints a list of options.
Also, you can check the `Makefile` for some examples of command lines to use.
For example, for maximum code size reduction and maximum verbosity, use:
[source, sh]
LuaSrcDiet --maximum --details myscript.lua -o myscript_.lua
=== Output Example
A sample output of LuaSrcDiet 0.11.0 for processing `llex.lua` at _--maximum_ settings is as follows:
----
Statistics for: LuaSrcDiet.lua -> sample/LuaSrcDiet.lua
*** local variable optimization summary ***
----------------------------------------------------------
Variable Unique Decl. Token Size Average
Types Names Count Count Bytes Bytes
----------------------------------------------------------
Global 10 0 19 95 5.00
----------------------------------------------------------
Local (in) 88 153 683 3340 4.89
TOTAL (in) 98 153 702 3435 4.89
----------------------------------------------------------
Local (out) 32 153 683 683 1.00
TOTAL (out) 42 153 702 778 1.11
----------------------------------------------------------
*** lexer-based optimizations summary ***
--------------------------------------------------------------------
Lexical Input Input Input Output Output Output
Elements Count Bytes Average Count Bytes Average
--------------------------------------------------------------------
TK_KEYWORD 374 1531 4.09 374 1531 4.09
TK_NAME 795 3963 4.98 795 1306 1.64
TK_NUMBER 54 59 1.09 54 59 1.09
TK_STRING 152 1725 11.35 152 1717 11.30
TK_LSTRING 7 1976 282.29 7 1976 282.29
TK_OP 997 1092 1.10 997 1092 1.10
TK_EOS 1 0 0.00 1 0 0.00
--------------------------------------------------------------------
TK_COMMENT 140 6884 49.17 1 18 18.00
TK_LCOMMENT 7 1723 246.14 0 0 0.00
TK_EOL 543 543 1.00 197 197 1.00
TK_SPACE 1270 2465 1.94 263 263 1.00
--------------------------------------------------------------------
Total Elements 4340 21961 5.06 2841 8159 2.87
--------------------------------------------------------------------
Total Tokens 2380 10346 4.35 2380 7681 3.23
--------------------------------------------------------------------
----
Overall, the file size is reduced by more than 9 kiB.
Tokens in the above report can be classified into “real” or actual tokens, and “fake” or whitespace tokens.
The number of “real” tokens remained the same.
Short comments and long comments were completely eliminated.
The number of line endings was reduced by 59, while all but 152 whitespace characters were optimized away.
So, token separators (whitespace, including line endings) now takes up just 10 % of the total file size.
No optimization of number tokens was possible, while 2 bytes were saved for string tokens.
For local variable name optimization, the report shows that 38 unique local variable names were reduced to 20 unique names.
The number of identifier tokens should stay the same (there is currently no optimization option to optimize away non-essential or unused “real” tokens).
Since there can be at most 53 single-character identifiers, all local variables are now one character in length.
Over 600 bytes was saved.
_--details_ will give a longer report and much more information.
A sample output of LuaSrcDiet 0.12.0 for processing the one-file `LuaSrcDiet.lua` program itself at _--maximum_ and _--opt-experimental_ settings is as follows:
----
*** local variable optimization summary ***
----------------------------------------------------------
Variable Unique Decl. Token Size Average
Types Names Count Count Bytes Bytes
----------------------------------------------------------
Global 27 0 51 280 5.49
----------------------------------------------------------
Local (in) 482 1063 4889 21466 4.39
TOTAL (in) 509 1063 4940 21746 4.40
----------------------------------------------------------
Local (out) 55 1063 4889 4897 1.00
TOTAL (out) 82 1063 4940 5177 1.05
----------------------------------------------------------
*** BINEQUIV: binary chunks are sort of equivalent
Statistics for: LuaSrcDiet.lua -> app_experimental.lua
*** lexer-based optimizations summary ***
--------------------------------------------------------------------
Lexical Input Input Input Output Output Output
Elements Count Bytes Average Count Bytes Average
--------------------------------------------------------------------
TK_KEYWORD 3083 12247 3.97 3083 12247 3.97
TK_NAME 5401 24121 4.47 5401 7552 1.40
TK_NUMBER 467 494 1.06 467 494 1.06
TK_STRING 787 7983 10.14 787 7974 10.13
TK_LSTRING 14 3453 246.64 14 3453 246.64
TK_OP 6381 6861 1.08 6171 6651 1.08
TK_EOS 1 0 0.00 1 0 0.00
--------------------------------------------------------------------
TK_COMMENT 1611 72339 44.90 1 18 18.00
TK_LCOMMENT 18 4404 244.67 0 0 0.00
TK_EOL 4419 4419 1.00 1778 1778 1.00
TK_SPACE 10439 24475 2.34 2081 2081 1.00
--------------------------------------------------------------------
Total Elements 32621 160796 4.93 19784 42248 2.14
--------------------------------------------------------------------
Total Tokens 16134 55159 3.42 15924 38371 2.41
--------------------------------------------------------------------
* WARNING: before and after lexer streams are NOT equivalent!
----
The command line was:
[source, sh]
lua LuaSrcDiet.lua LuaSrcDiet.lua -o app_experimental.lua --maximum --opt-experimental --noopt-srcequiv
The important thing to note is that while the binary chunks are equivalent, the source lexer streams are not equivalent.
Hence, the _--noopt-srcequiv_ makes LuaSrcDiet report a warning for failing the source equivalence test.
`LuaSrcDiet.lua` was reduced from 157 kiB to about 41.3 kiB.
The _--opt-experimental_ option saves an extra 205 bytes over standard _--maximum_.
Note the reduction in `TK_OP` count due to a reduction in semicolons and parentheses.
`TK_SPACE` has actually increased a bit due to semicolons that are changed into single spaces; some of these spaces could not be removed.
For more performance numbers, see the <<performance-stats#, Performance Statistics>> page.
== Verification
Code size reduction can be quite a hairy thing (even I peer at the results in suspicion), so some kind of verification is desirable for users who expect processed files to _not_ blow up.
Since LuaSrcDiet has been talked about as a tool to reduce code size in projects such as WoW add-ons, `eLua` and `nspire`, adding a verification step will reduce risk for all users of LuaSrcDiet.
LuaSrcDiet performs two kinds of equivalence testing as of version 0.12.0.
The two tests can be very, very loosely termed as _source equivalence testing_ and _binary equivalence testing_.
They are controlled by the _--opt-srcequiv_ and _--opt-binequiv_ options and are enabled by default.
Testing behaviour can be summarized as follows:
* Both tests are always executed.
The options control the resulting actions taken.
* Both options are normally enabled.
This will make any failing test to throw an error.
* When an option is disabled, LuaSrcDiet will at most print a warning.
* For passing results, see the following subsections that describe what the tests actually does.
You only need to disable a testing option for experimental optimizations (see the following section for more information on this).
For anything up to and including _--maximum_, both tests should pass.
If any test fail under these conditions, then something has gone wrong with LuaSrcDiet, and I would be interested to know what has blown up.
=== _--opt-srcequiv_ Source Equivalence
The source equivalence test uses LuaSrcDiets lexer to read and compare the _before_ and _after_ lexer token streams.
Numbers and strings are dumped as binary chunks using `loadstring()` and `string.dump()` and the results compared.
If your file passes this test, it means that a Lua 5.1.x binary should see the exact same token streams for both _before_ and _after_ files.
That is, the parser in Lua will see the same lexer sequence coming from the source for both files and thus they _should_ be equivalent.
Touch wood.
Heh.
However, if you are _cross-compiling_, it may be possible for this test to fail.
Experienced Lua developers can modify `equiv.lua` to handle such cases.
=== _--opt-binequiv_ Binary Equivalence
The binary equivalence test uses `loadstring()` and `string.dump()` to generate binary chunks of the entire _before_ and _after_ files.
Also, any shbang (`#!`) lines are removed prior to generation of the binary chunks.
The binary chunks are then run through a fake `undump` routine to verify the integrity of the binary chunks and to compare all parts that ought to be identical.
On a per-function prototype basis (where _ignored_ means that any difference between the two binary chunks is ignored):
* All debug information is ignored.
* The source name is ignored.
* Any line number data is ignored.
For example, `linedefined` and `lastlinedefined`.
The rest of the two binary chunks must be identical.
So, while the two are not binary-exact, they can be loosely termed as “equivalent” and should run in exactly the same manner.
Sort of.
You get the idea.
This test may also cause problems if you are _cross-compiling_.
== Experimental Stuff
The _--opt-experimental_ option applies experimental optimizations that generally, makes changes to “real” tokens.
Such changes may or may not lead to the result failing binary chunk equivalence testing.
They would likely fail source lexer stream equivalence testing, so the _--noopt-srcequiv_ option needs to be applied so that LuaSrcDiet just gives a warning instead of an error.
For sample files, see the `samples` directory.
Currently implemented experimental optimizations are as follows:
=== Semicolon Operator Removal
The semicolon (`;`) operator is an optional operator that is used to separate statements.
The optimization turns all of these operators into single spaces, which are then run through whitespace removal.
At worst, there will be no change to file size.
* _Fails_ source lexer stream equivalence.
* _Passes_ binary chunk equivalence.
=== Function Call Syntax Sugar Optimization
This optimization turns function calls that takes a single string or long string parameter into its syntax-sugar representation, which leaves out the parentheses.
Since strings can abut anything, each instance saves 2 bytes.
For example, the following:
[source, lua]
fish("cow")fish('cow')fish([[cow]])
is turned into:
[source, lua]
fish"cow"fish'cow'fish[[cow]]
* _Fails_ source lexer stream equivalence.
* _Passes_ binary chunk equivalence.
=== Other Experimental Optimizations
There are two more of these optimizations planned, before focus is turned to the Lua 5.2.x series:
* Simple `local` keyword removal.
Planned to work for a few kinds of patterns only.
* User directed name replacement, which will need user input to modify names or identifiers used in table keys and function methods or fields.

View File

@@ -0,0 +1,128 @@
= Performance Statistics
Kein-Hong Man
2011-09-13
== Size Comparisons
The following is the result of processing `llex.lua` from LuaSrcDiet 0.11.0 using various optimization options:
|===
| LuaSrcDiet Option | Size (bytes)
| Original | 12,421
| Empty lines only | 12,395
| Whitespace only | 9,372
| Local rename only | 11,794
| _--basic_ setting | 3,835
| Program default | 3,208
| _--maximum_ setting | 3,130
|===
The programs default settings does not remove all unnecessary EOLs.
The _--basic_ setting is more conservative than the default settings, it disables optimization of strings and numbers and renaming of locals.
For version 0.12.0, the following is the result of processing `LuaSrcDiet.lua` using various optimization options:
|===
| LuaSrcDiet Option | Size (bytes)
| Original | 160,796
| _--basic_ setting | 60,219
| Program default | 43,650
| _--maximum_ setting | 42,453
| max + experimental | 42,248
|===
The above best size can go a lot lower with simple `local` keyword removal and user directed name replacement, which will be the subject of the next release of LuaSrcDiet.
== Compression and luac
File sizes of LuaSrcDiet 0.11.0 main files in various forms:
[cols="m,5*d", options="header,footer"]
|===
| Source File | Original Size (bytes) | `luac` normal (bytes) | `luac` stripped (bytes) | LuaSrcDiet _--basic_ (bytes) | LuaSrcDiet _--maximum_ (bytes)
| LuaSrcDiet.lua | 21,961 | 20,952 | 11,000 | 11,005 | 8,159
| llex.lua | 12,421 | 8,613 | 4,247 | 3,835 | 3,130
| lparser.lua | 41,757 | 27,215 | 12,506 | 11,755 | 7,666
| optlex.lua | 31,009 | 16,992 | 8,021 | 9,129 | 6,858
| optparser.lua | 16,511 | 9,021 | 3,520 | 5,087 | 2,999
| Total | 123,659 | 82,793 | 39,294 | 40,811 | 28,812
|===
* “LuaSrcDiet --maximum” has the smallest total file size.
* The ratio of “Original Size” to “LuaSrcDiet --maximum” is *4.3*.
* The ratio of “Original Size” to “luac stripped” is *3.1*.
* The ratio of “luac stripped” to “LuaSrcDiet --maximum” is *1.4*.
Compressibility of LuaSrcDiet 0.11.0 main files in various forms:
|===
| Compression Method | Original Size | `luac` normal | `luac` stripped | LuaSrcDiet _--basic_ | LuaSrcDiet _--maximum_
| Uncompressed originals | 123,659 | 82,793 | 39,294 | 40,811 | 28,812
| gzip -9 | 28,288 | 29,210 | 17,732 | 12,041 | 10,451
| bzip2 -9 | 24,407 | 27,232 | 16,856 | 11,480 | 9,815
| lzma (7-zip max) | 25,530 | 23,908 | 15,741 | 11,241 | 9,685
|===
* “LuaSrcDiet --maximum” has the smallest total file size (but a binary chunk loads faster and works with a smaller Lua executable).
* The ratio of “Original size” to “Original size + bzip2” is *5.1*.
* The ratio of “Original size” to “LuaSrcDiet --maximum + bzip2” is *12.6*.
* The ratio of “LuaSrcDiet --maximum” to “LuaSrcDiet --maximum + bzip2” is *2.9*.
* The ratio of “Original size” to “luac stripped + bzip2” is *7.3*.
* The ratio of “luac stripped” to “luac stripped + bzip2” is *2.3*.
* The ratio of “luac stripped + bzip2” to “LuaSrcDiet --maximum + bzip2” is *1.7*.
So, squeezed source code are smaller than stripped binary chunks and compresses better than stripped binary chunks, at a ratio of 2.9 for squeezed source code versus 2.3 for stripped binary chunks.
Compressed binary chunks is still a very efficient way of storing Lua scripts, because using only binary chunks allow for the parts of Lua needed to compile from sources to be omitted (`llex.o`, `lparser.o`, `lcode.o`, `ldump.o`), saving over 24KB in the process.
Note that LuaSrcDiet _does not_ answer the question of whether embedding source code is better or embedding binary chunks is better.
It is simply a utility for producing smaller source code files and an exercise in processing Lua source code using a Lua-based lexer and parser skeleton.
== Compile Speed
The following is a primitive attempt to analyze in-memory Lua script loading performance (using the `loadstring` function in Lua).
The LuaSrcDiet 0.11.0 files (original, squeezed with _--maximum_ and stripped binary chunks versions) are loaded into memory first before a loop runs to repeatedly load the script files for 10 seconds.
A null loop is also performed (processing empty strings) and the time taken per null iteration is subtracted as a form of null adjustment.
Then, various performance parameters are calculated.
Note that `LuaSrcDiet.lua` was slightly modified (`#!` line removed) to let the `loadstring` function run.
The results below were obtained with a Lua 5.1.3 executable compiled using `make generic` on Cygwin/Windows XP SP2 on a Sempron 3000+ (1.8GHz).
The LuaSrcDiet 0.11.0 source files have 11,180 “real” tokens in total.
[cols="<h,4*d", options="header"]
|===
| | Null loop | Stripped binary chunk | Original Sources | Squeezed Sources
| Total Size (bytes) | 0 | 39,294 | 123,640 | 28,793
| Iterations | 312,155 | 9,680 | 1306 | 1,592
| Duration (sec) | 10 | 10 | 10 | 10
| Time/iteration (msec) | 0.032 | 1.033 | 7.657 | 6.281
| _Time/iteration, null adjusted (msec)_ | | 1.001 | 7.625 | 6.249
| _Load rate (MiB/sec)_ | | 37.44 | 15.46 | 4.39
| Load time per byte (ns) | | 25.5 | 61.7 | 217.0
| Load time per token (ns) | | | 682 | 559
| Source time vs binary chunk time ratio | | 1.00 | 7.62 | 6.24
| Binary chunk rate vs. source rate ratio | | 1.00 | 2.42 | 8.53
|===
The above shows that stripped binary chunks is still, in many ways, the highest-performance form of fixed Lua scripts.
On a very average machine, scripts load at over 37 MiB/sec (in memory).
This is very comparable to the burst speeds of common desktop hard disks of 2008.
If instant response is paramount, stripped binary chunks has little competition.
By contrast, source code that is squeezed to the maximum using LuaSrcDiet can only muster an in-memory load rate of 4.4 MiB/sec.
The original sources load at about 15.5 MiB/sec, but most of the speed is from the lexer scanning over comments and whitespace.
A quick calculation indicates that the speed of the lexer over comments and whitespace can be as much as 65 MiB/sec, but note that the speed is all for naught.
What really matters are the real tokens, and the squeezed source code manages to load faster than the original sources by 18 %.
So, the loading of stripped binary chunks is faster than squeezed source code by a bit over 6×.
The 4.4 MiB/sec speed for squeezed source code is still quite respectable.
When an application considers the time taken to load data from the disk and perhaps the time taken to decompress, loading source code may be perfectly fine in terms of performance.
For programs that already embed source code, using LuaSrcDiet to squeeze the source code probably speeds loading up by a tiny bit in addition to making programs smaller.

View File

@@ -0,0 +1,386 @@
= Technical Notes
Kein-Hong Man
2011-09-13
== Lexer Notes
The lexer (`llex.lua`) is a version of the native 5.1.x lexer from Yueliang 0.4.0, with significant modifications.
It does have several limitations:
* The decimal point must be `.` (period).
There is no localized decimal point replacement magic.
* There is no support for nested `[[`...`]]` long strings (no `LUA_COMPAT_LSTR`).
* The lexer may not properly lex source code with characters beyond the normal ASCII character set.
Identifiers with accented characters (or any character beyond a byte value of 127) cannot be recognized.
Instead of returning one token on each call, `llex.lua` processes an entire string (all data from an entire file) and returns.
Two lists (tokens and semantic information items) are set up in the module for use by the caller.
For maximum flexibility during processing, the lexer returns non-grammar lexical elements as tokens too.
Non-grammar elements, such as comments, whitespace, line endings, are classified along with “normal” tokens.
The lexer classifies 7 kinds of grammar tokens and 4 kinds of non-grammar tokens, as follows:
[cols="m,d"]
|===
| Grammar Token | Description
| TK_KEYWORD | keywords
| TK_NAME | identifiers
| TK_NUMBER | numbers (unconverted, kept in original form)
| TK_STRING | strings (no translation is done, includes delimiters)
| TK_LSTRING | long strings (no translation is done, includes delimiters)
| TK_OP | operators and punctuation (most single-char, some double)
| TK_EOS | end-of-stream (there is only one for each file/stream)
|===
[cols="m,d"]
|===
| Whitespace Token | Description
| TK_SPACE | whitespace (generally, spaces, \t, \v and \f)
| TK_COMMENT | comments (includes delimiters, also includes special first line shbang, which is handled specially in the optimizer)
| TK_LCOMMENT | block comments (includes delimiters)
| TK_EOL | end-of-lines (excludes those embedded in strings)
|===
A list of tokens can be generated by using the _--dump-lexer_ option, like this:
[source, sh]
lua LuaSrcDiet.lua --dump-lexer llex.lua > dump_llex.dat
== Lexer Optimizations
We aim to keep lexer-based optimizations free of parser considerations, i.e. we allow for generalized optimization of token sequences.
The table below considers the requirements for all combinations of significant tokens (except `TK_EOS`).
Other tokens are whitespace-like.
Comments can be considered to be a special kind of whitespace, e.g. a short comment needs to have a following EOL token, if we do not want to optimize away short comments.
[cols="h,6*m", options="header"]
|===
| _1st  2nd Token_ | Keyword | Name | Number | String | LString | Oper
| Keyword | [S] | [S] | [S] | | |
| Name | [S] | [S] | [S] | | |
| Number | [S] | [S] | [S] | | | [1]
| String | | | | | |
| LString | | | | | |
| Oper | | | [1] | | | [2]
|===
A dash (`-`) in the above means that the first token can abut the second token.
`*[S]*`:: Need at least one whitespace, set as either a space or kept as an EOL.
`*[1]*`::
Need a space if operator is a `.`, all others okay.
A `+` or `-` is used as part of a floating-point spec, but there does not appear to be any way of creating a float by joining with number with a `+` or `-` plus another number.
Since an `e` has to be somewhere in the first token, this cant be done.
`*[2]*`::
Normally there cannot be consecutive operators, but we plan to allow for generalized optimization of token sequences, i.e. even sequences that are grammatically illegal; so disallow adjacent operators if:
* the first is in `[=<>]` and the second is `=`
* disallow dot sequences to be adjacent, but `...` first okay
* disallow `[` followed by `=` or `[` (not optimal)
Also, a minus `-` cannot preceed a Comment or LComment, because comments start with a `--` prefix.
Apart from that, all Comment or LComment tokens can be set abut with a real token.
== Local Variable Renaming
The following discusses the problem of local variable optimization, specifically _local variable renaming_ in order to reduce source code size.
=== TK_NAME Token Considerations
A `TK_NAME` token means a number of things, and some of these cannot be renamed without analyzing the source code.
We are interested in the use of `TK_NAME` in the following:
[loweralpha]
. global variable access,
. local variable declaration, including `local` statements, `local` functions, function parameters, implicit `self` locals,
. local variable access, including upvalue access.
`TK_NAME` is also used in parts of the grammar as constant strings these tokens cannot be optimized without user assistance.
These include usage as:
[loweralpha, start=4]
. keys in `key=value` pairs in table construction,
. field or method names in `a:b` or `a.b` syntax forms.
For the local variable name optimization scheme used, we do not consider (d) and (e), and while global variables cannot be renamed without some kind of user assistance, they need to be considered or tracked as part of Luas variable access scheme.
=== Lifetime of a Local Variable
Consider the following example:
[source, lua]
local string, table = string, table
In the example, the two locals are assigned the values of the globals with the same names.
When Lua encounters the declaration portion:
[source, lua]
local string, table
the parser cannot immediately make the two local variable available to following code.
In the parser and code generator, locals are inactive when entries are created.
They are activated only when the function `adjustlocalvars()` is called to activate the appropriate local variables.
NOTE: The terminology used here may not be identical to the ones used in the Dragon Book they merely follow the LuaSrcDiet code as it was written before I have read the Dragon Book.
In the example, the two local variables are activated only after the whole statement has been parsed, that is, after the last `table` token.
Hence, the statement works as expected.
Also, once the two local variables goes out of scope, `removevars()` is called to deactivate them, allowing other variables of the same name to become visible again.
Another example worth mentioning is:
[source, lua]
local a, a, a, = 1, 2, 3
The above will assign 3 to `a`.
Thus, when optimizing local variable names, (1) we need to consider accesses of global variable names affecting the namespace, (2) for the local variable names themselves, we need to consider when they are declared, activated and removed, and (3) within the “live” time of locals, we need to know when they are accessed (since locals that are never accessed dont really matter.)
=== Local Variable Tracking
Every local variable declaration is considered an object to be renamed.
From the parser, we have the original name of the local variable, the token positions for declaration, activation and removal, and the token position for all the `TK_NAME` tokens which references this local.
All instances of the implicit `self` local variable are also flagged as such.
In addition to local variable information, all global variable accesses are tabled, one object entry for one name, and each object has a corresponding list of token positions for the `TK_NAME` tokens, which is where the global variables were accessed.
The key criteria is: *Our act of renaming cannot change the visibility of any of these locals and globals at the time they are accessed*.
However, _their scope of visibility may be changed during which they are not accessed_, so someone who tries to insert a variable reference somewhere into a program that has its locals renamed may find that it now refers to a different variable.
Of course, if every variable has a unique name, then there is no need for a name allocation algorithm, as there will be no conflict.
But, in order to maximize utilization of short identifier names to reduce the final code size, we want to reuse the names as much as possible.
In addition, fewer names will likely reduce symbol entropy and may slightly improve compressibility of the source code.
LuaSrcDiet avoids the use of non-ASCII letters, so there are only 53 single-character variable names.
=== Name Allocation Theory
To understand the renaming algorithm, first we need to establish how different local and global variables can operate happily without interfering with each other.
Consider three objects, local object A, local object B and global object G.
A and B involve declaration, activation and removal, and within the period it is active, there may be zero or more accesses of the local.
For G, there are only global variable accesses to look into.
Assume that we have assigned a new name to A and we wish to consider its effects on other locals and globals, for which we choose B and G as examples.
We assume local B has not been assigned a new name as we expect our algorithm to take care of collisions.
As lifetime is something like this:
----
Decl Act Rem
+ +-------------------------------+
-------------------------------------------------
----
where “Decl” is the time of declaration, “Act” is the time of activation, and “Rem” is the time of removal.
Between “Act” and “Rem”, the local is alive or “live” and Lua can see it if its corresponding `TK_NAME` identifier comes up.
----
Decl Act Rem
+ +-------------------------------+
-------------------------------------------------
* * * *
(1) (2) (3) (4)
----
Recall that the key criteria is to not change the visibility of globals and locals during when they are accessed.
Consider local and global accesses at (1), (2), (3) and (4).
A global G of the same name as A will only collide at (3), where Lua will see A and not G.
Since G must be accessed at (3) according to what the parser says, and we cannot modify the positions of “Decl”, “Act” and “Rem”, it follows that A cannot have the same name as G.
----
Decl Act Rem
+ +-----------------------+
---------------------------------
(1)+ +---+ (2)+ +---+ (3)+ +---+ (4)+ +---+
--------- --------- --------- ---------
----
For the case of A and B having the same names and colliding, consider the cases for which B is at (1), (2), (3) or (4) in the above.
(1) and (4) means that A and B are completely isolated from each other, hence in the two cases, A and B can safely use the same variable names.
To be specific, since we have assigned A, B is considered completely isolated from A if Bs activation-to-removal period is isolated from the time of As first access to last access, meaning Bs active time will never affect any of As accesses.
For (2) and (3), we have two cases where we need to consider which one has been activated first.
For (2), B is active before A, so A cannot impose on B.
But As accesses are valid while B is active, since A can override B.
For no collision in the case of (2), we simply need to ensure that the last access of B occurs before A is activated.
For (3), B is activated before A, hence B can override As accesses.
For no collision, all of As accesses cannot happen while B is active.
Thus position (3) follows the “A is never accessed when B is active” rule in a general way.
Local variables of a child function are in the position of (3).
To illustrate, the local B can use the same name as local A and live in a child function or block scope if each time A is accessed, Lua sees A and not B.
So we have to check all accesses of A and see whether they collide with the active period of B.
If A is not accessed during that period, then B can be active with the same name.
The above appears to resolve all sorts of cases where the active times of A and B overlap.
Note that in the above, the allocator does not need to know how locals are separated according to function prototypes.
Perhaps the allocator can be simplified if knowledge of function structure is utilized.
This scheme was implemented in a hurry in 2008 — it could probably be simpler if Lua grammar is considered, but LuaSrcDiet mainly processes various index values in tables.
=== Name Allocation Algorithm
To begin with, the name generator is mostly separate from the name allocation algorithm.
The name generator returns the next shortest name for the algorithm to apply to local variables.
To attempt to reduce symbol entropy (which benefit compression algorithms), the name generator follows English frequent letter usage.
There is also an option to calculate an actual symbol entropy table from the input data.
Since there are 53 one-character identifiers and (53 * 63 - 4) two-character identifiers (minus a few keywords), there isnt a pressing need to optimally maximize name reuse.
The single-file version of LuaSrcDiet 0.12.0, at just over 3000 SLOC and 156 kiB in size, currently allocates around 55 unique local variable names.
In theory, we should need no more than 260 local identifiers by default.
Why?
Since `LUAI_MAXVARS` is 200 and `LUAI_MAXUPVALUES` is 60, at any block scope, there can be at most `(LUAI_MAXVARS + LUAI_MAXUPVALUES)` locals referenced, or 260.
Also, those from outer scopes not referenced in inner scopes can reuse identifiers.
The net effect of this is that a local variable name allocation method should not allocate more than 260 identifier names for locals.
The current algorithm is a simple first-come first-served scheme:
[loweralpha]
. One local object that use the most tokens is named first.
. Any other non-conflicting locals with respect to the first object are assigned the same name.
. Assigned locals are removed from consideration and the procedure is repeated for objects that have not been assigned new names.
. Steps (a) to (c) repeats until no local objects are left.
In addition, there are a few extra issues to take care of:
[loweralpha, start=5]
. Implicit `self` locals that have been flagged as such are already “assigned to” and so they are left unmodified.
. The name generator skips `self` to avoid conflicts.
This is not optimal but it is unlikely a script will use so many local variables as to reach `self`.
. Keywords are also skipped for the name generator.
. Global name conflict resolution.
For (h), global name conflict resolution is handled just after the new name is generated.
The name can still be used for some locals even if it conflicts with other locals.
To remove conflicts, global variable accesses for the particular identifier name is checked.
Any local variables that are active when a global access is made is marked to be skipped.
The rest of the local objects can then use that name.
The algorithm has additional code for handling locals that use the same name in the same scope.
This extends the basic algorithm that was discussed earlier.
For example:
[source, lua]
----
local foo = 10 -- <1>
...
local foo = 20 -- <2>
...
print(e)
----
Since we are considering name visibility, the first `foo` does not really cease to exist when the second `foo` is declared, because if we were to make that assumption, and the first `foo` is removed before (2), then I should be able to use `e` as the name for the first `foo` and after (2), it should not conflict with variables in the outer scope with the same name.
To illustrate:
[source, lua]
----
local e = 10 -- 'foo' renamed to 'e'
...
local t = 20 -- error if we assumed 'e' removed here
...
print(e)
----
Since `e` is a global in the example, we now have an error as the name as been taken over by a local.
Thus, the first `foo` local must have its active time extend to the end of the current scope.
If there is no conflict between the first and second `foo`, the algorithm may still assign the same names to them.
The current fix to deal with the above chains local objects in order to find the removal position.
It may be possible to handle this in a clean manner LuaSrcDiet handles it as a fix to the basic algorithm.
== Ideas
The following is a list of optimization ideas that do not require heavy-duty source code parsing and comprehension.
=== Lexer-Based Optimization Ideas
* Convert long strings to normal strings, vice versa. +
_A little desperate for a few bytes, can be done, but not real keen on implementing it._
* Special number forms to take advantage of constant number folding. +
_For example, 65536 can be represented using 2^16^, and so on.
An expression must be evaluated in the same way, otherwise this seems unsafe._
* Warn if a number has too many digits. +
_Should we warn or “test and truncate”?
Not really an optimization that will see much use._
* Warn of opportunity for using a `local` to zap a bunch of globals. +
_Current recommendation is to use the HTML plugin to display globals in red.
The developer can then visually analyze the source code and make the appropriate fixes.
I think this is better than having the program guess the intentions of the developer._
* Spaces to tabs in comments, long comments, or long strings. +
_For long strings, need to know users intention.
Would rather not implement._
=== Parser-Based Optimization Ideas
Heavy-duty optimizations will need more data to be generated by the parser.
A full AST may eventually be needed.
The most attractive idea that can be quickly implemented with a significant code size “win” is to reduce the number of `local` keywords.
* Remove unused ``local``s that can be removed in the source. +
_Need to consider unused ``local``s in multiple assignments._
* Simplify declaration of ``local``s that can be merged. +
_From:_
+
[source, lua]
----
-- separate locals
local foo
local bar
-- separate locals with assignments
local foo = 123
local bar = "pqr"
----
+
_To:_
+
[source, lua]
----
-- merged locals
local foo,bar
-- merged locals with assignments
local foo,bar=123,"pqr"
----
* Simplify declarations using `nil`. +
_From:_
[source, lua]
local foo, bar = nil, nil
+
_To:_
[source, lua]
local foo,bar
* Simplify ``return``s using `nil`. +
_How desirable is this? From Lua list discussions, it seems to be potentially unsafe unless all return locations are known and checked._
* Removal of optional semicolons in statements and removal of commas or semicolons in table constructors. +
_Yeah, this might save a few bytes._
* Remove table constructor elements using `nil`. +
_Not sure if this is safe to do._
* Simplify logical or relational operator expressions. +
_This is more suitable for an optimizing compiler project._

View File

@@ -0,0 +1,41 @@
-- vim: set ft=lua:
package = 'LuaSrcDiet'
version = '0.3.0-2'
source = { url = 'https://github.com/jirutka/luasrcdiet/archive/v0.3.0/luasrcdiet-0.3.0.tar.gz', md5 = 'c0ff36ef66cd0568c96bc54e9253a8fa' }
description = {
summary = 'Compresses Lua source code by removing unnecessary characters',
detailed = [[
This is revival of LuaSrcDiet originally written by Kein-Hong Man.]],
homepage = 'https://github.com/jirutka/luasrcdiet',
maintainer = 'Jakub Jirutka <jakub@jirutka.cz>',
license = 'MIT',
}
dependencies = {
'lua >= 5.1',
}
build = {
type = 'builtin',
modules = {
['luasrcdiet'] = 'luasrcdiet/init.lua',
['luasrcdiet.equiv'] = 'luasrcdiet/equiv.lua',
['luasrcdiet.fs'] = 'luasrcdiet/fs.lua',
['luasrcdiet.llex'] = 'luasrcdiet/llex.lua',
['luasrcdiet.lparser'] = 'luasrcdiet/lparser.lua',
['luasrcdiet.optlex'] = 'luasrcdiet/optlex.lua',
['luasrcdiet.optparser'] = 'luasrcdiet/optparser.lua',
['luasrcdiet.plugin.example'] = 'luasrcdiet/plugin/example.lua',
['luasrcdiet.plugin.html'] = 'luasrcdiet/plugin/html.lua',
['luasrcdiet.plugin.sloc'] = 'luasrcdiet/plugin/sloc.lua',
['luasrcdiet.utils'] = 'luasrcdiet/utils.lua',
},
install = {
bin = {
luasrcdiet = 'bin/luasrcdiet',
}
}
}

View File

@@ -0,0 +1,28 @@
rock_manifest = {
bin = {
luasrcdiet = "6c318685d57f827cf5baf7037a5d6072"
},
doc = {
["features-and-usage.adoc"] = "157587c27a0c340d9d1dd06af9b339b5",
["performance-stats.adoc"] = "cf5f96a86e021a3a584089fafcabd056",
["tech-notes.adoc"] = "075bc34e667a0055e659e656baa2365a"
},
lua = {
luasrcdiet = {
["equiv.lua"] = "967a6b17573d229e326dbb740ad7fe8c",
["fs.lua"] = "53db7dfc50d026b683fad68ed70ead0f",
["init.lua"] = "c6f368e6cf311f3257067fed0fbcd06a",
["llex.lua"] = "ede897af261fc362a82d87fbad91ea2b",
["lparser.lua"] = "c1e1f04d412b79a040fd1c2b74112953",
["optlex.lua"] = "7c986da991a338494c36770b4a30fa9f",
["optparser.lua"] = "b125a271ac1c691dec68b63019b1b5da",
plugin = {
["example.lua"] = "86b5c1e9dc7959db6b221d6d5a0db3d1",
["html.lua"] = "c0d3336a133f0c8663f395ee98d54f6a",
["sloc.lua"] = "fb1a91b18b701ab83f21c87733be470a"
},
["utils.lua"] = "bd6c1e85c6a9bf3383d336a4797fb292"
}
},
["luasrcdiet-0.3.0-2.rockspec"] = "da70047e1b0cbdc1ff08d060327fa110"
}

View File

@@ -0,0 +1,650 @@
commands = {
luadocumentor = {
"luadocumentor/0.1.5-1"
},
luasrcdiet = {
"luasrcdiet/0.3.0-2"
}
}
dependencies = {
luadocumentor = {
["0.1.5-1"] = {
{
constraints = {
{
op = "~>",
version = {
5, 1, string = "5.1"
}
}
},
name = "lua"
},
{
constraints = {
{
op = "~>",
version = {
1, 6, string = "1.6"
}
}
},
name = "luafilesystem"
},
{
constraints = {
{
op = "~>",
version = {
0, 32, string = "0.32"
}
}
},
name = "markdown"
},
{
constraints = {
{
op = "~>",
version = {
0, 7, string = "0.7"
}
}
},
name = "metalua-compiler"
},
{
constraints = {
{
op = "~>",
version = {
0, 9, string = "0.9"
}
}
},
name = "penlight"
}
}
},
luafilesystem = {
["1.6.3-2"] = {
{
constraints = {
{
op = ">=",
version = {
5, 1, string = "5.1"
}
}
},
name = "lua"
}
}
},
luasrcdiet = {
["0.3.0-2"] = {
{
constraints = {
{
op = ">=",
version = {
5, 1, string = "5.1"
}
}
},
name = "lua"
}
}
},
markdown = {
["0.32-2"] = {
{
constraints = {
{
op = ">=",
version = {
5, 1, string = "5.1"
}
}
},
name = "lua"
}
}
},
["metalua-compiler"] = {
["0.7.3-1"] = {
{
constraints = {
{
op = "~>",
version = {
5, 1, string = "5.1"
}
}
},
name = "lua"
},
{
constraints = {
{
op = "~>",
version = {
1, 6, string = "1.6"
}
}
},
name = "luafilesystem"
},
{
constraints = {
{
op = ">=",
version = {
0, 7, 3, string = "0.7.3"
}
}
},
name = "metalua-parser"
}
}
},
["metalua-parser"] = {
["0.7.3-2"] = {
{
constraints = {
{
op = ">=",
version = {
5, 1, string = "5.1"
}
}
},
name = "lua"
}
}
},
penlight = {
["0.9.8-1"] = {
{
constraints = {},
name = "luafilesystem"
}
}
}
}
modules = {
defaultcss = {
"luadocumentor/0.1.5-1"
},
docgenerator = {
"luadocumentor/0.1.5-1"
},
extractors = {
"luadocumentor/0.1.5-1"
},
["fs.lfs"] = {
"luadocumentor/0.1.5-1"
},
lddextractor = {
"luadocumentor/0.1.5-1"
},
lfs = {
"luafilesystem/1.6.3-2"
},
luasrcdiet = {
"luasrcdiet/0.3.0-2"
},
["luasrcdiet.equiv"] = {
"luasrcdiet/0.3.0-2"
},
["luasrcdiet.fs"] = {
"luasrcdiet/0.3.0-2"
},
["luasrcdiet.llex"] = {
"luasrcdiet/0.3.0-2"
},
["luasrcdiet.lparser"] = {
"luasrcdiet/0.3.0-2"
},
["luasrcdiet.optlex"] = {
"luasrcdiet/0.3.0-2"
},
["luasrcdiet.optparser"] = {
"luasrcdiet/0.3.0-2"
},
["luasrcdiet.plugin.example"] = {
"luasrcdiet/0.3.0-2"
},
["luasrcdiet.plugin.html"] = {
"luasrcdiet/0.3.0-2"
},
["luasrcdiet.plugin.sloc"] = {
"luasrcdiet/0.3.0-2"
},
["luasrcdiet.utils"] = {
"luasrcdiet/0.3.0-2"
},
markdown = {
"markdown/0.32-2"
},
["metalua.compiler"] = {
"metalua-parser/0.7.3-2"
},
["metalua.compiler.bytecode"] = {
"metalua-compiler/0.7.3-1"
},
["metalua.compiler.bytecode.compile"] = {
"metalua-compiler/0.7.3-1"
},
["metalua.compiler.bytecode.lcode"] = {
"metalua-compiler/0.7.3-1"
},
["metalua.compiler.bytecode.ldump"] = {
"metalua-compiler/0.7.3-1"
},
["metalua.compiler.bytecode.lopcodes"] = {
"metalua-compiler/0.7.3-1"
},
["metalua.compiler.globals"] = {
"metalua-compiler/0.7.3-1"
},
["metalua.compiler.parser"] = {
"metalua-parser/0.7.3-2"
},
["metalua.compiler.parser.annot.generator"] = {
"metalua-parser/0.7.3-2"
},
["metalua.compiler.parser.annot.grammar"] = {
"metalua-parser/0.7.3-2"
},
["metalua.compiler.parser.expr"] = {
"metalua-parser/0.7.3-2"
},
["metalua.compiler.parser.ext"] = {
"metalua-parser/0.7.3-2"
},
["metalua.compiler.parser.lexer"] = {
"metalua-parser/0.7.3-2"
},
["metalua.compiler.parser.meta"] = {
"metalua-parser/0.7.3-2"
},
["metalua.compiler.parser.misc"] = {
"metalua-parser/0.7.3-2"
},
["metalua.compiler.parser.stat"] = {
"metalua-parser/0.7.3-2"
},
["metalua.compiler.parser.table"] = {
"metalua-parser/0.7.3-2"
},
["metalua.grammar.generator"] = {
"metalua-parser/0.7.3-2"
},
["metalua.grammar.lexer"] = {
"metalua-parser/0.7.3-2"
},
["metalua.loader"] = {
"metalua-compiler/0.7.3-1"
},
["metalua.pprint"] = {
"metalua-parser/0.7.3-2"
},
["metalua/compiler/ast_to_src.mlua"] = {
"metalua-compiler/0.7.3-1"
},
["metalua/extension/comprehension.mlua"] = {
"metalua-compiler/0.7.3-1"
},
["metalua/extension/match.mlua"] = {
"metalua-compiler/0.7.3-1"
},
["metalua/repl.mlua"] = {
"metalua-compiler/0.7.3-1"
},
["metalua/treequery.mlua"] = {
"metalua-compiler/0.7.3-1"
},
["metalua/treequery/walk.mlua"] = {
"metalua-compiler/0.7.3-1"
},
["models.apimodel"] = {
"luadocumentor/0.1.5-1"
},
["models.apimodelbuilder"] = {
"luadocumentor/0.1.5-1"
},
["models.internalmodel"] = {
"luadocumentor/0.1.5-1"
},
["models.ldparser"] = {
"luadocumentor/0.1.5-1"
},
["models/internalmodelbuilder.mlua"] = {
"luadocumentor/0.1.5-1"
},
pl = {
"penlight/0.9.8-1"
},
["pl.Date"] = {
"penlight/0.9.8-1"
},
["pl.List"] = {
"penlight/0.9.8-1"
},
["pl.Map"] = {
"penlight/0.9.8-1"
},
["pl.MultiMap"] = {
"penlight/0.9.8-1"
},
["pl.OrderedMap"] = {
"penlight/0.9.8-1"
},
["pl.Set"] = {
"penlight/0.9.8-1"
},
["pl.app"] = {
"penlight/0.9.8-1"
},
["pl.array2d"] = {
"penlight/0.9.8-1"
},
["pl.class"] = {
"penlight/0.9.8-1"
},
["pl.comprehension"] = {
"penlight/0.9.8-1"
},
["pl.config"] = {
"penlight/0.9.8-1"
},
["pl.data"] = {
"penlight/0.9.8-1"
},
["pl.dir"] = {
"penlight/0.9.8-1"
},
["pl.file"] = {
"penlight/0.9.8-1"
},
["pl.func"] = {
"penlight/0.9.8-1"
},
["pl.input"] = {
"penlight/0.9.8-1"
},
["pl.lapp"] = {
"penlight/0.9.8-1"
},
["pl.lexer"] = {
"penlight/0.9.8-1"
},
["pl.luabalanced"] = {
"penlight/0.9.8-1"
},
["pl.operator"] = {
"penlight/0.9.8-1"
},
["pl.path"] = {
"penlight/0.9.8-1"
},
["pl.permute"] = {
"penlight/0.9.8-1"
},
["pl.platf.luajava"] = {
"penlight/0.9.8-1"
},
["pl.pretty"] = {
"penlight/0.9.8-1"
},
["pl.seq"] = {
"penlight/0.9.8-1"
},
["pl.sip"] = {
"penlight/0.9.8-1"
},
["pl.strict"] = {
"penlight/0.9.8-1"
},
["pl.stringio"] = {
"penlight/0.9.8-1"
},
["pl.stringx"] = {
"penlight/0.9.8-1"
},
["pl.tablex"] = {
"penlight/0.9.8-1"
},
["pl.template"] = {
"penlight/0.9.8-1"
},
["pl.test"] = {
"penlight/0.9.8-1"
},
["pl.text"] = {
"penlight/0.9.8-1"
},
["pl.utils"] = {
"penlight/0.9.8-1"
},
["pl.xml"] = {
"penlight/0.9.8-1"
},
["template.file"] = {
"luadocumentor/0.1.5-1"
},
["template.index"] = {
"luadocumentor/0.1.5-1"
},
["template.index.recordtypedef"] = {
"luadocumentor/0.1.5-1"
},
["template.item"] = {
"luadocumentor/0.1.5-1"
},
["template.page"] = {
"luadocumentor/0.1.5-1"
},
["template.recordtypedef"] = {
"luadocumentor/0.1.5-1"
},
["template.usage"] = {
"luadocumentor/0.1.5-1"
},
["template.utils"] = {
"luadocumentor/0.1.5-1"
},
templateengine = {
"luadocumentor/0.1.5-1"
}
}
repository = {
luadocumentor = {
["0.1.5-1"] = {
{
arch = "installed",
commands = {
luadocumentor = "luadocumentor"
},
dependencies = {
luafilesystem = "1.6.3-2",
markdown = "0.32-2",
["metalua-compiler"] = "0.7.3-1",
["metalua-parser"] = "0.7.3-2",
penlight = "0.9.8-1"
},
modules = {
defaultcss = "defaultcss.lua",
docgenerator = "docgenerator.lua",
extractors = "extractors.lua",
["fs.lfs"] = "fs/lfs.lua",
lddextractor = "lddextractor.lua",
["models.apimodel"] = "models/apimodel.lua",
["models.apimodelbuilder"] = "models/apimodelbuilder.lua",
["models.internalmodel"] = "models/internalmodel.lua",
["models.ldparser"] = "models/ldparser.lua",
["models/internalmodelbuilder.mlua"] = "models/internalmodelbuilder.mlua",
["template.file"] = "template/file.lua",
["template.index"] = "template/index.lua",
["template.index.recordtypedef"] = "template/index/recordtypedef.lua",
["template.item"] = "template/item.lua",
["template.page"] = "template/page.lua",
["template.recordtypedef"] = "template/recordtypedef.lua",
["template.usage"] = "template/usage.lua",
["template.utils"] = "template/utils.lua",
templateengine = "templateengine.lua"
}
}
}
},
luafilesystem = {
["1.6.3-2"] = {
{
arch = "installed",
commands = {},
dependencies = {},
modules = {
lfs = "lfs.dll"
}
}
}
},
luasrcdiet = {
["0.3.0-2"] = {
{
arch = "installed",
commands = {
luasrcdiet = "luasrcdiet"
},
dependencies = {},
modules = {
luasrcdiet = "luasrcdiet/init.lua",
["luasrcdiet.equiv"] = "luasrcdiet/equiv.lua",
["luasrcdiet.fs"] = "luasrcdiet/fs.lua",
["luasrcdiet.llex"] = "luasrcdiet/llex.lua",
["luasrcdiet.lparser"] = "luasrcdiet/lparser.lua",
["luasrcdiet.optlex"] = "luasrcdiet/optlex.lua",
["luasrcdiet.optparser"] = "luasrcdiet/optparser.lua",
["luasrcdiet.plugin.example"] = "luasrcdiet/plugin/example.lua",
["luasrcdiet.plugin.html"] = "luasrcdiet/plugin/html.lua",
["luasrcdiet.plugin.sloc"] = "luasrcdiet/plugin/sloc.lua",
["luasrcdiet.utils"] = "luasrcdiet/utils.lua"
}
}
}
},
markdown = {
["0.32-2"] = {
{
arch = "installed",
commands = {},
dependencies = {},
modules = {
markdown = "markdown.lua"
}
}
}
},
["metalua-compiler"] = {
["0.7.3-1"] = {
{
arch = "installed",
commands = {},
dependencies = {
luafilesystem = "1.6.3-2",
["metalua-parser"] = "0.7.3-2"
},
modules = {
["metalua.compiler.bytecode"] = "metalua/compiler/bytecode.lua",
["metalua.compiler.bytecode.compile"] = "metalua/compiler/bytecode/compile.lua",
["metalua.compiler.bytecode.lcode"] = "metalua/compiler/bytecode/lcode.lua",
["metalua.compiler.bytecode.ldump"] = "metalua/compiler/bytecode/ldump.lua",
["metalua.compiler.bytecode.lopcodes"] = "metalua/compiler/bytecode/lopcodes.lua",
["metalua.compiler.globals"] = "metalua/compiler/globals.lua",
["metalua.loader"] = "metalua/loader.lua",
["metalua/compiler/ast_to_src.mlua"] = "metalua/compiler/ast_to_src.mlua",
["metalua/extension/comprehension.mlua"] = "metalua/extension/comprehension.mlua",
["metalua/extension/match.mlua"] = "metalua/extension/match.mlua",
["metalua/repl.mlua"] = "metalua/repl.mlua",
["metalua/treequery.mlua"] = "metalua/treequery.mlua",
["metalua/treequery/walk.mlua"] = "metalua/treequery/walk.mlua"
}
}
}
},
["metalua-parser"] = {
["0.7.3-2"] = {
{
arch = "installed",
commands = {},
dependencies = {},
modules = {
["metalua.compiler"] = "metalua/compiler.lua",
["metalua.compiler.parser"] = "metalua/compiler/parser.lua",
["metalua.compiler.parser.annot.generator"] = "metalua/compiler/parser/annot/generator.lua",
["metalua.compiler.parser.annot.grammar"] = "metalua/compiler/parser/annot/grammar.lua",
["metalua.compiler.parser.expr"] = "metalua/compiler/parser/expr.lua",
["metalua.compiler.parser.ext"] = "metalua/compiler/parser/ext.lua",
["metalua.compiler.parser.lexer"] = "metalua/compiler/parser/lexer.lua",
["metalua.compiler.parser.meta"] = "metalua/compiler/parser/meta.lua",
["metalua.compiler.parser.misc"] = "metalua/compiler/parser/misc.lua",
["metalua.compiler.parser.stat"] = "metalua/compiler/parser/stat.lua",
["metalua.compiler.parser.table"] = "metalua/compiler/parser/table.lua",
["metalua.grammar.generator"] = "metalua/grammar/generator.lua",
["metalua.grammar.lexer"] = "metalua/grammar/lexer.lua",
["metalua.pprint"] = "metalua/pprint.lua"
}
}
}
},
penlight = {
["0.9.8-1"] = {
{
arch = "installed",
commands = {},
dependencies = {
luafilesystem = "1.6.3-2"
},
modules = {
pl = "pl/init.lua",
["pl.Date"] = "pl/Date.lua",
["pl.List"] = "pl/List.lua",
["pl.Map"] = "pl/Map.lua",
["pl.MultiMap"] = "pl/MultiMap.lua",
["pl.OrderedMap"] = "pl/OrderedMap.lua",
["pl.Set"] = "pl/Set.lua",
["pl.app"] = "pl/app.lua",
["pl.array2d"] = "pl/array2d.lua",
["pl.class"] = "pl/class.lua",
["pl.comprehension"] = "pl/comprehension.lua",
["pl.config"] = "pl/config.lua",
["pl.data"] = "pl/data.lua",
["pl.dir"] = "pl/dir.lua",
["pl.file"] = "pl/file.lua",
["pl.func"] = "pl/func.lua",
["pl.input"] = "pl/input.lua",
["pl.lapp"] = "pl/lapp.lua",
["pl.lexer"] = "pl/lexer.lua",
["pl.luabalanced"] = "pl/luabalanced.lua",
["pl.operator"] = "pl/operator.lua",
["pl.path"] = "pl/path.lua",
["pl.permute"] = "pl/permute.lua",
["pl.platf.luajava"] = "pl/platf/luajava.lua",
["pl.pretty"] = "pl/pretty.lua",
["pl.seq"] = "pl/seq.lua",
["pl.sip"] = "pl/sip.lua",
["pl.strict"] = "pl/strict.lua",
["pl.stringio"] = "pl/stringio.lua",
["pl.stringx"] = "pl/stringx.lua",
["pl.tablex"] = "pl/tablex.lua",
["pl.template"] = "pl/template.lua",
["pl.test"] = "pl/test.lua",
["pl.text"] = "pl/text.lua",
["pl.utils"] = "pl/utils.lua",
["pl.xml"] = "pl/xml.lua"
}
}
}
}
}

View File

@@ -0,0 +1,23 @@
package = "Markdown"
version = "0.32-2"
source = {
url = "http://www.frykholm.se/files/markdown-0.32.tar.gz",
dir = "."
}
description = {
summary = "Markdown text-to-html markup system.",
detailed = [[
A pure-lua implementation of the Markdown text-to-html markup system.
]],
license = "MIT",
homepage = "http://www.frykholm.se/files/markdown.lua"
}
dependencies = {
"lua >= 5.1",
}
build = {
type = "none",
install = {
lua = { "markdown.lua" },
}
}

View File

@@ -0,0 +1,6 @@
rock_manifest = {
lua = {
["markdown.lua"] = "0ea5f9d6d22a6c9aa4fdf63cf1d7d066"
},
["markdown-0.32-2.rockspec"] = "83f0335058d8fbd078d4f2c1ce941df0"
}

View File

@@ -0,0 +1,104 @@
Metalua Compiler
================
## Metalua compiler
This module `metalua-compiler` depends on `metalua-parser`. Its main
feature is to compile ASTs into Lua 5.1 bytecode, allowing to convert
them into bytecode files and executable functions. This opens the
following possibilities:
* compiler objects generated with `require 'metalua.compiler'.new()`
support methods `:xxx_to_function()` and `:xxx_to_bytecode()`;
* Compile-time meta-programming: use of `-{...}` splices in source
code, to generate code during compilation;
* Some syntax extensions, such as structural pattern matching and
lists by comprehension;
* Some AST manipulation facilities such as `treequery`, which are
implemented with Metalua syntax extensions.
## What's new in Metalua 0.7
This is a major overhaul of the compiler's architecture. Some of the
most noteworthy changes are:
* No more installation or bootstrap script. Some Metalua source files
have been rewritten in plain Lua, and module sources have been
refactored, so that if you just drop the `metalua` folder somewhere
in your `LUA_PATH`, it works.
* The compiler can be cut in two parts:
* a parser which generates ASTs out of Lua sources, and should be
either portable or easily ported to Lua 5.2;
* a compiler, which can turn sources and AST into executable
Lua 5.1 bytecode and run it. It also supports compile-time
meta-programming, i.e. code included between `-{ ... }` is
executed during compilation, and the ASTs it produces are
included in the resulting bytecode.
* Both parts are packaged as separate LuaRocks, `metalua-parser` and
`metalua-compiler` respectively, so that you can install the former
without the latter.
* The parser is not a unique object anymore. Instead,
`require "metalua.compiler".new()` returns a different compiler
instance every time it's called. Compiler instances can be reused on
as many source files as wanted, but extending one instance's grammar
doesn't affect other compiler instances.
* Included standard library has been shed. There are too many standard
libs in Lua, and none of them is standard enough, offering
yet-another-one, coupled with a specific compiler can only add to
confusion.
* Many syntax extensions, which either were arguably more code samples
than actual production-ready tools, or relied too heavily on the
removed runtime standard libraries, have been removed.
* The remaining libraries and samples are:
* `metalua.compiler` converts sources into ASTs, bytecode,
functions, and ASTs back into sources.
* `metalua` compiles and/or executes files from the command line,
can start an interactive REPL session.
* `metalua.loader` adds a package loader which allows to use modules
written in Metalua, even from a plain Lua program.
* `metalua.treequery` is an advanced DSL allowing to search ASTs in
a smart way, e.g. "_search `return` statements which return a
`local` variable but aren't in a nested `function`_".
* `metalua.extension.comprehension` is a language extension which
supports lists by comprehension
(`even = { i for i=1, 100 if i%2==0 }`) and improved loops
(`for i=1, 10 for j=1,10 if i~=j do print(i,j) end`).
* `metalua.extension.match` is a language extension which offers
Haskell/ML structural pattern matching
(``match AST with `Function{ args, body } -> ... | `Number{ 0 } -> ...end``)
* **TODO Move basic extensions in a separate module.**
* To remove the compilation speed penalty associated with
metaprogramming, when environment variable `LUA_MCACHE` or Lua
variable `package.mcache` is defined and LuaFileSystem is available,
the results of Metalua source compilations is cached. Unless the
source file is more recent than the latest cached bytecode file, the
latter is loaded instead of the former.
* The Luarock install for the full compiler lists dependencies towards
Readline, LuaFileSytem, and Alt-Getopts. Those projects are
optional, but having them automatically installed by LuaRocks offers
a better user experience.
* The license has changed from MIT to double license MIT + EPL. This
has been done in order to provide the IP guarantees expected by the
Eclipse Foundation, to include Metalua in Eclipse's
[Lua Development Tools](http://www.eclipse.org/koneki/ldt/).

View File

@@ -0,0 +1,177 @@
Metalua Parser
==============
`metalua-parser` is a subset of the Metalua compiler, which turns
valid Lua source files and strings into abstract syntax trees
(AST). This README includes a description of this AST format. People
interested by Lua code analysis and generation are encouraged to
produce and/or consume this format to represent ASTs.
It has been designed for Lua 5.1. It hasn't been tested against
Lua 5.2, but should be easily ported.
## Usage
Module `metalua.compiler` has a `new()` function, which returns a
compiler instance. This instance has a set of methods of the form
`:xxx_to_yyy(input)`, where `xxx` and `yyy` must be one of the
following:
* `srcfile` the name of a Lua source file;
* `src` a string containing the Lua sources of a list of statements;
* `lexstream` a lexical tokens stream;
* `ast` an abstract syntax tree;
* `bytecode` a chunk of Lua bytecode that can be loaded in a Lua 5.1
VM (not available if you only installed the parser);
* `function` an executable Lua function.
Compiling into bytecode or executable functions requires the whole
Metalua compiler, not only the parser. The most frequently used
functions are `:src_to_ast(source_string)` and
`:srcfile_to_ast("path/to/source/file.lua")`.
mlc = require 'metalua.compiler'.new()
ast = mlc :src_to_ast[[ return 123 ]]
A compiler instance can be reused as much as you want; it's only
interesting to work with more than one compiler instance when you
start extending their grammars.
## Abstract Syntax Trees definition
### Notation
Trees are written below with some Metalua syntax sugar, which
increases their readability. the backquote symbol introduces a `tag`,
i.e. a string stored in the `"tag"` field of a table:
* `` `Foo{ 1, 2, 3 }`` is a shortcut for `{tag="Foo", 1, 2, 3}`;
* `` `Foo`` is a shortcut for `{tag="Foo"}`;
* `` `Foo 123`` is a shortcut for `` `Foo{ 123 }``, and therefore
`{tag="Foo", 123 }`; the expression after the tag must be a literal
number or string.
When using a Metalua interpreter or compiler, the backtick syntax is
supported and can be used directly. Metalua's pretty-printing helpers
also try to use backtick syntax whenever applicable.
### Tree elements
Tree elements are mainly categorized into statements `stat`,
expressions `expr` and lists of statements `block`. Auxiliary
definitions include function applications/method invocation `apply`,
are both valid statements and expressions, expressions admissible on
the left-hand-side of an assignment statement `lhs`.
block: { stat* }
stat:
`Do{ stat* }
| `Set{ {lhs+} {expr+} } -- lhs1, lhs2... = e1, e2...
| `While{ expr block } -- while e do b end
| `Repeat{ block expr } -- repeat b until e
| `If{ (expr block)+ block? } -- if e1 then b1 [elseif e2 then b2] ... [else bn] end
| `Fornum{ ident expr expr expr? block } -- for ident = e, e[, e] do b end
| `Forin{ {ident+} {expr+} block } -- for i1, i2... in e1, e2... do b end
| `Local{ {ident+} {expr+}? } -- local i1, i2... = e1, e2...
| `Localrec{ ident expr } -- only used for 'local function'
| `Goto{ <string> } -- goto str
| `Label{ <string> } -- ::str::
| `Return{ <expr*> } -- return e1, e2...
| `Break -- break
| apply
expr:
`Nil | `Dots | `True | `False
| `Number{ <number> }
| `String{ <string> }
| `Function{ { ident* `Dots? } block }
| `Table{ ( `Pair{ expr expr } | expr )* }
| `Op{ opid expr expr? }
| `Paren{ expr } -- significant to cut multiple values returns
| apply
| lhs
apply:
`Call{ expr expr* }
| `Invoke{ expr `String{ <string> } expr* }
ident: `Id{ <string> }
lhs: ident | `Index{ expr expr }
opid: 'add' | 'sub' | 'mul' | 'div'
| 'mod' | 'pow' | 'concat'| 'eq'
| 'lt' | 'le' | 'and' | 'or'
| 'not' | 'len'
### Meta-data (lineinfo)
ASTs also embed some metadata, allowing to map them to their source
representation. Those informations are stored in a `"lineinfo"` field
in each tree node, which points to the range of characters in the
source string which represents it, and to the content of any comment
that would appear immediately before or after that node.
Lineinfo objects have two fields, `"first"` and `"last"`, describing
respectively the beginning and the end of the subtree in the
sources. For instance, the sub-node ``Number{123}` produced by parsing
`[[return 123]]` will have `lineinfo.first` describing offset 8, and
`lineinfo.last` describing offset 10:
> mlc = require 'metalua.compiler'.new()
> ast = mlc :src_to_ast "return 123 -- comment"
> print(ast[1][1].lineinfo)
<?|L1|C8-10|K8-10|C>
>
A lineinfo keeps track of character offsets relative to the beginning
of the source string/file ("K8-10" above), line numbers (L1 above; a
lineinfo spanning on several lines would read something like "L1-10"),
columns i.e. offset within the line ("C8-10" above), and a filename if
available (the "?" mark above indicating that we have no file name, as
the AST comes from a string). The final "|C>" indicates that there's a
comment immediately after the node; an initial "<C|" would have meant
that there was a comment immediately before the node.
Positions represent either the end of a token and the beginning of an
inter-token space (`"last"` fields) or the beginning of a token, and
the end of an inter-token space (`"first"` fields). Inter-token spaces
might be empty. They can also contain comments, which might be useful
to link with surrounding tokens and AST subtrees.
Positions are chained with their "dual" one: a position at the
beginning of and inter-token space keeps a refernce to the position at
the end of that inter-token space in its `"facing"` field, and
conversly, end-of-inter-token positions keep track of the inter-token
space beginning, also in `"facing"`. An inter-token space can be
empty, e.g. in `"2+2"`, in which case `lineinfo==lineinfo.facing`.
Comments are also kept in the `"comments"` field. If present, this
field contains a list of comments, with a `"lineinfo"` field
describing the span between the first and last comment. Each comment
is represented by a list of one string, with a `"lineinfo"` describing
the span of this comment only. Consecutive lines of `--` comments are
considered as one comment: `"-- foo\n-- bar\n"` parses as one comment
whose text is `"foo\nbar"`, whereas `"-- foo\n\n-- bar\n"` parses as
two comments `"foo"` and `"bar"`.
So for instance, if `f` is the AST of a function and I want to
retrieve the comment before the function, I'd do:
f_comment = f.lineinfo.first.comments[1][1]
The informations in lineinfo positions, i.e. in each `"first"` and
`"last"` field, are held in the following fields:
* `"source"` the filename (optional);
* `"offset"` the 1-based offset relative to the beginning of the string/file;
* `"line"` the 1-based line number;
* `"column"` the 1-based offset within the line;
* `"facing"` the position at the opposite end of the inter-token space.
* `"comments"` the comments in the associated inter-token space (optional).
* `"id"` an arbitrary number, which uniquely identifies an inter-token
space within a given tokens stream.

View File

@@ -0,0 +1,13 @@
Metalua
=======
Metalua is a Lua code analysis tool, as well as a compiler for a
superset of Lua 5.1 supporting Compile-Time Meta-Programming. It's
separated into two LuaRocks, `metalua-parser` and
`metalua-compiler`. The documentation of each rock can be found in
`README-parser.md` and `README-compiler.md`.
All the code in Metalue is released under dual lincenses:
* MIT public license (same as Lua);
* EPL public license (same as Eclipse).

View File

@@ -0,0 +1,47 @@
--*-lua-*--
package = "metalua-compiler"
version = "0.7.3-1"
source = {
url = "http://git.eclipse.org/c/koneki/org.eclipse.koneki.metalua.git/snapshot/org.eclipse.koneki.metalua-v0.7.3.tar.gz"
}
description = {
summary = "Metalua's compiler: converting (Meta)lua source strings and files into executable Lua 5.1 bytecode",
detailed = [[
This is the Metalua copmiler, packaged as a rock, depending
on the spearate metalua-parser AST generating library. It
compiles a superset of Lua 5.1 into bytecode, which can
then be loaded and executed by a Lua 5.1 VM. It also allows
to dump ASTs back into Lua source files.
]],
homepage = "http://git.eclipse.org/c/koneki/org.eclipse.koneki.metalua.git",
license = "EPL + MIT"
}
dependencies = {
"lua ~> 5.1", -- Lua 5.2 bytecode not supported
"luafilesystem ~> 1.6", -- Cached compilation based on file timestamps
"metalua-parser >= 0.7.3", -- AST production
}
build = {
type="builtin",
modules={
["metalua.compiler.bytecode"] = "metalua/compiler/bytecode.lua",
["metalua.compiler.globals"] = "metalua/compiler/globals.lua",
["metalua.compiler.bytecode.compile"] = "metalua/compiler/bytecode/compile.lua",
["metalua.compiler.bytecode.lcode"] = "metalua/compiler/bytecode/lcode.lua",
["metalua.compiler.bytecode.lopcodes"] = "metalua/compiler/bytecode/lopcodes.lua",
["metalua.compiler.bytecode.ldump"] = "metalua/compiler/bytecode/ldump.lua",
["metalua.loader"] = "metalua/loader.lua",
},
install={
lua={
["metalua.treequery"] = "metalua/treequery.mlua",
["metalua.compiler.ast_to_src"] = "metalua/compiler/ast_to_src.mlua",
["metalua.treequery.walk"] = "metalua/treequery/walk.mlua",
["metalua.extension.match"] = "metalua/extension/match.mlua",
["metalua.extension.comprehension"] = "metalua/extension/comprehension.mlua",
["metalua.repl"] = "metalua/repl.mlua",
}
}
}

View File

@@ -0,0 +1,33 @@
rock_manifest = {
doc = {
["README-compiler.md"] = "292523d759247d210d32fb2f6153e0f4",
["README-parser.md"] = "b44e3673d96dd296f2c0e92a6c87ee18",
["README.md"] = "20bfb490cddef9e101e44688791abcda"
},
lua = {
metalua = {
compiler = {
["ast_to_src.mlua"] = "1309f76df37585ef8e1f67f748b07b22",
bytecode = {
["compile.lua"] = "430e4a6fac8b64b5ebb3ae585ebae75a",
["lcode.lua"] = "3ad8755ebe8ea8eca6b1d2846eec92c4",
["ldump.lua"] = "295e1d9657fb0126ce3471b3366da694",
["lopcodes.lua"] = "a0f15cfc93b026b0a868466d066f1d21"
},
["bytecode.lua"] = "1032e5233455fd4e504daf5d2893527b",
["globals.lua"] = "80ae19c6e640de0746348c91633c4c55"
},
extension = {
["comprehension.mlua"] = "426f5856896bda4c3763bd5f61410685",
["match.mlua"] = "79960265331e8b2f46199c2411a103de"
},
["loader.lua"] = "1cdbf6cdf6ca97c55540d068474f1d8a",
["repl.mlua"] = "729456f3a8cc073788acee564a0495f0",
treequery = {
["walk.mlua"] = "5159aaddbec55936f91ea4236f6451d3"
},
["treequery.mlua"] = "97ffcee0825ac3bc776d01566767b2e8"
}
},
["metalua-compiler-0.7.3-1.rockspec"] = "b3883b25641d862db6828300bb755d51"
}

View File

@@ -0,0 +1,104 @@
Metalua Compiler
================
## Metalua compiler
This module `metalua-compiler` depends on `metalua-parser`. Its main
feature is to compile ASTs into Lua 5.1 bytecode, allowing to convert
them into bytecode files and executable functions. This opens the
following possibilities:
* compiler objects generated with `require 'metalua.compiler'.new()`
support methods `:xxx_to_function()` and `:xxx_to_bytecode()`;
* Compile-time meta-programming: use of `-{...}` splices in source
code, to generate code during compilation;
* Some syntax extensions, such as structural pattern matching and
lists by comprehension;
* Some AST manipulation facilities such as `treequery`, which are
implemented with Metalua syntax extensions.
## What's new in Metalua 0.7
This is a major overhaul of the compiler's architecture. Some of the
most noteworthy changes are:
* No more installation or bootstrap script. Some Metalua source files
have been rewritten in plain Lua, and module sources have been
refactored, so that if you just drop the `metalua` folder somewhere
in your `LUA_PATH`, it works.
* The compiler can be cut in two parts:
* a parser which generates ASTs out of Lua sources, and should be
either portable or easily ported to Lua 5.2;
* a compiler, which can turn sources and AST into executable
Lua 5.1 bytecode and run it. It also supports compile-time
meta-programming, i.e. code included between `-{ ... }` is
executed during compilation, and the ASTs it produces are
included in the resulting bytecode.
* Both parts are packaged as separate LuaRocks, `metalua-parser` and
`metalua-compiler` respectively, so that you can install the former
without the latter.
* The parser is not a unique object anymore. Instead,
`require "metalua.compiler".new()` returns a different compiler
instance every time it's called. Compiler instances can be reused on
as many source files as wanted, but extending one instance's grammar
doesn't affect other compiler instances.
* Included standard library has been shed. There are too many standard
libs in Lua, and none of them is standard enough, offering
yet-another-one, coupled with a specific compiler can only add to
confusion.
* Many syntax extensions, which either were arguably more code samples
than actual production-ready tools, or relied too heavily on the
removed runtime standard libraries, have been removed.
* The remaining libraries and samples are:
* `metalua.compiler` converts sources into ASTs, bytecode,
functions, and ASTs back into sources.
* `metalua` compiles and/or executes files from the command line,
can start an interactive REPL session.
* `metalua.loader` adds a package loader which allows to use modules
written in Metalua, even from a plain Lua program.
* `metalua.treequery` is an advanced DSL allowing to search ASTs in
a smart way, e.g. "_search `return` statements which return a
`local` variable but aren't in a nested `function`_".
* `metalua.extension.comprehension` is a language extension which
supports lists by comprehension
(`even = { i for i=1, 100 if i%2==0 }`) and improved loops
(`for i=1, 10 for j=1,10 if i~=j do print(i,j) end`).
* `metalua.extension.match` is a language extension which offers
Haskell/ML structural pattern matching
(``match AST with `Function{ args, body } -> ... | `Number{ 0 } -> ...end``)
* **TODO Move basic extensions in a separate module.**
* To remove the compilation speed penalty associated with
metaprogramming, when environment variable `LUA_MCACHE` or Lua
variable `package.mcache` is defined and LuaFileSystem is available,
the results of Metalua source compilations is cached. Unless the
source file is more recent than the latest cached bytecode file, the
latter is loaded instead of the former.
* The Luarock install for the full compiler lists dependencies towards
Readline, LuaFileSytem, and Alt-Getopts. Those projects are
optional, but having them automatically installed by LuaRocks offers
a better user experience.
* The license has changed from MIT to double license MIT + EPL. This
has been done in order to provide the IP guarantees expected by the
Eclipse Foundation, to include Metalua in Eclipse's
[Lua Development Tools](http://www.eclipse.org/koneki/ldt/).

View File

@@ -0,0 +1,177 @@
Metalua Parser
==============
`metalua-parser` is a subset of the Metalua compiler, which turns
valid Lua source files and strings into abstract syntax trees
(AST). This README includes a description of this AST format. People
interested by Lua code analysis and generation are encouraged to
produce and/or consume this format to represent ASTs.
It has been designed for Lua 5.1. It hasn't been tested against
Lua 5.2, but should be easily ported.
## Usage
Module `metalua.compiler` has a `new()` function, which returns a
compiler instance. This instance has a set of methods of the form
`:xxx_to_yyy(input)`, where `xxx` and `yyy` must be one of the
following:
* `srcfile` the name of a Lua source file;
* `src` a string containing the Lua sources of a list of statements;
* `lexstream` a lexical tokens stream;
* `ast` an abstract syntax tree;
* `bytecode` a chunk of Lua bytecode that can be loaded in a Lua 5.1
VM (not available if you only installed the parser);
* `function` an executable Lua function.
Compiling into bytecode or executable functions requires the whole
Metalua compiler, not only the parser. The most frequently used
functions are `:src_to_ast(source_string)` and
`:srcfile_to_ast("path/to/source/file.lua")`.
mlc = require 'metalua.compiler'.new()
ast = mlc :src_to_ast[[ return 123 ]]
A compiler instance can be reused as much as you want; it's only
interesting to work with more than one compiler instance when you
start extending their grammars.
## Abstract Syntax Trees definition
### Notation
Trees are written below with some Metalua syntax sugar, which
increases their readability. the backquote symbol introduces a `tag`,
i.e. a string stored in the `"tag"` field of a table:
* `` `Foo{ 1, 2, 3 }`` is a shortcut for `{tag="Foo", 1, 2, 3}`;
* `` `Foo`` is a shortcut for `{tag="Foo"}`;
* `` `Foo 123`` is a shortcut for `` `Foo{ 123 }``, and therefore
`{tag="Foo", 123 }`; the expression after the tag must be a literal
number or string.
When using a Metalua interpreter or compiler, the backtick syntax is
supported and can be used directly. Metalua's pretty-printing helpers
also try to use backtick syntax whenever applicable.
### Tree elements
Tree elements are mainly categorized into statements `stat`,
expressions `expr` and lists of statements `block`. Auxiliary
definitions include function applications/method invocation `apply`,
are both valid statements and expressions, expressions admissible on
the left-hand-side of an assignment statement `lhs`.
block: { stat* }
stat:
`Do{ stat* }
| `Set{ {lhs+} {expr+} } -- lhs1, lhs2... = e1, e2...
| `While{ expr block } -- while e do b end
| `Repeat{ block expr } -- repeat b until e
| `If{ (expr block)+ block? } -- if e1 then b1 [elseif e2 then b2] ... [else bn] end
| `Fornum{ ident expr expr expr? block } -- for ident = e, e[, e] do b end
| `Forin{ {ident+} {expr+} block } -- for i1, i2... in e1, e2... do b end
| `Local{ {ident+} {expr+}? } -- local i1, i2... = e1, e2...
| `Localrec{ ident expr } -- only used for 'local function'
| `Goto{ <string> } -- goto str
| `Label{ <string> } -- ::str::
| `Return{ <expr*> } -- return e1, e2...
| `Break -- break
| apply
expr:
`Nil | `Dots | `True | `False
| `Number{ <number> }
| `String{ <string> }
| `Function{ { ident* `Dots? } block }
| `Table{ ( `Pair{ expr expr } | expr )* }
| `Op{ opid expr expr? }
| `Paren{ expr } -- significant to cut multiple values returns
| apply
| lhs
apply:
`Call{ expr expr* }
| `Invoke{ expr `String{ <string> } expr* }
ident: `Id{ <string> }
lhs: ident | `Index{ expr expr }
opid: 'add' | 'sub' | 'mul' | 'div'
| 'mod' | 'pow' | 'concat'| 'eq'
| 'lt' | 'le' | 'and' | 'or'
| 'not' | 'len'
### Meta-data (lineinfo)
ASTs also embed some metadata, allowing to map them to their source
representation. Those informations are stored in a `"lineinfo"` field
in each tree node, which points to the range of characters in the
source string which represents it, and to the content of any comment
that would appear immediately before or after that node.
Lineinfo objects have two fields, `"first"` and `"last"`, describing
respectively the beginning and the end of the subtree in the
sources. For instance, the sub-node ``Number{123}` produced by parsing
`[[return 123]]` will have `lineinfo.first` describing offset 8, and
`lineinfo.last` describing offset 10:
> mlc = require 'metalua.compiler'.new()
> ast = mlc :src_to_ast "return 123 -- comment"
> print(ast[1][1].lineinfo)
<?|L1|C8-10|K8-10|C>
>
A lineinfo keeps track of character offsets relative to the beginning
of the source string/file ("K8-10" above), line numbers (L1 above; a
lineinfo spanning on several lines would read something like "L1-10"),
columns i.e. offset within the line ("C8-10" above), and a filename if
available (the "?" mark above indicating that we have no file name, as
the AST comes from a string). The final "|C>" indicates that there's a
comment immediately after the node; an initial "<C|" would have meant
that there was a comment immediately before the node.
Positions represent either the end of a token and the beginning of an
inter-token space (`"last"` fields) or the beginning of a token, and
the end of an inter-token space (`"first"` fields). Inter-token spaces
might be empty. They can also contain comments, which might be useful
to link with surrounding tokens and AST subtrees.
Positions are chained with their "dual" one: a position at the
beginning of and inter-token space keeps a refernce to the position at
the end of that inter-token space in its `"facing"` field, and
conversly, end-of-inter-token positions keep track of the inter-token
space beginning, also in `"facing"`. An inter-token space can be
empty, e.g. in `"2+2"`, in which case `lineinfo==lineinfo.facing`.
Comments are also kept in the `"comments"` field. If present, this
field contains a list of comments, with a `"lineinfo"` field
describing the span between the first and last comment. Each comment
is represented by a list of one string, with a `"lineinfo"` describing
the span of this comment only. Consecutive lines of `--` comments are
considered as one comment: `"-- foo\n-- bar\n"` parses as one comment
whose text is `"foo\nbar"`, whereas `"-- foo\n\n-- bar\n"` parses as
two comments `"foo"` and `"bar"`.
So for instance, if `f` is the AST of a function and I want to
retrieve the comment before the function, I'd do:
f_comment = f.lineinfo.first.comments[1][1]
The informations in lineinfo positions, i.e. in each `"first"` and
`"last"` field, are held in the following fields:
* `"source"` the filename (optional);
* `"offset"` the 1-based offset relative to the beginning of the string/file;
* `"line"` the 1-based line number;
* `"column"` the 1-based offset within the line;
* `"facing"` the position at the opposite end of the inter-token space.
* `"comments"` the comments in the associated inter-token space (optional).
* `"id"` an arbitrary number, which uniquely identifies an inter-token
space within a given tokens stream.

View File

@@ -0,0 +1,13 @@
Metalua
=======
Metalua is a Lua code analysis tool, as well as a compiler for a
superset of Lua 5.1 supporting Compile-Time Meta-Programming. It's
separated into two LuaRocks, `metalua-parser` and
`metalua-compiler`. The documentation of each rock can be found in
`README-parser.md` and `README-compiler.md`.
All the code in Metalue is released under dual lincenses:
* MIT public license (same as Lua);
* EPL public license (same as Eclipse).

View File

@@ -0,0 +1,38 @@
--*-lua-*--
package = "metalua-parser"
version = "0.7.3-2"
source = {
url = "http://git.eclipse.org/c/koneki/org.eclipse.koneki.metalua.git/snapshot/org.eclipse.koneki.metalua-v0.7.3.tar.gz"
}
description = {
summary = "Metalua's parser: converting Lua source strings and files into AST",
detailed = [[
This is a subset of the full Metalua compiler. It defines and generates an AST
format for Lua programs, which offers a nice level of abstraction to reason about
and manipulate Lua programs.
]],
homepage = "http://git.eclipse.org/c/koneki/org.eclipse.koneki.metalua.git",
license = "EPL + MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type="builtin",
modules={
["metalua.grammar.generator"] = "metalua/grammar/generator.lua",
["metalua.grammar.lexer"] = "metalua/grammar/lexer.lua",
["metalua.compiler.parser"] = "metalua/compiler/parser.lua",
["metalua.compiler.parser.table"] = "metalua/compiler/parser/table.lua",
["metalua.compiler.parser.ext"] = "metalua/compiler/parser/ext.lua",
["metalua.compiler.parser.annot.generator"] = "metalua/compiler/parser/annot/generator.lua",
["metalua.compiler.parser.annot.grammar"] = "metalua/compiler/parser/annot/grammar.lua",
["metalua.compiler.parser.stat"] = "metalua/compiler/parser/stat.lua",
["metalua.compiler.parser.misc"] = "metalua/compiler/parser/misc.lua",
["metalua.compiler.parser.lexer"] = "metalua/compiler/parser/lexer.lua",
["metalua.compiler.parser.meta"] = "metalua/compiler/parser/meta.lua",
["metalua.compiler.parser.expr"] = "metalua/compiler/parser/expr.lua",
["metalua.compiler"] = "metalua/compiler.lua",
["metalua.pprint"] = "metalua/pprint.lua",
}
}

View File

@@ -0,0 +1,34 @@
rock_manifest = {
doc = {
["README-compiler.md"] = "292523d759247d210d32fb2f6153e0f4",
["README-parser.md"] = "b44e3673d96dd296f2c0e92a6c87ee18",
["README.md"] = "20bfb490cddef9e101e44688791abcda"
},
lua = {
metalua = {
compiler = {
parser = {
annot = {
["generator.lua"] = "d86f7507d66ba6a3692a6f8611e9939b",
["grammar.lua"] = "7d195bde7992efd9923771751b67b18f"
},
["expr.lua"] = "3a0b1984a6f92280e2e63b074fdcec10",
["ext.lua"] = "a99e31a07bc390b826f6653bcc47d89b",
["lexer.lua"] = "eac0f9d475d9dae4ea5a2724014cebec",
["meta.lua"] = "12870bceda6395695020b739196e2a92",
["misc.lua"] = "49d59f4fc1bfb77b36f78d4f87ae258f",
["stat.lua"] = "83f10ac899be12ca4df58bbe8645299f",
["table.lua"] = "5d2389e89603b7f78c731e6918aa1a9b"
},
["parser.lua"] = "e6ae68ce200de8071bb0fefad97f9b79"
},
["compiler.lua"] = "ca65ee9a3053581f4315821a31d0c1fd",
grammar = {
["generator.lua"] = "b8a29e817d6798c12f40a230a0f6d0af",
["lexer.lua"] = "7cb7c835479a9be884130eaacb9be60a"
},
["pprint.lua"] = "0b9bd8757b45c2d4be30106abcbd45b2"
}
},
["metalua-parser-0.7.3-2.rockspec"] = "a56680900b0b51701db7cd7abf49af92"
}

View File

@@ -0,0 +1,66 @@
package = "penlight"
version = "0.9.8-1"
source = {
dir = "penlight-0.9.8",
url = "http://stevedonovan.github.com/files/penlight-0.9.8-core.zip",
}
description = {
summary = "Lua utility libraries loosely based on the Python standard libraries",
homepage = "http://stevedonovan.github.com/Penlight",
license = "MIT/X11",
maintainer = "steve.j.donovan@gmail.com",
detailed = [[
Penlight is a set of pure Lua libraries for making it easier to work with common tasks like
iterating over directories, reading configuration files and the like. Provides functional operations
on tables and sequences.
]]
}
dependencies = {
"luafilesystem",
}
build = {
type = "builtin",
modules = {
["pl.strict"] = "lua/pl/strict.lua",
["pl.dir"] = "lua/pl/dir.lua",
["pl.operator"] = "lua/pl/operator.lua",
["pl.input"] = "lua/pl/input.lua",
["pl.config"] = "lua/pl/config.lua",
["pl.seq"] = "lua/pl/seq.lua",
["pl.stringio"] = "lua/pl/stringio.lua",
["pl.text"] = "lua/pl/text.lua",
["pl.test"] = "lua/pl/test.lua",
["pl.tablex"] = "lua/pl/tablex.lua",
["pl.app"] = "lua/pl/app.lua",
["pl.stringx"] = "lua/pl/stringx.lua",
["pl.lexer"] = "lua/pl/lexer.lua",
["pl.utils"] = "lua/pl/utils.lua",
["pl.sip"] = "lua/pl/sip.lua",
["pl.permute"] = "lua/pl/permute.lua",
["pl.pretty"] = "lua/pl/pretty.lua",
["pl.class"] = "lua/pl/class.lua",
["pl.List"] = "lua/pl/List.lua",
["pl.data"] = "lua/pl/data.lua",
["pl.Date"] = "lua/pl/Date.lua",
["pl"] = "lua/pl/init.lua",
["pl.luabalanced"] = "lua/pl/luabalanced.lua",
["pl.comprehension"] = "lua/pl/comprehension.lua",
["pl.path"] = "lua/pl/path.lua",
["pl.array2d"] = "lua/pl/array2d.lua",
["pl.func"] = "lua/pl/func.lua",
["pl.lapp"] = "lua/pl/lapp.lua",
["pl.file"] = "lua/pl/file.lua",
['pl.template'] = "lua/pl/template.lua",
["pl.Map"] = "lua/pl/Map.lua",
["pl.MultiMap"] = "lua/pl/MultiMap.lua",
["pl.OrderedMap"] = "lua/pl/OrderedMap.lua",
["pl.Set"] = "lua/pl/Set.lua",
["pl.xml"] = "lua/pl/xml.lua",
["pl.platf.luajava"] = "lua/pl/platf/luajava.lua"
},
}

View File

@@ -0,0 +1,45 @@
rock_manifest = {
lua = {
pl = {
["Date.lua"] = "d2131d59151ce978c4db6a648fcd275a",
["List.lua"] = "1236c5eb08956619daacd25a462a9682",
["Map.lua"] = "0297a536ac0595ac59e8828f8c867f53",
["MultiMap.lua"] = "e5f898fe2443e51c38825e9bc3d1aee5",
["OrderedMap.lua"] = "bd8e39c59e22c582a33e2f025d3ae914",
["Set.lua"] = "346ff7392fd4aeda418fb832e8da7a7f",
["app.lua"] = "23ffb79e69a3fd679013cf82d95ed792",
["array2d.lua"] = "77618ec2e2de4d6d237484dfd742cd73",
["class.lua"] = "6f58bf39e7f90711b6840ad6955d258e",
["comprehension.lua"] = "f8600ba945dde5d959194500a687c69f",
["config.lua"] = "9ea3ce0ac3cdf2ce0e17f1353f32abb6",
["data.lua"] = "be446ff813b5bcf30b4063601165df6a",
["dir.lua"] = "3d60d4c1caeaabe199fe361e4e9b14a4",
["file.lua"] = "f5c9527ea14b511d2cb9af80b219c562",
["func.lua"] = "cc50d73512b6d0518f6587b82844de8c",
["init.lua"] = "9232be7d8790d4f907972a00dec7949d",
["input.lua"] = "bab7c64ca9a740df5e9fb9909610bbc4",
["lapp.lua"] = "1cc81f048bc3fcd775c40cd9a2d601a7",
["lexer.lua"] = "da0db5e323a2d37545ccb02592d0d3c8",
["luabalanced.lua"] = "00b94a997a9ea4d73f54c10893f3b35f",
["operator.lua"] = "e606629c738966cf497bb938457adebd",
["path.lua"] = "b0714bc337c068b7252f64250fe59604",
["permute.lua"] = "b0ed9ba2787119ef99468329a54ea16a",
platf = {
["luajava.lua"] = "9c2898667281ad9501cc05a8e31a6f53"
},
["pretty.lua"] = "3ece64317ce05916eaba91fa96d9e7c0",
["seq.lua"] = "e99e420345ab11120a7b741d8184920a",
["sip.lua"] = "bde74f65e7246017d3ef034d178100ea",
["strict.lua"] = "720e939931dbbe42fad8fd4e7736435e",
["stringio.lua"] = "a8f4c786ea1b62f16ed05e6b09840044",
["stringx.lua"] = "43f57755969c6b4001316226506a3744",
["tablex.lua"] = "dec027cc3a3901766bd933c5fc0f3e93",
["template.lua"] = "f358175bbb84c401c6213c953ce295a4",
["test.lua"] = "1c45f7b1c438673f1eb668e2ca592f1c",
["text.lua"] = "c30f90cab2d00186a6432e408ba1fe14",
["utils.lua"] = "68cd38638a29b4ab5f1cc0eae38dce77",
["xml.lua"] = "e13ed468c450fccb9a8e858a0f787eef"
}
},
["penlight-0.9.8-1.rockspec"] = "96edac3ff1d0ac57cb45d6551a56a775"
}

View File

@@ -0,0 +1,653 @@
#!/usr/bin/env lua
---------
-- LuaSrcDiet
--
-- Compresses Lua source code by removing unnecessary characters.
-- For Lua 5.1+ source code.
--
-- **Notes:**
--
-- * Remember to update version and date information below (MSG_TITLE).
-- * TODO: passing data tables around is a horrific mess.
-- * TODO: to implement pcall() to properly handle lexer etc. errors.
-- * TODO: need some automatic testing for a semblance of sanity.
-- * TODO: the plugin module is highly experimental and unstable.
----
local equiv = require "luasrcdiet.equiv"
local fs = require "luasrcdiet.fs"
local llex = require "luasrcdiet.llex"
local lparser = require "luasrcdiet.lparser"
local luasrcdiet = require "luasrcdiet.init"
local optlex = require "luasrcdiet.optlex"
local optparser = require "luasrcdiet.optparser"
local byte = string.byte
local concat = table.concat
local find = string.find
local fmt = string.format
local gmatch = string.gmatch
local match = string.match
local print = print
local rep = string.rep
local sub = string.sub
local plugin
local LUA_VERSION = match(_VERSION, " (5%.[123])$") or "5.1"
-- Is --opt-binequiv available for this Lua version?
local BIN_EQUIV_AVAIL = LUA_VERSION == "5.1" and not package.loaded.jit
---------------------- Messages and textual data ----------------------
local MSG_TITLE = fmt([[
LuaSrcDiet: Puts your Lua 5.1+ source code on a diet
Version %s <%s>
]], luasrcdiet._VERSION, luasrcdiet._HOMEPAGE)
local MSG_USAGE = [[
usage: luasrcdiet [options] [filenames]
example:
>luasrcdiet myscript.lua -o myscript_.lua
options:
-v, --version prints version information
-h, --help prints usage information
-o <file> specify file name to write output
-s <suffix> suffix for output files (default '_')
--keep <msg> keep block comment with <msg> inside
--plugin <module> run <module> in plugin/ directory
- stop handling arguments
(optimization levels)
--none all optimizations off (normalizes EOLs only)
--basic lexer-based optimizations only
--maximum maximize reduction of source
(informational)
--quiet process files quietly
--read-only read file and print token stats only
--dump-lexer dump raw tokens from lexer to stdout
--dump-parser dump variable tracking tables from parser
--details extra info (strings, numbers, locals)
features (to disable, insert 'no' prefix like --noopt-comments):
%s
default settings:
%s]]
-- Optimization options, for ease of switching on and off.
--
-- * Positive to enable optimization, negative (no) to disable.
-- * These options should follow --opt-* and --noopt-* style for now.
local OPTION = [[
--opt-comments,'remove comments and block comments'
--opt-whitespace,'remove whitespace excluding EOLs'
--opt-emptylines,'remove empty lines'
--opt-eols,'all above, plus remove unnecessary EOLs'
--opt-strings,'optimize strings and long strings'
--opt-numbers,'optimize numbers'
--opt-locals,'optimize local variable names'
--opt-entropy,'tries to reduce symbol entropy of locals'
--opt-srcequiv,'insist on source (lexer stream) equivalence'
--opt-binequiv,'insist on binary chunk equivalence (only for PUC Lua 5.1)'
--opt-experimental,'apply experimental optimizations'
]]
-- Preset configuration.
local DEFAULT_CONFIG = [[
--opt-comments --opt-whitespace --opt-emptylines
--opt-numbers --opt-locals
--opt-srcequiv --noopt-binequiv
]]
-- Override configurations: MUST explicitly enable/disable everything.
local BASIC_CONFIG = [[
--opt-comments --opt-whitespace --opt-emptylines
--noopt-eols --noopt-strings --noopt-numbers
--noopt-locals --noopt-entropy
--opt-srcequiv --noopt-binequiv
]]
local MAXIMUM_CONFIG = [[
--opt-comments --opt-whitespace --opt-emptylines
--opt-eols --opt-strings --opt-numbers
--opt-locals --opt-entropy
--opt-srcequiv
]] .. (BIN_EQUIV_AVAIL and ' --opt-binequiv' or ' --noopt-binequiv')
local NONE_CONFIG = [[
--noopt-comments --noopt-whitespace --noopt-emptylines
--noopt-eols --noopt-strings --noopt-numbers
--noopt-locals --noopt-entropy
--opt-srcequiv --noopt-binequiv
]]
local DEFAULT_SUFFIX = "_" -- default suffix for file renaming
local PLUGIN_SUFFIX = "luasrcdiet.plugin." -- relative location of plugins
------------- Startup and initialize option list handling -------------
--- Simple error message handler; change to error if traceback wanted.
--
-- @tparam string msg The message to print.
local function die(msg)
print("LuaSrcDiet (error): "..msg); os.exit(1)
end
--die = error--DEBUG
-- Prepare text for list of optimizations, prepare lookup table.
local MSG_OPTIONS = ""
do
local WIDTH = 24
local o = {}
for op, desc in gmatch(OPTION, "%s*([^,]+),'([^']+)'") do
local msg = " "..op
msg = msg..rep(" ", WIDTH - #msg)..desc.."\n"
MSG_OPTIONS = MSG_OPTIONS..msg
o[op] = true
o["--no"..sub(op, 3)] = true
end
OPTION = o -- replace OPTION with lookup table
end
MSG_USAGE = fmt(MSG_USAGE, MSG_OPTIONS, DEFAULT_CONFIG)
--------- Global variable initialization, option set handling ---------
local suffix = DEFAULT_SUFFIX -- file suffix
local option = {} -- program options
local stat_c, stat_l -- statistics tables
--- Sets option lookup table based on a text list of options.
--
-- Note: additional forced settings for --opt-eols is done in optlex.lua.
--
-- @tparam string CONFIG
local function set_options(CONFIG)
for op in gmatch(CONFIG, "(%-%-%S+)") do
if sub(op, 3, 4) == "no" and -- handle negative options
OPTION["--"..sub(op, 5)] then
option[sub(op, 5)] = false
else
option[sub(op, 3)] = true
end
end
end
-------------------------- Support functions --------------------------
-- List of token types, parser-significant types are up to TTYPE_GRAMMAR
-- while the rest are not used by parsers; arranged for stats display.
local TTYPES = {
"TK_KEYWORD", "TK_NAME", "TK_NUMBER", -- grammar
"TK_STRING", "TK_LSTRING", "TK_OP",
"TK_EOS",
"TK_COMMENT", "TK_LCOMMENT", -- non-grammar
"TK_EOL", "TK_SPACE",
}
local TTYPE_GRAMMAR = 7
local EOLTYPES = { -- EOL names for token dump
["\n"] = "LF", ["\r"] = "CR",
["\n\r"] = "LFCR", ["\r\n"] = "CRLF",
}
--- Reads source code from the file.
--
-- @tparam string fname Path of the file to read.
-- @treturn string Content of the file.
local function load_file(fname)
local data, err = fs.read_file(fname, "rb")
if not data then die(err) end
return data
end
--- Saves source code to the file.
--
-- @tparam string fname Path of the destination file.
-- @tparam string dat The data to write into the file.
local function save_file(fname, dat)
local ok, err = fs.write_file(fname, dat, "wb")
if not ok then die(err) end
end
------------------ Functions to deal with statistics ------------------
--- Initializes the statistics table.
local function stat_init()
stat_c, stat_l = {}, {}
for i = 1, #TTYPES do
local ttype = TTYPES[i]
stat_c[ttype], stat_l[ttype] = 0, 0
end
end
--- Adds a token to the statistics table.
--
-- @tparam string tok The token.
-- @param seminfo
local function stat_add(tok, seminfo)
stat_c[tok] = stat_c[tok] + 1
stat_l[tok] = stat_l[tok] + #seminfo
end
--- Computes totals for the statistics table, returns average table.
--
-- @treturn table
local function stat_calc()
local function avg(c, l) -- safe average function
if c == 0 then return 0 end
return l / c
end
local stat_a = {}
local c, l = 0, 0
for i = 1, TTYPE_GRAMMAR do -- total grammar tokens
local ttype = TTYPES[i]
c = c + stat_c[ttype]; l = l + stat_l[ttype]
end
stat_c.TOTAL_TOK, stat_l.TOTAL_TOK = c, l
stat_a.TOTAL_TOK = avg(c, l)
c, l = 0, 0
for i = 1, #TTYPES do -- total all tokens
local ttype = TTYPES[i]
c = c + stat_c[ttype]; l = l + stat_l[ttype]
stat_a[ttype] = avg(stat_c[ttype], stat_l[ttype])
end
stat_c.TOTAL_ALL, stat_l.TOTAL_ALL = c, l
stat_a.TOTAL_ALL = avg(c, l)
return stat_a
end
----------------------------- Main tasks -----------------------------
--- A simple token dumper, minimal translation of seminfo data.
--
-- @tparam string srcfl Path of the source file.
local function dump_tokens(srcfl)
-- Load file and process source input into tokens.
local z = load_file(srcfl)
local toklist, seminfolist = llex.lex(z)
-- Display output.
for i = 1, #toklist do
local tok, seminfo = toklist[i], seminfolist[i]
if tok == "TK_OP" and byte(seminfo) < 32 then
seminfo = "("..byte(seminfo)..")"
elseif tok == "TK_EOL" then
seminfo = EOLTYPES[seminfo]
else
seminfo = "'"..seminfo.."'"
end
print(tok.." "..seminfo)
end--for
end
--- Dumps globalinfo and localinfo tables.
--
-- @tparam string srcfl Path of the source file.
local function dump_parser(srcfl)
-- Load file and process source input into tokens,
local z = load_file(srcfl)
local toklist, seminfolist, toklnlist = llex.lex(z)
-- Do parser optimization here.
local xinfo = lparser.parse(toklist, seminfolist, toklnlist)
local globalinfo, localinfo = xinfo.globalinfo, xinfo.localinfo
-- Display output.
local hl = rep("-", 72)
print("*** Local/Global Variable Tracker Tables ***")
print(hl.."\n GLOBALS\n"..hl)
-- global tables have a list of xref numbers only
for i = 1, #globalinfo do
local obj = globalinfo[i]
local msg = "("..i..") '"..obj.name.."' -> "
local xref = obj.xref
for j = 1, #xref do msg = msg..xref[j].." " end
print(msg)
end
-- Local tables have xref numbers and a few other special
-- numbers that are specially named: decl (declaration xref),
-- act (activation xref), rem (removal xref).
print(hl.."\n LOCALS (decl=declared act=activated rem=removed)\n"..hl)
for i = 1, #localinfo do
local obj = localinfo[i]
local msg = "("..i..") '"..obj.name.."' decl:"..obj.decl..
" act:"..obj.act.." rem:"..obj.rem
if obj.is_special then
msg = msg.." is_special"
end
msg = msg.." -> "
local xref = obj.xref
for j = 1, #xref do msg = msg..xref[j].." " end
print(msg)
end
print(hl.."\n")
end
--- Reads source file(s) and reports some statistics.
--
-- @tparam string srcfl Path of the source file.
local function read_only(srcfl)
-- Load file and process source input into tokens.
local z = load_file(srcfl)
local toklist, seminfolist = llex.lex(z)
print(MSG_TITLE)
print("Statistics for: "..srcfl.."\n")
-- Collect statistics.
stat_init()
for i = 1, #toklist do
local tok, seminfo = toklist[i], seminfolist[i]
stat_add(tok, seminfo)
end--for
local stat_a = stat_calc()
-- Display output.
local function figures(tt)
return stat_c[tt], stat_l[tt], stat_a[tt]
end
local tabf1, tabf2 = "%-16s%8s%8s%10s", "%-16s%8d%8d%10.2f"
local hl = rep("-", 42)
print(fmt(tabf1, "Lexical", "Input", "Input", "Input"))
print(fmt(tabf1, "Elements", "Count", "Bytes", "Average"))
print(hl)
for i = 1, #TTYPES do
local ttype = TTYPES[i]
print(fmt(tabf2, ttype, figures(ttype)))
if ttype == "TK_EOS" then print(hl) end
end
print(hl)
print(fmt(tabf2, "Total Elements", figures("TOTAL_ALL")))
print(hl)
print(fmt(tabf2, "Total Tokens", figures("TOTAL_TOK")))
print(hl.."\n")
end
--- Processes source file(s), writes output and reports some statistics.
--
-- @tparam string srcfl Path of the source file.
-- @tparam string destfl Path of the destination file where to write optimized source.
local function process_file(srcfl, destfl)
-- handle quiet option
local function print(...) --luacheck: ignore 431
if option.QUIET then return end
_G.print(...)
end
if plugin and plugin.init then -- plugin init
option.EXIT = false
plugin.init(option, srcfl, destfl)
if option.EXIT then return end
end
print(MSG_TITLE) -- title message
-- Load file and process source input into tokens.
local z = load_file(srcfl)
if plugin and plugin.post_load then -- plugin post-load
z = plugin.post_load(z) or z
if option.EXIT then return end
end
local toklist, seminfolist, toklnlist = llex.lex(z)
if plugin and plugin.post_lex then -- plugin post-lex
plugin.post_lex(toklist, seminfolist, toklnlist)
if option.EXIT then return end
end
-- Collect 'before' statistics.
stat_init()
for i = 1, #toklist do
local tok, seminfo = toklist[i], seminfolist[i]
stat_add(tok, seminfo)
end--for
local stat1_a = stat_calc()
local stat1_c, stat1_l = stat_c, stat_l
-- Do parser optimization here.
optparser.print = print -- hack
local xinfo = lparser.parse(toklist, seminfolist, toklnlist)
if plugin and plugin.post_parse then -- plugin post-parse
plugin.post_parse(xinfo.globalinfo, xinfo.localinfo)
if option.EXIT then return end
end
optparser.optimize(option, toklist, seminfolist, xinfo)
if plugin and plugin.post_optparse then -- plugin post-optparse
plugin.post_optparse()
if option.EXIT then return end
end
-- Do lexer optimization here, save output file.
local warn = optlex.warn -- use this as a general warning lookup
optlex.print = print -- hack
toklist, seminfolist, toklnlist
= optlex.optimize(option, toklist, seminfolist, toklnlist)
if plugin and plugin.post_optlex then -- plugin post-optlex
plugin.post_optlex(toklist, seminfolist, toklnlist)
if option.EXIT then return end
end
local dat = concat(seminfolist)
-- Depending on options selected, embedded EOLs in long strings and
-- long comments may not have been translated to \n, tack a warning.
if find(dat, "\r\n", 1, 1) or
find(dat, "\n\r", 1, 1) then
warn.MIXEDEOL = true
end
-- Test source and binary chunk equivalence.
equiv.init(option, llex, warn)
equiv.source(z, dat)
if BIN_EQUIV_AVAIL then
equiv.binary(z, dat)
end
local smsg = "before and after lexer streams are NOT equivalent!"
local bmsg = "before and after binary chunks are NOT equivalent!"
-- for reporting, die if option was selected, else just warn
if warn.SRC_EQUIV then
if option["opt-srcequiv"] then die(smsg) end
else
print("*** SRCEQUIV: token streams are sort of equivalent")
if option["opt-locals"] then
print("(but no identifier comparisons since --opt-locals enabled)")
end
print()
end
if warn.BIN_EQUIV then
if option["opt-binequiv"] then die(bmsg) end
elseif BIN_EQUIV_AVAIL then
print("*** BINEQUIV: binary chunks are sort of equivalent")
print()
end
-- Save optimized source stream to output file.
save_file(destfl, dat)
-- Collect 'after' statistics.
stat_init()
for i = 1, #toklist do
local tok, seminfo = toklist[i], seminfolist[i]
stat_add(tok, seminfo)
end--for
local stat_a = stat_calc()
-- Display output.
print("Statistics for: "..srcfl.." -> "..destfl.."\n")
local function figures(tt)
return stat1_c[tt], stat1_l[tt], stat1_a[tt],
stat_c[tt], stat_l[tt], stat_a[tt]
end
local tabf1, tabf2 = "%-16s%8s%8s%10s%8s%8s%10s",
"%-16s%8d%8d%10.2f%8d%8d%10.2f"
local hl = rep("-", 68)
print("*** lexer-based optimizations summary ***\n"..hl)
print(fmt(tabf1, "Lexical",
"Input", "Input", "Input",
"Output", "Output", "Output"))
print(fmt(tabf1, "Elements",
"Count", "Bytes", "Average",
"Count", "Bytes", "Average"))
print(hl)
for i = 1, #TTYPES do
local ttype = TTYPES[i]
print(fmt(tabf2, ttype, figures(ttype)))
if ttype == "TK_EOS" then print(hl) end
end
print(hl)
print(fmt(tabf2, "Total Elements", figures("TOTAL_ALL")))
print(hl)
print(fmt(tabf2, "Total Tokens", figures("TOTAL_TOK")))
print(hl)
-- Report warning flags from optimizing process.
if warn.LSTRING then
print("* WARNING: "..warn.LSTRING)
elseif warn.MIXEDEOL then
print("* WARNING: ".."output still contains some CRLF or LFCR line endings")
elseif warn.SRC_EQUIV then
print("* WARNING: "..smsg)
elseif warn.BIN_EQUIV then
print("* WARNING: "..bmsg)
end
print()
end
---------------------------- Main functions ---------------------------
local arg = {...} -- program arguments
set_options(DEFAULT_CONFIG) -- set to default options at beginning
--- Does per-file handling, ship off to tasks.
--
-- @tparam {string,...} fspec List of source files.
local function do_files(fspec)
for i = 1, #fspec do
local srcfl = fspec[i]
local destfl
-- Find and replace extension for filenames.
local extb, exte = find(srcfl, "%.[^%.%\\%/]*$")
local basename, extension = srcfl, ""
if extb and extb > 1 then
basename = sub(srcfl, 1, extb - 1)
extension = sub(srcfl, extb, exte)
end
destfl = basename..suffix..extension
if #fspec == 1 and option.OUTPUT_FILE then
destfl = option.OUTPUT_FILE
end
if srcfl == destfl then
die("output filename identical to input filename")
end
-- Perform requested operations.
if option.DUMP_LEXER then
dump_tokens(srcfl)
elseif option.DUMP_PARSER then
dump_parser(srcfl)
elseif option.READ_ONLY then
read_only(srcfl)
else
process_file(srcfl, destfl)
end
end--for
end
--- The main function.
local function main()
local fspec = {}
local argn, i = #arg, 1
if argn == 0 then
option.HELP = true
end
-- Handle arguments.
while i <= argn do
local o, p = arg[i], arg[i + 1]
local dash = match(o, "^%-%-?")
if dash == "-" then -- single-dash options
if o == "-h" then
option.HELP = true; break
elseif o == "-v" then
option.VERSION = true; break
elseif o == "-s" then
if not p then die("-s option needs suffix specification") end
suffix = p
i = i + 1
elseif o == "-o" then
if not p then die("-o option needs a file name") end
option.OUTPUT_FILE = p
i = i + 1
elseif o == "-" then
break -- ignore rest of args
else
die("unrecognized option "..o)
end
elseif dash == "--" then -- double-dash options
if o == "--help" then
option.HELP = true; break
elseif o == "--version" then
option.VERSION = true; break
elseif o == "--keep" then
if not p then die("--keep option needs a string to match for") end
option.KEEP = p
i = i + 1
elseif o == "--plugin" then
if not p then die("--plugin option needs a module name") end
if option.PLUGIN then die("only one plugin can be specified") end
option.PLUGIN = p
plugin = require(PLUGIN_SUFFIX..p)
i = i + 1
elseif o == "--quiet" then
option.QUIET = true
elseif o == "--read-only" then
option.READ_ONLY = true
elseif o == "--basic" then
set_options(BASIC_CONFIG)
elseif o == "--maximum" then
set_options(MAXIMUM_CONFIG)
elseif o == "--none" then
set_options(NONE_CONFIG)
elseif o == "--dump-lexer" then
option.DUMP_LEXER = true
elseif o == "--dump-parser" then
option.DUMP_PARSER = true
elseif o == "--details" then
option.DETAILS = true
elseif OPTION[o] then -- lookup optimization options
set_options(o)
else
die("unrecognized option "..o)
end
else
fspec[#fspec + 1] = o -- potential filename
end
i = i + 1
end--while
if option.HELP then
print(MSG_TITLE..MSG_USAGE); return true
elseif option.VERSION then
print(MSG_TITLE); return true
end
if option["opt-binequiv"] and not BIN_EQUIV_AVAIL then
die("--opt-binequiv is available only for PUC Lua 5.1!")
end
if #fspec > 0 then
if #fspec > 1 and option.OUTPUT_FILE then
die("with -o, only one source file can be specified")
end
do_files(fspec)
return true
else
die("nothing to do!")
end
end
-- entry point -> main() -> do_files()
if not main() then
die("Please run with option -h or --help for usage information")
end

View File

@@ -0,0 +1,300 @@
= Features and Usage
Kein-Hong Man
2011-09-13
== Features
LuaSrcDiet features include the following:
* Predefined default, _--basic_ (token-only) and _--maximum_ settings.
* Avoid deleting a block comment with a certain message with _--keep_; this is for copyright or license texts.
* Special handling for `#!` (shbang) lines and in functions, `self` implicit parameters.
* Dumping of raw information using _--dump-lexer_ and _--dump-parser_.
See the `samples` directory.
* A HTML plugin: outputs files that highlights globals and locals, useful for eliminating globals. See the `samples` directory.
* An SLOC plugin: counts significant lines of Lua code, like SLOCCount.
* Source and binary equivalence testing with _--opt-srcequiv_ and _--opt-binequiv_.
List of optimizations:
* Line endings are always normalized to LF, except those embedded in comments or strings.
* _--opt-comments_: Removal of comments and comment blocks.
* _--opt-whitespace_: Removal of whitespace, excluding end-of-line characters.
* _--opt-emptylines_: Removal of empty lines.
* _--opt-eols_: Removal of unnecessary end-of-line characters.
* _--opt-strings_: Rewrite strings and long strings. See the `samples` directory.
* _--opt-numbers_: Rewrite numbers. See the `samples` directory.
* _--opt-locals_: Rename local variable names. Does not rename field or method names.
* _--opt-entropy_: Tries to improve symbol entropy when renaming locals by calculating actual letter frequencies.
* _--opt-experimental_: Apply experimental optimizations.
LuaSrcDiet tries to allow each option to be enabled or disabled separately, but they are not completely orthogonal.
If comment removal is disabled, LuaSrcDiet only removes trailing whitespace.
Trailing whitespace is not removed in long strings, a warning is generated instead.
If empty line removal is disabled, LuaSrcDiet keeps all significant code on the same lines.
Thus, a user is able to debug using the original sources as a reference since the line numbering is unchanged.
String optimization deals mainly with optimizing escape sequences, but delimiters can be switched between single quotes and double quotes if the source size of the string can be reduced.
For long strings and long comments, LuaSrcDiet also tries to reduce the `=` separators in the
delimiters if possible.
For number optimization, LuaSrcDiet saves space by trying to generate the shortest possible sequence, and in the process it does not produce “proper” scientific notation (e.g. 1.23e5) but does away with the decimal point (e.g. 123e3) instead.
The local variable name optimizer uses a full parser of Lua 5.1 source code, thus it can rename all local variables, including upvalues and function parameters.
It should handle the implicit `self` parameter gracefully.
In addition, local variable names are either renamed into the shortest possible names following English frequent letter usage or are arranged by calculating entropy with the _--opt-entropy_ option.
Variable names are reused whenever possible, reducing the number of unique variable names.
For example, for `LuaSrcDiet.lua` (version 0.11.0), 683 local identifiers representing 88 unique names were optimized into 32 unique names, all which are one character in length, saving over 2600 bytes.
If you need some kind of reassurance that your app will still work at reduced size, see the section on verification below.
== Usage
LuaSrcDiet needs a Lua 5.1.x (preferably Lua 5.1.4) binary to run.
On Unix machines, one can use the following command line:
[source, sh]
LuaSrcDiet myscript.lua -o myscript_.lua
On Windows machines, the above command line can be used on Cygwin, or you can run Lua with the LuaSrcDiet script like this:
[source, sh]
lua LuaSrcDiet.lua myscript.lua -o myscript_.lua
When run without arguments, LuaSrcDiet prints a list of options.
Also, you can check the `Makefile` for some examples of command lines to use.
For example, for maximum code size reduction and maximum verbosity, use:
[source, sh]
LuaSrcDiet --maximum --details myscript.lua -o myscript_.lua
=== Output Example
A sample output of LuaSrcDiet 0.11.0 for processing `llex.lua` at _--maximum_ settings is as follows:
----
Statistics for: LuaSrcDiet.lua -> sample/LuaSrcDiet.lua
*** local variable optimization summary ***
----------------------------------------------------------
Variable Unique Decl. Token Size Average
Types Names Count Count Bytes Bytes
----------------------------------------------------------
Global 10 0 19 95 5.00
----------------------------------------------------------
Local (in) 88 153 683 3340 4.89
TOTAL (in) 98 153 702 3435 4.89
----------------------------------------------------------
Local (out) 32 153 683 683 1.00
TOTAL (out) 42 153 702 778 1.11
----------------------------------------------------------
*** lexer-based optimizations summary ***
--------------------------------------------------------------------
Lexical Input Input Input Output Output Output
Elements Count Bytes Average Count Bytes Average
--------------------------------------------------------------------
TK_KEYWORD 374 1531 4.09 374 1531 4.09
TK_NAME 795 3963 4.98 795 1306 1.64
TK_NUMBER 54 59 1.09 54 59 1.09
TK_STRING 152 1725 11.35 152 1717 11.30
TK_LSTRING 7 1976 282.29 7 1976 282.29
TK_OP 997 1092 1.10 997 1092 1.10
TK_EOS 1 0 0.00 1 0 0.00
--------------------------------------------------------------------
TK_COMMENT 140 6884 49.17 1 18 18.00
TK_LCOMMENT 7 1723 246.14 0 0 0.00
TK_EOL 543 543 1.00 197 197 1.00
TK_SPACE 1270 2465 1.94 263 263 1.00
--------------------------------------------------------------------
Total Elements 4340 21961 5.06 2841 8159 2.87
--------------------------------------------------------------------
Total Tokens 2380 10346 4.35 2380 7681 3.23
--------------------------------------------------------------------
----
Overall, the file size is reduced by more than 9 kiB.
Tokens in the above report can be classified into “real” or actual tokens, and “fake” or whitespace tokens.
The number of “real” tokens remained the same.
Short comments and long comments were completely eliminated.
The number of line endings was reduced by 59, while all but 152 whitespace characters were optimized away.
So, token separators (whitespace, including line endings) now takes up just 10 % of the total file size.
No optimization of number tokens was possible, while 2 bytes were saved for string tokens.
For local variable name optimization, the report shows that 38 unique local variable names were reduced to 20 unique names.
The number of identifier tokens should stay the same (there is currently no optimization option to optimize away non-essential or unused “real” tokens).
Since there can be at most 53 single-character identifiers, all local variables are now one character in length.
Over 600 bytes was saved.
_--details_ will give a longer report and much more information.
A sample output of LuaSrcDiet 0.12.0 for processing the one-file `LuaSrcDiet.lua` program itself at _--maximum_ and _--opt-experimental_ settings is as follows:
----
*** local variable optimization summary ***
----------------------------------------------------------
Variable Unique Decl. Token Size Average
Types Names Count Count Bytes Bytes
----------------------------------------------------------
Global 27 0 51 280 5.49
----------------------------------------------------------
Local (in) 482 1063 4889 21466 4.39
TOTAL (in) 509 1063 4940 21746 4.40
----------------------------------------------------------
Local (out) 55 1063 4889 4897 1.00
TOTAL (out) 82 1063 4940 5177 1.05
----------------------------------------------------------
*** BINEQUIV: binary chunks are sort of equivalent
Statistics for: LuaSrcDiet.lua -> app_experimental.lua
*** lexer-based optimizations summary ***
--------------------------------------------------------------------
Lexical Input Input Input Output Output Output
Elements Count Bytes Average Count Bytes Average
--------------------------------------------------------------------
TK_KEYWORD 3083 12247 3.97 3083 12247 3.97
TK_NAME 5401 24121 4.47 5401 7552 1.40
TK_NUMBER 467 494 1.06 467 494 1.06
TK_STRING 787 7983 10.14 787 7974 10.13
TK_LSTRING 14 3453 246.64 14 3453 246.64
TK_OP 6381 6861 1.08 6171 6651 1.08
TK_EOS 1 0 0.00 1 0 0.00
--------------------------------------------------------------------
TK_COMMENT 1611 72339 44.90 1 18 18.00
TK_LCOMMENT 18 4404 244.67 0 0 0.00
TK_EOL 4419 4419 1.00 1778 1778 1.00
TK_SPACE 10439 24475 2.34 2081 2081 1.00
--------------------------------------------------------------------
Total Elements 32621 160796 4.93 19784 42248 2.14
--------------------------------------------------------------------
Total Tokens 16134 55159 3.42 15924 38371 2.41
--------------------------------------------------------------------
* WARNING: before and after lexer streams are NOT equivalent!
----
The command line was:
[source, sh]
lua LuaSrcDiet.lua LuaSrcDiet.lua -o app_experimental.lua --maximum --opt-experimental --noopt-srcequiv
The important thing to note is that while the binary chunks are equivalent, the source lexer streams are not equivalent.
Hence, the _--noopt-srcequiv_ makes LuaSrcDiet report a warning for failing the source equivalence test.
`LuaSrcDiet.lua` was reduced from 157 kiB to about 41.3 kiB.
The _--opt-experimental_ option saves an extra 205 bytes over standard _--maximum_.
Note the reduction in `TK_OP` count due to a reduction in semicolons and parentheses.
`TK_SPACE` has actually increased a bit due to semicolons that are changed into single spaces; some of these spaces could not be removed.
For more performance numbers, see the <<performance-stats#, Performance Statistics>> page.
== Verification
Code size reduction can be quite a hairy thing (even I peer at the results in suspicion), so some kind of verification is desirable for users who expect processed files to _not_ blow up.
Since LuaSrcDiet has been talked about as a tool to reduce code size in projects such as WoW add-ons, `eLua` and `nspire`, adding a verification step will reduce risk for all users of LuaSrcDiet.
LuaSrcDiet performs two kinds of equivalence testing as of version 0.12.0.
The two tests can be very, very loosely termed as _source equivalence testing_ and _binary equivalence testing_.
They are controlled by the _--opt-srcequiv_ and _--opt-binequiv_ options and are enabled by default.
Testing behaviour can be summarized as follows:
* Both tests are always executed.
The options control the resulting actions taken.
* Both options are normally enabled.
This will make any failing test to throw an error.
* When an option is disabled, LuaSrcDiet will at most print a warning.
* For passing results, see the following subsections that describe what the tests actually does.
You only need to disable a testing option for experimental optimizations (see the following section for more information on this).
For anything up to and including _--maximum_, both tests should pass.
If any test fail under these conditions, then something has gone wrong with LuaSrcDiet, and I would be interested to know what has blown up.
=== _--opt-srcequiv_ Source Equivalence
The source equivalence test uses LuaSrcDiets lexer to read and compare the _before_ and _after_ lexer token streams.
Numbers and strings are dumped as binary chunks using `loadstring()` and `string.dump()` and the results compared.
If your file passes this test, it means that a Lua 5.1.x binary should see the exact same token streams for both _before_ and _after_ files.
That is, the parser in Lua will see the same lexer sequence coming from the source for both files and thus they _should_ be equivalent.
Touch wood.
Heh.
However, if you are _cross-compiling_, it may be possible for this test to fail.
Experienced Lua developers can modify `equiv.lua` to handle such cases.
=== _--opt-binequiv_ Binary Equivalence
The binary equivalence test uses `loadstring()` and `string.dump()` to generate binary chunks of the entire _before_ and _after_ files.
Also, any shbang (`#!`) lines are removed prior to generation of the binary chunks.
The binary chunks are then run through a fake `undump` routine to verify the integrity of the binary chunks and to compare all parts that ought to be identical.
On a per-function prototype basis (where _ignored_ means that any difference between the two binary chunks is ignored):
* All debug information is ignored.
* The source name is ignored.
* Any line number data is ignored.
For example, `linedefined` and `lastlinedefined`.
The rest of the two binary chunks must be identical.
So, while the two are not binary-exact, they can be loosely termed as “equivalent” and should run in exactly the same manner.
Sort of.
You get the idea.
This test may also cause problems if you are _cross-compiling_.
== Experimental Stuff
The _--opt-experimental_ option applies experimental optimizations that generally, makes changes to “real” tokens.
Such changes may or may not lead to the result failing binary chunk equivalence testing.
They would likely fail source lexer stream equivalence testing, so the _--noopt-srcequiv_ option needs to be applied so that LuaSrcDiet just gives a warning instead of an error.
For sample files, see the `samples` directory.
Currently implemented experimental optimizations are as follows:
=== Semicolon Operator Removal
The semicolon (`;`) operator is an optional operator that is used to separate statements.
The optimization turns all of these operators into single spaces, which are then run through whitespace removal.
At worst, there will be no change to file size.
* _Fails_ source lexer stream equivalence.
* _Passes_ binary chunk equivalence.
=== Function Call Syntax Sugar Optimization
This optimization turns function calls that takes a single string or long string parameter into its syntax-sugar representation, which leaves out the parentheses.
Since strings can abut anything, each instance saves 2 bytes.
For example, the following:
[source, lua]
fish("cow")fish('cow')fish([[cow]])
is turned into:
[source, lua]
fish"cow"fish'cow'fish[[cow]]
* _Fails_ source lexer stream equivalence.
* _Passes_ binary chunk equivalence.
=== Other Experimental Optimizations
There are two more of these optimizations planned, before focus is turned to the Lua 5.2.x series:
* Simple `local` keyword removal.
Planned to work for a few kinds of patterns only.
* User directed name replacement, which will need user input to modify names or identifiers used in table keys and function methods or fields.

View File

@@ -0,0 +1,128 @@
= Performance Statistics
Kein-Hong Man
2011-09-13
== Size Comparisons
The following is the result of processing `llex.lua` from LuaSrcDiet 0.11.0 using various optimization options:
|===
| LuaSrcDiet Option | Size (bytes)
| Original | 12,421
| Empty lines only | 12,395
| Whitespace only | 9,372
| Local rename only | 11,794
| _--basic_ setting | 3,835
| Program default | 3,208
| _--maximum_ setting | 3,130
|===
The programs default settings does not remove all unnecessary EOLs.
The _--basic_ setting is more conservative than the default settings, it disables optimization of strings and numbers and renaming of locals.
For version 0.12.0, the following is the result of processing `LuaSrcDiet.lua` using various optimization options:
|===
| LuaSrcDiet Option | Size (bytes)
| Original | 160,796
| _--basic_ setting | 60,219
| Program default | 43,650
| _--maximum_ setting | 42,453
| max + experimental | 42,248
|===
The above best size can go a lot lower with simple `local` keyword removal and user directed name replacement, which will be the subject of the next release of LuaSrcDiet.
== Compression and luac
File sizes of LuaSrcDiet 0.11.0 main files in various forms:
[cols="m,5*d", options="header,footer"]
|===
| Source File | Original Size (bytes) | `luac` normal (bytes) | `luac` stripped (bytes) | LuaSrcDiet _--basic_ (bytes) | LuaSrcDiet _--maximum_ (bytes)
| LuaSrcDiet.lua | 21,961 | 20,952 | 11,000 | 11,005 | 8,159
| llex.lua | 12,421 | 8,613 | 4,247 | 3,835 | 3,130
| lparser.lua | 41,757 | 27,215 | 12,506 | 11,755 | 7,666
| optlex.lua | 31,009 | 16,992 | 8,021 | 9,129 | 6,858
| optparser.lua | 16,511 | 9,021 | 3,520 | 5,087 | 2,999
| Total | 123,659 | 82,793 | 39,294 | 40,811 | 28,812
|===
* “LuaSrcDiet --maximum” has the smallest total file size.
* The ratio of “Original Size” to “LuaSrcDiet --maximum” is *4.3*.
* The ratio of “Original Size” to “luac stripped” is *3.1*.
* The ratio of “luac stripped” to “LuaSrcDiet --maximum” is *1.4*.
Compressibility of LuaSrcDiet 0.11.0 main files in various forms:
|===
| Compression Method | Original Size | `luac` normal | `luac` stripped | LuaSrcDiet _--basic_ | LuaSrcDiet _--maximum_
| Uncompressed originals | 123,659 | 82,793 | 39,294 | 40,811 | 28,812
| gzip -9 | 28,288 | 29,210 | 17,732 | 12,041 | 10,451
| bzip2 -9 | 24,407 | 27,232 | 16,856 | 11,480 | 9,815
| lzma (7-zip max) | 25,530 | 23,908 | 15,741 | 11,241 | 9,685
|===
* “LuaSrcDiet --maximum” has the smallest total file size (but a binary chunk loads faster and works with a smaller Lua executable).
* The ratio of “Original size” to “Original size + bzip2” is *5.1*.
* The ratio of “Original size” to “LuaSrcDiet --maximum + bzip2” is *12.6*.
* The ratio of “LuaSrcDiet --maximum” to “LuaSrcDiet --maximum + bzip2” is *2.9*.
* The ratio of “Original size” to “luac stripped + bzip2” is *7.3*.
* The ratio of “luac stripped” to “luac stripped + bzip2” is *2.3*.
* The ratio of “luac stripped + bzip2” to “LuaSrcDiet --maximum + bzip2” is *1.7*.
So, squeezed source code are smaller than stripped binary chunks and compresses better than stripped binary chunks, at a ratio of 2.9 for squeezed source code versus 2.3 for stripped binary chunks.
Compressed binary chunks is still a very efficient way of storing Lua scripts, because using only binary chunks allow for the parts of Lua needed to compile from sources to be omitted (`llex.o`, `lparser.o`, `lcode.o`, `ldump.o`), saving over 24KB in the process.
Note that LuaSrcDiet _does not_ answer the question of whether embedding source code is better or embedding binary chunks is better.
It is simply a utility for producing smaller source code files and an exercise in processing Lua source code using a Lua-based lexer and parser skeleton.
== Compile Speed
The following is a primitive attempt to analyze in-memory Lua script loading performance (using the `loadstring` function in Lua).
The LuaSrcDiet 0.11.0 files (original, squeezed with _--maximum_ and stripped binary chunks versions) are loaded into memory first before a loop runs to repeatedly load the script files for 10 seconds.
A null loop is also performed (processing empty strings) and the time taken per null iteration is subtracted as a form of null adjustment.
Then, various performance parameters are calculated.
Note that `LuaSrcDiet.lua` was slightly modified (`#!` line removed) to let the `loadstring` function run.
The results below were obtained with a Lua 5.1.3 executable compiled using `make generic` on Cygwin/Windows XP SP2 on a Sempron 3000+ (1.8GHz).
The LuaSrcDiet 0.11.0 source files have 11,180 “real” tokens in total.
[cols="<h,4*d", options="header"]
|===
| | Null loop | Stripped binary chunk | Original Sources | Squeezed Sources
| Total Size (bytes) | 0 | 39,294 | 123,640 | 28,793
| Iterations | 312,155 | 9,680 | 1306 | 1,592
| Duration (sec) | 10 | 10 | 10 | 10
| Time/iteration (msec) | 0.032 | 1.033 | 7.657 | 6.281
| _Time/iteration, null adjusted (msec)_ | | 1.001 | 7.625 | 6.249
| _Load rate (MiB/sec)_ | | 37.44 | 15.46 | 4.39
| Load time per byte (ns) | | 25.5 | 61.7 | 217.0
| Load time per token (ns) | | | 682 | 559
| Source time vs binary chunk time ratio | | 1.00 | 7.62 | 6.24
| Binary chunk rate vs. source rate ratio | | 1.00 | 2.42 | 8.53
|===
The above shows that stripped binary chunks is still, in many ways, the highest-performance form of fixed Lua scripts.
On a very average machine, scripts load at over 37 MiB/sec (in memory).
This is very comparable to the burst speeds of common desktop hard disks of 2008.
If instant response is paramount, stripped binary chunks has little competition.
By contrast, source code that is squeezed to the maximum using LuaSrcDiet can only muster an in-memory load rate of 4.4 MiB/sec.
The original sources load at about 15.5 MiB/sec, but most of the speed is from the lexer scanning over comments and whitespace.
A quick calculation indicates that the speed of the lexer over comments and whitespace can be as much as 65 MiB/sec, but note that the speed is all for naught.
What really matters are the real tokens, and the squeezed source code manages to load faster than the original sources by 18 %.
So, the loading of stripped binary chunks is faster than squeezed source code by a bit over 6×.
The 4.4 MiB/sec speed for squeezed source code is still quite respectable.
When an application considers the time taken to load data from the disk and perhaps the time taken to decompress, loading source code may be perfectly fine in terms of performance.
For programs that already embed source code, using LuaSrcDiet to squeeze the source code probably speeds loading up by a tiny bit in addition to making programs smaller.

View File

@@ -0,0 +1,386 @@
= Technical Notes
Kein-Hong Man
2011-09-13
== Lexer Notes
The lexer (`llex.lua`) is a version of the native 5.1.x lexer from Yueliang 0.4.0, with significant modifications.
It does have several limitations:
* The decimal point must be `.` (period).
There is no localized decimal point replacement magic.
* There is no support for nested `[[`...`]]` long strings (no `LUA_COMPAT_LSTR`).
* The lexer may not properly lex source code with characters beyond the normal ASCII character set.
Identifiers with accented characters (or any character beyond a byte value of 127) cannot be recognized.
Instead of returning one token on each call, `llex.lua` processes an entire string (all data from an entire file) and returns.
Two lists (tokens and semantic information items) are set up in the module for use by the caller.
For maximum flexibility during processing, the lexer returns non-grammar lexical elements as tokens too.
Non-grammar elements, such as comments, whitespace, line endings, are classified along with “normal” tokens.
The lexer classifies 7 kinds of grammar tokens and 4 kinds of non-grammar tokens, as follows:
[cols="m,d"]
|===
| Grammar Token | Description
| TK_KEYWORD | keywords
| TK_NAME | identifiers
| TK_NUMBER | numbers (unconverted, kept in original form)
| TK_STRING | strings (no translation is done, includes delimiters)
| TK_LSTRING | long strings (no translation is done, includes delimiters)
| TK_OP | operators and punctuation (most single-char, some double)
| TK_EOS | end-of-stream (there is only one for each file/stream)
|===
[cols="m,d"]
|===
| Whitespace Token | Description
| TK_SPACE | whitespace (generally, spaces, \t, \v and \f)
| TK_COMMENT | comments (includes delimiters, also includes special first line shbang, which is handled specially in the optimizer)
| TK_LCOMMENT | block comments (includes delimiters)
| TK_EOL | end-of-lines (excludes those embedded in strings)
|===
A list of tokens can be generated by using the _--dump-lexer_ option, like this:
[source, sh]
lua LuaSrcDiet.lua --dump-lexer llex.lua > dump_llex.dat
== Lexer Optimizations
We aim to keep lexer-based optimizations free of parser considerations, i.e. we allow for generalized optimization of token sequences.
The table below considers the requirements for all combinations of significant tokens (except `TK_EOS`).
Other tokens are whitespace-like.
Comments can be considered to be a special kind of whitespace, e.g. a short comment needs to have a following EOL token, if we do not want to optimize away short comments.
[cols="h,6*m", options="header"]
|===
| _1st  2nd Token_ | Keyword | Name | Number | String | LString | Oper
| Keyword | [S] | [S] | [S] | | |
| Name | [S] | [S] | [S] | | |
| Number | [S] | [S] | [S] | | | [1]
| String | | | | | |
| LString | | | | | |
| Oper | | | [1] | | | [2]
|===
A dash (`-`) in the above means that the first token can abut the second token.
`*[S]*`:: Need at least one whitespace, set as either a space or kept as an EOL.
`*[1]*`::
Need a space if operator is a `.`, all others okay.
A `+` or `-` is used as part of a floating-point spec, but there does not appear to be any way of creating a float by joining with number with a `+` or `-` plus another number.
Since an `e` has to be somewhere in the first token, this cant be done.
`*[2]*`::
Normally there cannot be consecutive operators, but we plan to allow for generalized optimization of token sequences, i.e. even sequences that are grammatically illegal; so disallow adjacent operators if:
* the first is in `[=<>]` and the second is `=`
* disallow dot sequences to be adjacent, but `...` first okay
* disallow `[` followed by `=` or `[` (not optimal)
Also, a minus `-` cannot preceed a Comment or LComment, because comments start with a `--` prefix.
Apart from that, all Comment or LComment tokens can be set abut with a real token.
== Local Variable Renaming
The following discusses the problem of local variable optimization, specifically _local variable renaming_ in order to reduce source code size.
=== TK_NAME Token Considerations
A `TK_NAME` token means a number of things, and some of these cannot be renamed without analyzing the source code.
We are interested in the use of `TK_NAME` in the following:
[loweralpha]
. global variable access,
. local variable declaration, including `local` statements, `local` functions, function parameters, implicit `self` locals,
. local variable access, including upvalue access.
`TK_NAME` is also used in parts of the grammar as constant strings these tokens cannot be optimized without user assistance.
These include usage as:
[loweralpha, start=4]
. keys in `key=value` pairs in table construction,
. field or method names in `a:b` or `a.b` syntax forms.
For the local variable name optimization scheme used, we do not consider (d) and (e), and while global variables cannot be renamed without some kind of user assistance, they need to be considered or tracked as part of Luas variable access scheme.
=== Lifetime of a Local Variable
Consider the following example:
[source, lua]
local string, table = string, table
In the example, the two locals are assigned the values of the globals with the same names.
When Lua encounters the declaration portion:
[source, lua]
local string, table
the parser cannot immediately make the two local variable available to following code.
In the parser and code generator, locals are inactive when entries are created.
They are activated only when the function `adjustlocalvars()` is called to activate the appropriate local variables.
NOTE: The terminology used here may not be identical to the ones used in the Dragon Book they merely follow the LuaSrcDiet code as it was written before I have read the Dragon Book.
In the example, the two local variables are activated only after the whole statement has been parsed, that is, after the last `table` token.
Hence, the statement works as expected.
Also, once the two local variables goes out of scope, `removevars()` is called to deactivate them, allowing other variables of the same name to become visible again.
Another example worth mentioning is:
[source, lua]
local a, a, a, = 1, 2, 3
The above will assign 3 to `a`.
Thus, when optimizing local variable names, (1) we need to consider accesses of global variable names affecting the namespace, (2) for the local variable names themselves, we need to consider when they are declared, activated and removed, and (3) within the “live” time of locals, we need to know when they are accessed (since locals that are never accessed dont really matter.)
=== Local Variable Tracking
Every local variable declaration is considered an object to be renamed.
From the parser, we have the original name of the local variable, the token positions for declaration, activation and removal, and the token position for all the `TK_NAME` tokens which references this local.
All instances of the implicit `self` local variable are also flagged as such.
In addition to local variable information, all global variable accesses are tabled, one object entry for one name, and each object has a corresponding list of token positions for the `TK_NAME` tokens, which is where the global variables were accessed.
The key criteria is: *Our act of renaming cannot change the visibility of any of these locals and globals at the time they are accessed*.
However, _their scope of visibility may be changed during which they are not accessed_, so someone who tries to insert a variable reference somewhere into a program that has its locals renamed may find that it now refers to a different variable.
Of course, if every variable has a unique name, then there is no need for a name allocation algorithm, as there will be no conflict.
But, in order to maximize utilization of short identifier names to reduce the final code size, we want to reuse the names as much as possible.
In addition, fewer names will likely reduce symbol entropy and may slightly improve compressibility of the source code.
LuaSrcDiet avoids the use of non-ASCII letters, so there are only 53 single-character variable names.
=== Name Allocation Theory
To understand the renaming algorithm, first we need to establish how different local and global variables can operate happily without interfering with each other.
Consider three objects, local object A, local object B and global object G.
A and B involve declaration, activation and removal, and within the period it is active, there may be zero or more accesses of the local.
For G, there are only global variable accesses to look into.
Assume that we have assigned a new name to A and we wish to consider its effects on other locals and globals, for which we choose B and G as examples.
We assume local B has not been assigned a new name as we expect our algorithm to take care of collisions.
As lifetime is something like this:
----
Decl Act Rem
+ +-------------------------------+
-------------------------------------------------
----
where “Decl” is the time of declaration, “Act” is the time of activation, and “Rem” is the time of removal.
Between “Act” and “Rem”, the local is alive or “live” and Lua can see it if its corresponding `TK_NAME` identifier comes up.
----
Decl Act Rem
+ +-------------------------------+
-------------------------------------------------
* * * *
(1) (2) (3) (4)
----
Recall that the key criteria is to not change the visibility of globals and locals during when they are accessed.
Consider local and global accesses at (1), (2), (3) and (4).
A global G of the same name as A will only collide at (3), where Lua will see A and not G.
Since G must be accessed at (3) according to what the parser says, and we cannot modify the positions of “Decl”, “Act” and “Rem”, it follows that A cannot have the same name as G.
----
Decl Act Rem
+ +-----------------------+
---------------------------------
(1)+ +---+ (2)+ +---+ (3)+ +---+ (4)+ +---+
--------- --------- --------- ---------
----
For the case of A and B having the same names and colliding, consider the cases for which B is at (1), (2), (3) or (4) in the above.
(1) and (4) means that A and B are completely isolated from each other, hence in the two cases, A and B can safely use the same variable names.
To be specific, since we have assigned A, B is considered completely isolated from A if Bs activation-to-removal period is isolated from the time of As first access to last access, meaning Bs active time will never affect any of As accesses.
For (2) and (3), we have two cases where we need to consider which one has been activated first.
For (2), B is active before A, so A cannot impose on B.
But As accesses are valid while B is active, since A can override B.
For no collision in the case of (2), we simply need to ensure that the last access of B occurs before A is activated.
For (3), B is activated before A, hence B can override As accesses.
For no collision, all of As accesses cannot happen while B is active.
Thus position (3) follows the “A is never accessed when B is active” rule in a general way.
Local variables of a child function are in the position of (3).
To illustrate, the local B can use the same name as local A and live in a child function or block scope if each time A is accessed, Lua sees A and not B.
So we have to check all accesses of A and see whether they collide with the active period of B.
If A is not accessed during that period, then B can be active with the same name.
The above appears to resolve all sorts of cases where the active times of A and B overlap.
Note that in the above, the allocator does not need to know how locals are separated according to function prototypes.
Perhaps the allocator can be simplified if knowledge of function structure is utilized.
This scheme was implemented in a hurry in 2008 — it could probably be simpler if Lua grammar is considered, but LuaSrcDiet mainly processes various index values in tables.
=== Name Allocation Algorithm
To begin with, the name generator is mostly separate from the name allocation algorithm.
The name generator returns the next shortest name for the algorithm to apply to local variables.
To attempt to reduce symbol entropy (which benefit compression algorithms), the name generator follows English frequent letter usage.
There is also an option to calculate an actual symbol entropy table from the input data.
Since there are 53 one-character identifiers and (53 * 63 - 4) two-character identifiers (minus a few keywords), there isnt a pressing need to optimally maximize name reuse.
The single-file version of LuaSrcDiet 0.12.0, at just over 3000 SLOC and 156 kiB in size, currently allocates around 55 unique local variable names.
In theory, we should need no more than 260 local identifiers by default.
Why?
Since `LUAI_MAXVARS` is 200 and `LUAI_MAXUPVALUES` is 60, at any block scope, there can be at most `(LUAI_MAXVARS + LUAI_MAXUPVALUES)` locals referenced, or 260.
Also, those from outer scopes not referenced in inner scopes can reuse identifiers.
The net effect of this is that a local variable name allocation method should not allocate more than 260 identifier names for locals.
The current algorithm is a simple first-come first-served scheme:
[loweralpha]
. One local object that use the most tokens is named first.
. Any other non-conflicting locals with respect to the first object are assigned the same name.
. Assigned locals are removed from consideration and the procedure is repeated for objects that have not been assigned new names.
. Steps (a) to (c) repeats until no local objects are left.
In addition, there are a few extra issues to take care of:
[loweralpha, start=5]
. Implicit `self` locals that have been flagged as such are already “assigned to” and so they are left unmodified.
. The name generator skips `self` to avoid conflicts.
This is not optimal but it is unlikely a script will use so many local variables as to reach `self`.
. Keywords are also skipped for the name generator.
. Global name conflict resolution.
For (h), global name conflict resolution is handled just after the new name is generated.
The name can still be used for some locals even if it conflicts with other locals.
To remove conflicts, global variable accesses for the particular identifier name is checked.
Any local variables that are active when a global access is made is marked to be skipped.
The rest of the local objects can then use that name.
The algorithm has additional code for handling locals that use the same name in the same scope.
This extends the basic algorithm that was discussed earlier.
For example:
[source, lua]
----
local foo = 10 -- <1>
...
local foo = 20 -- <2>
...
print(e)
----
Since we are considering name visibility, the first `foo` does not really cease to exist when the second `foo` is declared, because if we were to make that assumption, and the first `foo` is removed before (2), then I should be able to use `e` as the name for the first `foo` and after (2), it should not conflict with variables in the outer scope with the same name.
To illustrate:
[source, lua]
----
local e = 10 -- 'foo' renamed to 'e'
...
local t = 20 -- error if we assumed 'e' removed here
...
print(e)
----
Since `e` is a global in the example, we now have an error as the name as been taken over by a local.
Thus, the first `foo` local must have its active time extend to the end of the current scope.
If there is no conflict between the first and second `foo`, the algorithm may still assign the same names to them.
The current fix to deal with the above chains local objects in order to find the removal position.
It may be possible to handle this in a clean manner LuaSrcDiet handles it as a fix to the basic algorithm.
== Ideas
The following is a list of optimization ideas that do not require heavy-duty source code parsing and comprehension.
=== Lexer-Based Optimization Ideas
* Convert long strings to normal strings, vice versa. +
_A little desperate for a few bytes, can be done, but not real keen on implementing it._
* Special number forms to take advantage of constant number folding. +
_For example, 65536 can be represented using 2^16^, and so on.
An expression must be evaluated in the same way, otherwise this seems unsafe._
* Warn if a number has too many digits. +
_Should we warn or “test and truncate”?
Not really an optimization that will see much use._
* Warn of opportunity for using a `local` to zap a bunch of globals. +
_Current recommendation is to use the HTML plugin to display globals in red.
The developer can then visually analyze the source code and make the appropriate fixes.
I think this is better than having the program guess the intentions of the developer._
* Spaces to tabs in comments, long comments, or long strings. +
_For long strings, need to know users intention.
Would rather not implement._
=== Parser-Based Optimization Ideas
Heavy-duty optimizations will need more data to be generated by the parser.
A full AST may eventually be needed.
The most attractive idea that can be quickly implemented with a significant code size “win” is to reduce the number of `local` keywords.
* Remove unused ``local``s that can be removed in the source. +
_Need to consider unused ``local``s in multiple assignments._
* Simplify declaration of ``local``s that can be merged. +
_From:_
+
[source, lua]
----
-- separate locals
local foo
local bar
-- separate locals with assignments
local foo = 123
local bar = "pqr"
----
+
_To:_
+
[source, lua]
----
-- merged locals
local foo,bar
-- merged locals with assignments
local foo,bar=123,"pqr"
----
* Simplify declarations using `nil`. +
_From:_
[source, lua]
local foo, bar = nil, nil
+
_To:_
[source, lua]
local foo,bar
* Simplify ``return``s using `nil`. +
_How desirable is this? From Lua list discussions, it seems to be potentially unsafe unless all return locations are known and checked._
* Removal of optional semicolons in statements and removal of commas or semicolons in table constructors. +
_Yeah, this might save a few bytes._
* Remove table constructor elements using `nil`. +
_Not sure if this is safe to do._
* Simplify logical or relational operator expressions. +
_This is more suitable for an optimizing compiler project._

View File

@@ -0,0 +1,41 @@
-- vim: set ft=lua:
package = 'LuaSrcDiet'
version = '0.3.0-2'
source = { url = 'https://github.com/jirutka/luasrcdiet/archive/v0.3.0/luasrcdiet-0.3.0.tar.gz', md5 = 'c0ff36ef66cd0568c96bc54e9253a8fa' }
description = {
summary = 'Compresses Lua source code by removing unnecessary characters',
detailed = [[
This is revival of LuaSrcDiet originally written by Kein-Hong Man.]],
homepage = 'https://github.com/jirutka/luasrcdiet',
maintainer = 'Jakub Jirutka <jakub@jirutka.cz>',
license = 'MIT',
}
dependencies = {
'lua >= 5.1',
}
build = {
type = 'builtin',
modules = {
['luasrcdiet'] = 'luasrcdiet/init.lua',
['luasrcdiet.equiv'] = 'luasrcdiet/equiv.lua',
['luasrcdiet.fs'] = 'luasrcdiet/fs.lua',
['luasrcdiet.llex'] = 'luasrcdiet/llex.lua',
['luasrcdiet.lparser'] = 'luasrcdiet/lparser.lua',
['luasrcdiet.optlex'] = 'luasrcdiet/optlex.lua',
['luasrcdiet.optparser'] = 'luasrcdiet/optparser.lua',
['luasrcdiet.plugin.example'] = 'luasrcdiet/plugin/example.lua',
['luasrcdiet.plugin.html'] = 'luasrcdiet/plugin/html.lua',
['luasrcdiet.plugin.sloc'] = 'luasrcdiet/plugin/sloc.lua',
['luasrcdiet.utils'] = 'luasrcdiet/utils.lua',
},
install = {
bin = {
luasrcdiet = 'bin/luasrcdiet',
}
}
}

View File

@@ -0,0 +1,28 @@
rock_manifest = {
bin = {
luasrcdiet = "6c318685d57f827cf5baf7037a5d6072"
},
doc = {
["features-and-usage.adoc"] = "157587c27a0c340d9d1dd06af9b339b5",
["performance-stats.adoc"] = "cf5f96a86e021a3a584089fafcabd056",
["tech-notes.adoc"] = "075bc34e667a0055e659e656baa2365a"
},
lua = {
luasrcdiet = {
["equiv.lua"] = "967a6b17573d229e326dbb740ad7fe8c",
["fs.lua"] = "53db7dfc50d026b683fad68ed70ead0f",
["init.lua"] = "c6f368e6cf311f3257067fed0fbcd06a",
["llex.lua"] = "ede897af261fc362a82d87fbad91ea2b",
["lparser.lua"] = "c1e1f04d412b79a040fd1c2b74112953",
["optlex.lua"] = "7c986da991a338494c36770b4a30fa9f",
["optparser.lua"] = "b125a271ac1c691dec68b63019b1b5da",
plugin = {
["example.lua"] = "86b5c1e9dc7959db6b221d6d5a0db3d1",
["html.lua"] = "c0d3336a133f0c8663f395ee98d54f6a",
["sloc.lua"] = "fb1a91b18b701ab83f21c87733be470a"
},
["utils.lua"] = "bd6c1e85c6a9bf3383d336a4797fb292"
}
},
["luasrcdiet-0.3.0-2.rockspec"] = "da70047e1b0cbdc1ff08d060327fa110"
}

View File

@@ -0,0 +1,270 @@
return [[html {
color: #000;
background: #FFF;
}
body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td {
margin: 0;
padding: 0;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
fieldset,img {
border: 0;
}
address,caption,cite,code,dfn,em,strong,th,var,optgroup {
font-style: inherit;
font-weight: inherit;
}
del,ins {
text-decoration: none;
}
li {
list-style: bullet;
margin-left: 20px;
}
caption,th {
text-align: left;
}
h1,h2,h3,h4,h5,h6 {
font-size: 100%;
font-weight: bold;
}
q:before,q:after {
content: '';
}
abbr,acronym {
border: 0;
font-variant: normal;
}
sup {
vertical-align: baseline;
}
sub {
vertical-align: baseline;
}
legend {
color: #000;
}
input,button,textarea,select,optgroup,option {
font-family: inherit;
font-size: inherit;
font-style: inherit;
font-weight: inherit;
}
input,button,textarea,select {*font-size:100%;
}
/* END RESET */
body {
margin-left: 1em;
margin-right: 1em;
font-family: arial, helvetica, geneva, sans-serif;
background-color: #ffffff; margin: 0px;
}
code, tt { font-family: monospace; }
body, p, td, th { font-size: .95em; line-height: 1.2em;}
p, ul { margin: 10px 0 0 10px;}
strong { font-weight: bold;}
em { font-style: italic;}
h1 {
font-size: 1.5em;
margin: 25px 0 20px 0;
}
h2, h3, h4 { margin: 15px 0 10px 0; }
h2 { font-size: 1.25em; }
h3 { font-size: 1.15em; }
h4 { font-size: 1.06em; }
a:link { font-weight: bold; color: #004080; text-decoration: none; }
a:visited { font-weight: bold; color: #006699; text-decoration: none; }
a:link:hover { text-decoration: underline; }
hr {
color:#cccccc;
background: #00007f;
height: 1px;
}
blockquote { margin-left: 3em; }
ul { list-style-type: disc; }
p.name {
font-family: "Andale Mono", monospace;
padding-top: 1em;
}
p:first-child {
margin-top: 0px;
}
pre.example {
background-color: rgb(245, 245, 245);
border: 1px solid silver;
padding: 10px;
margin: 10px 0 10px 0;
font-family: "Andale Mono", monospace;
font-size: .85em;
}
pre {
background-color: rgb(245, 245, 245);
border: 1px solid silver;
padding: 10px;
margin: 10px 0 10px 0;
font-family: "Andale Mono", monospace;
}
table.index { border: 1px #00007f; }
table.index td { text-align: left; vertical-align: top; }
#container {
margin-left: 1em;
margin-right: 1em;
background-color: #f0f0f0;
}
#product {
text-align: center;
border-bottom: 1px solid #cccccc;
background-color: #ffffff;
}
#product big {
font-size: 2em;
}
#main {
background-color: #f0f0f0;
border-left: 2px solid #cccccc;
}
#navigation {
float: left;
width: 18em;
vertical-align: top;
background-color: #f0f0f0;
overflow: scroll;
position: fixed;
height:100%;
}
#navigation h2 {
background-color:#e7e7e7;
font-size:1.1em;
color:#000000;
text-align: left;
padding:0.2em;
border-top:1px solid #dddddd;
border-bottom:1px solid #dddddd;
}
#navigation ul
{
font-size:1em;
list-style-type: none;
margin: 1px 1px 10px 1px;
}
#navigation li {
text-indent: -1em;
display: block;
margin: 3px 0px 0px 22px;
}
#navigation li li a {
margin: 0px 3px 0px -1em;
}
#content {
margin-left: 18em;
padding: 1em;
border-left: 2px solid #cccccc;
border-right: 2px solid #cccccc;
background-color: #ffffff;
}
#about {
clear: both;
padding: 5px;
border-top: 2px solid #cccccc;
background-color: #ffffff;
}
@media print {
body {
font: 12pt "Times New Roman", "TimeNR", Times, serif;
}
a { font-weight: bold; color: #004080; text-decoration: underline; }
#main {
background-color: #ffffff;
border-left: 0px;
}
#container {
margin-left: 2%;
margin-right: 2%;
background-color: #ffffff;
}
#content {
padding: 1em;
background-color: #ffffff;
}
#navigation {
display: none;
}
pre.example {
font-family: "Andale Mono", monospace;
font-size: 10pt;
page-break-inside: avoid;
}
}
table.module_list {
border-width: 1px;
border-style: solid;
border-color: #cccccc;
border-collapse: collapse;
}
table.module_list td {
border-width: 1px;
padding: 3px;
border-style: solid;
border-color: #cccccc;
}
table.module_list td.name { background-color: #f0f0f0; }
table.module_list td.summary { width: 100%; }
table.function_list {
border-width: 1px;
border-style: solid;
border-color: #cccccc;
border-collapse: collapse;
}
table.function_list td {
border-width: 1px;
padding: 3px;
border-style: solid;
border-color: #cccccc;
}
table.function_list td.name { background-color: #f0f0f0; }
table.function_list td.summary { width: 100%; }
dl.table dt, dl.function dt {border-top: 1px solid #ccc; padding-top: 1em;}
dl.table dd, dl.function dd {padding-bottom: 1em; margin: 10px 0 0 20px;}
dl.table h3, dl.function h3 {font-size: .95em;}
]]

View File

@@ -0,0 +1,87 @@
--------------------------------------------------------------------------------
-- Copyright (c) 2012-2014 Sierra Wireless.
-- All rights reserved. This program and the accompanying materials
-- are made available under the terms of the Eclipse Public License v1.0
-- which accompanies this distribution, and is available at
-- http://www.eclipse.org/legal/epl-v10.html
--
-- Contributors:
-- Kevin KIN-FOO <kkinfoo@sierrawireless.com>
-- - initial API and implementation and initial documentation
--------------------------------------------------------------------------------
--
-- Load documentation generator and update its path
--
local templateengine = require 'templateengine'
for name, def in pairs( require 'template.utils' ) do
templateengine.env [ name ] = def
end
-- Load documentation extractor and set handled languages
local lddextractor = require 'lddextractor'
local M = {}
M.defaultsitemainpagename = 'index'
function M.generatedocforfiles(filenames, cssname,noheuristic)
if not filenames then return nil, 'No files provided.' end
--
-- Generate API model elements for all files
--
local generatedfiles = {}
local wrongfiles = {}
for _, filename in pairs( filenames ) do
-- Load file content
local file, error = io.open(filename, 'r')
if not file then return nil, 'Unable to read "'..filename..'"\n'..err end
local code = file:read('*all')
file:close()
-- Get module for current file
local apimodule, err = lddextractor.generateapimodule(filename, code,noheuristic)
-- Handle modules with module name
if apimodule and apimodule.name then
generatedfiles[ apimodule.name ] = apimodule
elseif not apimodule then
-- Track faulty files
table.insert(wrongfiles, 'Unable to extract comments from "'..filename..'".\n'..err)
elseif not apimodule.name then
-- Do not generate documentation for unnamed modules
table.insert(wrongfiles, 'Unable to create documentation for "'..filename..'", no module name provided.')
end
end
--
-- Defining index, which will summarize all modules
--
local index = {
modules = generatedfiles,
name = M.defaultsitemainpagename,
tag='index'
}
generatedfiles[ M.defaultsitemainpagename ] = index
--
-- Define page cursor
--
local page = {
currentmodule = nil,
headers = { [[<link rel="stylesheet" href="]].. cssname ..[[" type="text/css"/>]] },
modules = generatedfiles,
tag = 'page'
}
--
-- Iterate over modules, generating complete doc pages
--
for _, module in pairs( generatedfiles ) do
-- Update current cursor page
page.currentmodule = module
-- Generate page
local content, error = templateengine.applytemplate(page)
if not content then return nil, error end
module.body = content
end
return generatedfiles, wrongfiles
end
return M

View File

@@ -0,0 +1,102 @@
--------------------------------------------------------------------------------
-- Copyright (c) 2012-2014 Sierra Wireless.
-- All rights reserved. This program and the accompanying materials
-- are made available under the terms of the Eclipse Public License v1.0
-- which accompanies this distribution, and is available at
-- http://www.eclipse.org/legal/epl-v10.html
--
-- Contributors:
-- Kevin KIN-FOO <kkinfoo@sierrawireless.com>
-- - initial API and implementation and initial documentation
--------------------------------------------------------------------------------
local M = {}
require 'metalua.loader'
local compiler = require 'metalua.compiler'
local mlc = compiler.new()
local Q = require 'metalua.treequery'
-- Enable to retrieve all Javadoc-like comments from C code
function M.c(code)
if not code then return nil, 'No code provided' end
local comments = {}
-- Loop over comments stripping cosmetic '*'
for comment in code:gmatch('%s*/%*%*+(.-)%*+/') do
-- All Lua special comment are prefixed with an '-',
-- so we also comment C comment to make them compliant
table.insert(comments, '-'..comment)
end
return comments
end
-- Enable to retrieve "---" comments from Lua code
function M.lua( code )
if not code then return nil, 'No code provided' end
-- manage shebang
if code then code = code:gsub("^(#.-\n)", function (s) return string.rep(' ',string.len(s)) end) end
-- check for errors
local f, err = loadstring(code,'source_to_check')
if not f then
return nil, 'Syntax error.\n' .. err
end
-- Get ast from file
local status, ast = pcall(mlc.src_to_ast, mlc, code)
--
-- Detect parsing errors
--
if not status then
return nil, 'There might be a syntax error.\n' .. ast
end
--
-- Extract commented nodes from AST
--
-- Function enabling commented node selection
local function acceptcommentednode(node)
return node.lineinfo and ( node.lineinfo.last.comments or node.lineinfo.first.comments )
end
-- Fetch commented node from AST
local commentednodes = Q(ast):filter( acceptcommentednode ):list()
-- Comment cache to avoid selecting same comment twice
local commentcache = {}
-- Will contain selected comments
local comments = {}
-- Loop over commented nodes
for _, node in ipairs( commentednodes ) do
-- A node can is relateds to comment before and after itself,
-- the following gathers them.
local commentlists = {}
if node.lineinfo and node.lineinfo.first.comments then
table.insert(commentlists, node.lineinfo.first.comments)
end
if node.lineinfo and node.lineinfo.last.comments then
table.insert(commentlists, node.lineinfo.last.comments)
end
-- Now that we have comments before and fater the node,
-- collect them in a single table
for _, list in ipairs( commentlists ) do
for _, commenttable in ipairs(list) do
-- Only select special comments
local firstcomment = #commenttable > 0 and #commenttable[1] > 0 and commenttable[1]
if firstcomment:sub(1, 1) == '-' then
for _, comment in ipairs( commenttable ) do
-- Only comments which were not already collected
if not commentcache[comment] then
commentcache[comment] = true
table.insert(comments, comment)
end
end
end
end
end
end
return comments
end
return M

View File

@@ -0,0 +1,130 @@
--------------------------------------------------------------------------------
-- Copyright (c) 2012-2014 Sierra Wireless.
-- All rights reserved. This program and the accompanying materials
-- are made available under the terms of the Eclipse Public License v1.0
-- which accompanies this distribution, and is available at
-- http://www.eclipse.org/legal/epl-v10.html
--
-- Contributors:
-- Kevin KIN-FOO <kkinfoo@sierrawireless.com>
-- - initial API and implementation and initial documentation
--------------------------------------------------------------------------------
local lfs = require 'lfs'
local M = {}
local function iswindows()
local p = io.popen("echo %os%")
if not p then
return false
end
local result =p:read("*l")
p:close()
return result == "Windows_NT"
end
M.separator = iswindows() and [[\]] or [[/]]
---
-- Will recursively browse given directories and list files encountered
-- @param tab Table, list where files will be added
-- @param dirorfiles list of path to browse in order to build list.
-- Files from this list will be added to <code>tab</code> list.
-- @return <code>tab</code> list, table containing all files from directories
-- and files contained in <code>dirorfile</code>
local function appendfiles(tab, dirorfile)
-- Nothing to process
if #dirorfile < 1 then return tab end
-- Append all files to list
local dirs = {}
for _, path in ipairs( dirorfile ) do
-- Determine element nature
local elementnature = lfs.attributes (path, "mode")
-- Handle files
if elementnature == 'file' then
table.insert(tab, path)
else if elementnature == 'directory' then
-- Check if folder is accessible
local status, error = pcall(lfs.dir, path)
if not status then return nil, error end
--
-- Handle folders
--
for diskelement in lfs.dir(path) do
-- Format current file name
local currentfilename
if path:sub(#path) == M.separator then
currentfilename = path .. diskelement
else
currentfilename = path .. M.separator .. diskelement
end
-- Handle folder elements
local nature, err = lfs.attributes (currentfilename, "mode")
-- Append file to current list
if nature == 'file' then
table.insert(tab, currentfilename)
elseif nature == 'directory' then
-- Avoid current and parent directory in order to avoid
-- endless recursion
if diskelement ~= '.' and diskelement ~= '..' then
-- Handle subfolders
table.insert(dirs, currentfilename)
end
end
end
end
end
end
-- If we only encountered files, going deeper is useless
if #dirs == 0 then return tab end
-- Append files from encountered directories
return appendfiles(tab, dirs)
end
---
-- Provide a list of files from a directory
-- @param list Table of directories to browse
-- @return table of string, path to files contained in given directories
function M.filelist(list)
if not list then return nil, 'No directory list provided' end
return appendfiles({}, list)
end
function M.checkdirectory( dirlist )
if not dirlist then return false end
local missingdirs = {}
for _, filename in ipairs( dirlist ) do
if not lfs.attributes(filename, 'mode') then
table.insert(missingdirs, filename)
end
end
if #missingdirs > 0 then
return false, missingdirs
end
return true
end
function M.fill(filename, content)
--
-- Ensure parent directory exists
--
local parent = filename:gmatch([[(.*)]] .. M.separator ..[[(.+)]])()
local parentnature = lfs.attributes(parent, 'mode')
-- Create parent directory while absent
if not parentnature then
lfs.mkdir( parent )
elseif parentnature ~= 'directory' then
-- Notify that disk element already exists
return nil, parent..' is a '..parentnature..'.'
end
-- Create actual file
local file, error = io.open(filename, 'w')
if not file then
return nil, error
end
file:write( content )
file:close()
return true
end
return M

View File

@@ -0,0 +1,113 @@
--------------------------------------------------------------------------------
-- Copyright (c) 2012-2014 Sierra Wireless.
-- All rights reserved. This program and the accompanying materials
-- are made available under the terms of the Eclipse Public License v1.0
-- which accompanies this distribution, and is available at
-- http://www.eclipse.org/legal/epl-v10.html
--
-- Contributors:
-- Kevin KIN-FOO <kkinfoo@sierrawireless.com>
-- - initial API and implementation and initial documentation
--------------------------------------------------------------------------------
require 'metalua.loader'
local compiler = require 'metalua.compiler'
local mlc = compiler.new()
local M = {}
--
-- Define default supported languages
--
M.supportedlanguages = {}
local extractors = require 'extractors'
-- Support Lua comment extracting
M.supportedlanguages['lua'] = extractors.lua
-- Support C comment extracting
for _,c in ipairs({'c', 'cpp', 'c++'}) do
M.supportedlanguages[c] = extractors.c
end
-- Extract comment from code,
-- type of code is deduced from filename extension
function M.extract(filename, code)
-- Check parameters
if not code then return nil, 'No code provided' end
if type(filename) ~= "string" then
return nil, 'No string for file name provided'
end
-- Extract file extension
local fileextension = filename:gmatch('.*%.(.*)')()
if not fileextension then
return nil, 'File '..filename..' has no extension, could not determine how to extract documentation.'
end
-- Check if it is possible to extract documentation from these files
local extractor = M.supportedlanguages[ fileextension ]
if not extractor then
return nil, 'Unable to extract documentation from '.. fileextension .. ' file.'
end
return extractor( code )
end
-- Generate a file gathering only comments from given code
function M.generatecommentfile(filename, code)
local comments, error = M.extract(filename, code)
if not comments then
return nil, 'Unable to generate comment file.\n'..error
end
local filecontent = {}
for _, comment in ipairs( comments ) do
table.insert(filecontent, "--[[")
table.insert(filecontent, comment)
table.insert(filecontent, "\n]]\n\n")
end
return table.concat(filecontent)..'return nil\n'
end
-- Create API Model module from a 'comment only' lua file
function M.generateapimodule(filename, code,noheuristic)
if not filename then return nil, 'No file name given.' end
if not code then return nil, 'No code provided.' end
if type(filename) ~= "string" then return nil, 'No string for file name provided' end
-- for non lua file get comment file
if filename:gmatch('.*%.(.*)')() ~= 'lua' then
local err
code, err = M.generatecommentfile(filename, code)
if not code then
return nil, 'Unable to create api module for "'..filename..'".\n'..err
end
else
-- manage shebang
if code then code = code:gsub("^(#.-\n)", function (s) return string.rep(' ',string.len(s)) end) end
-- check for errors
local f, err = loadstring(code,'source_to_check')
if not f then
return nil, 'File'..filename..'contains syntax error.\n' .. err
end
end
local status, ast = pcall(mlc.src_to_ast, mlc, code)
if not status then
return nil, 'Unable to compute ast for "'..filename..'".\n'..ast
end
-- Extract module name as the filename without extension
local modulename
local matcher = string.gmatch(filename,'.*/(.*)%..*$')
if matcher then modulename = matcher() end
-- Create api model
local apimodelbuilder = require 'models.apimodelbuilder'
local _file, comment2apiobj = apimodelbuilder.createmoduleapi(ast, modulename)
-- Create internal model
if not noheuristic then
local internalmodelbuilder = require "models.internalmodelbuilder"
local _internalcontent = internalmodelbuilder.createinternalcontent(ast,_file,comment2apiobj, modulename)
end
return _file
end
return M

View File

@@ -0,0 +1,465 @@
---------
-- Source and binary equivalency comparisons
--
-- **Notes:**
--
-- * Intended as an extra safety check for mission-critical code,
-- should give affirmative results if everything works.
-- * Heavy on load() and string.dump(), which may be slowish,
-- and may cause problems for cross-compiled applications.
-- * Optional detailed information dump is mainly for debugging,
-- reason being, if the two are not equivalent when they should be,
-- then some form of optimization has failed.
-- * source: IMPORTANT: TK_NAME not compared if opt-locals enabled.
-- * binary: IMPORTANT: Some shortcuts are taken with int and size_t
-- value reading -- if the functions break, then the binary chunk
-- is very large indeed.
-- * binary: There is a lack of diagnostic information when a compare
-- fails; you can use ChunkSpy and compare using visual diff.
----
local byte = string.byte
local dump = string.dump
local load = loadstring or load --luacheck: ignore 113
local sub = string.sub
local M = {}
local is_realtoken = { -- significant (grammar) tokens
TK_KEYWORD = true,
TK_NAME = true,
TK_NUMBER = true,
TK_STRING = true,
TK_LSTRING = true,
TK_OP = true,
TK_EOS = true,
}
local option, llex, warn
--- The initialization function.
--
-- @tparam {[string]=bool,...} _option
-- @tparam luasrcdiet.llex _llex
-- @tparam table _warn
function M.init(_option, _llex, _warn)
option = _option
llex = _llex
warn = _warn
end
--- Builds lists containing a 'normal' lexer stream.
--
-- @tparam string s The source code.
-- @treturn table
-- @treturn table
local function build_stream(s)
local stok, sseminfo = llex.lex(s) -- source list (with whitespace elements)
local tok, seminfo -- processed list (real elements only)
= {}, {}
for i = 1, #stok do
local t = stok[i]
if is_realtoken[t] then
tok[#tok + 1] = t
seminfo[#seminfo + 1] = sseminfo[i]
end
end--for
return tok, seminfo
end
-- Tests source (lexer stream) equivalence.
--
-- @tparam string z
-- @tparam string dat
function M.source(z, dat)
-- Returns a dumped string for seminfo compares.
local function dumpsem(s)
local sf = load("return "..s, "z")
if sf then
return dump(sf)
end
end
-- Marks and optionally reports non-equivalence.
local function bork(msg)
if option.DETAILS then print("SRCEQUIV: "..msg) end
warn.SRC_EQUIV = true
end
-- Get lexer streams for both source strings, compare.
local tok1, seminfo1 = build_stream(z) -- original
local tok2, seminfo2 = build_stream(dat) -- compressed
-- Compare shbang lines ignoring EOL.
local sh1 = z:match("^(#[^\r\n]*)")
local sh2 = dat:match("^(#[^\r\n]*)")
if sh1 or sh2 then
if not sh1 or not sh2 or sh1 ~= sh2 then
bork("shbang lines different")
end
end
-- Compare by simple count.
if #tok1 ~= #tok2 then
bork("count "..#tok1.." "..#tok2)
return
end
-- Compare each element the best we can.
for i = 1, #tok1 do
local t1, t2 = tok1[i], tok2[i]
local s1, s2 = seminfo1[i], seminfo2[i]
if t1 ~= t2 then -- by type
bork("type ["..i.."] "..t1.." "..t2)
break
end
if t1 == "TK_KEYWORD" or t1 == "TK_NAME" or t1 == "TK_OP" then
if t1 == "TK_NAME" and option["opt-locals"] then
-- can't compare identifiers of locals that are optimized
elseif s1 ~= s2 then -- by semantic info (simple)
bork("seminfo ["..i.."] "..t1.." "..s1.." "..s2)
break
end
elseif t1 == "TK_EOS" then
-- no seminfo to compare
else-- "TK_NUMBER" or "TK_STRING" or "TK_LSTRING"
-- compare 'binary' form, so dump a function
local s1b,s2b = dumpsem(s1), dumpsem(s2)
if not s1b or not s2b or s1b ~= s2b then
bork("seminfo ["..i.."] "..t1.." "..s1.." "..s2)
break
end
end
end--for
-- Successful comparison if end is reached with no borks.
end
--- Tests binary chunk equivalence (only for PUC Lua 5.1).
--
-- @tparam string z
-- @tparam string dat
function M.binary(z, dat)
local TNIL = 0 --luacheck: ignore 211
local TBOOLEAN = 1
local TNUMBER = 3
local TSTRING = 4
-- sizes of data types
local endian
local sz_int
local sz_sizet
local sz_inst
local sz_number
local getint
local getsizet
-- Marks and optionally reports non-equivalence.
local function bork(msg)
if option.DETAILS then print("BINEQUIV: "..msg) end
warn.BIN_EQUIV = true
end
-- Checks if bytes exist.
local function ensure(c, sz)
if c.i + sz - 1 > c.len then return end
return true
end
-- Skips some bytes.
local function skip(c, sz)
if not sz then sz = 1 end
c.i = c.i + sz
end
-- Returns a byte value.
local function getbyte(c)
local i = c.i
if i > c.len then return end
local d = sub(c.dat, i, i)
c.i = i + 1
return byte(d)
end
-- Return an int value (little-endian).
local function getint_l(c)
local n, scale = 0, 1
if not ensure(c, sz_int) then return end
for _ = 1, sz_int do
n = n + scale * getbyte(c)
scale = scale * 256
end
return n
end
-- Returns an int value (big-endian).
local function getint_b(c)
local n = 0
if not ensure(c, sz_int) then return end
for _ = 1, sz_int do
n = n * 256 + getbyte(c)
end
return n
end
-- Returns a size_t value (little-endian).
local function getsizet_l(c)
local n, scale = 0, 1
if not ensure(c, sz_sizet) then return end
for _ = 1, sz_sizet do
n = n + scale * getbyte(c)
scale = scale * 256
end
return n
end
-- Returns a size_t value (big-endian).
local function getsizet_b(c)
local n = 0
if not ensure(c, sz_sizet) then return end
for _ = 1, sz_sizet do
n = n * 256 + getbyte(c)
end
return n
end
-- Returns a block (as a string).
local function getblock(c, sz)
local i = c.i
local j = i + sz - 1
if j > c.len then return end
local d = sub(c.dat, i, j)
c.i = i + sz
return d
end
-- Returns a string.
local function getstring(c)
local n = getsizet(c)
if not n then return end
if n == 0 then return "" end
return getblock(c, n)
end
-- Compares byte value.
local function goodbyte(c1, c2)
local b1, b2 = getbyte(c1), getbyte(c2)
if not b1 or not b2 or b1 ~= b2 then
return
end
return b1
end
-- Compares byte value.
local function badbyte(c1, c2)
local b = goodbyte(c1, c2)
if not b then return true end
end
-- Compares int value.
local function goodint(c1, c2)
local i1, i2 = getint(c1), getint(c2)
if not i1 or not i2 or i1 ~= i2 then
return
end
return i1
end
-- Recursively-called function to compare function prototypes.
local function getfunc(c1, c2)
-- source name (ignored)
if not getstring(c1) or not getstring(c2) then
bork("bad source name"); return
end
-- linedefined (ignored)
if not getint(c1) or not getint(c2) then
bork("bad linedefined"); return
end
-- lastlinedefined (ignored)
if not getint(c1) or not getint(c2) then
bork("bad lastlinedefined"); return
end
if not (ensure(c1, 4) and ensure(c2, 4)) then
bork("prototype header broken")
end
-- nups (compared)
if badbyte(c1, c2) then
bork("bad nups"); return
end
-- numparams (compared)
if badbyte(c1, c2) then
bork("bad numparams"); return
end
-- is_vararg (compared)
if badbyte(c1, c2) then
bork("bad is_vararg"); return
end
-- maxstacksize (compared)
if badbyte(c1, c2) then
bork("bad maxstacksize"); return
end
-- code (compared)
local ncode = goodint(c1, c2)
if not ncode then
bork("bad ncode"); return
end
local code1 = getblock(c1, ncode * sz_inst)
local code2 = getblock(c2, ncode * sz_inst)
if not code1 or not code2 or code1 ~= code2 then
bork("bad code block"); return
end
-- constants (compared)
local nconst = goodint(c1, c2)
if not nconst then
bork("bad nconst"); return
end
for _ = 1, nconst do
local ctype = goodbyte(c1, c2)
if not ctype then
bork("bad const type"); return
end
if ctype == TBOOLEAN then
if badbyte(c1, c2) then
bork("bad boolean value"); return
end
elseif ctype == TNUMBER then
local num1 = getblock(c1, sz_number)
local num2 = getblock(c2, sz_number)
if not num1 or not num2 or num1 ~= num2 then
bork("bad number value"); return
end
elseif ctype == TSTRING then
local str1 = getstring(c1)
local str2 = getstring(c2)
if not str1 or not str2 or str1 ~= str2 then
bork("bad string value"); return
end
end
end
-- prototypes (compared recursively)
local nproto = goodint(c1, c2)
if not nproto then
bork("bad nproto"); return
end
for _ = 1, nproto do
if not getfunc(c1, c2) then
bork("bad function prototype"); return
end
end
-- debug information (ignored)
-- lineinfo (ignored)
local sizelineinfo1 = getint(c1)
if not sizelineinfo1 then
bork("bad sizelineinfo1"); return
end
local sizelineinfo2 = getint(c2)
if not sizelineinfo2 then
bork("bad sizelineinfo2"); return
end
if not getblock(c1, sizelineinfo1 * sz_int) then
bork("bad lineinfo1"); return
end
if not getblock(c2, sizelineinfo2 * sz_int) then
bork("bad lineinfo2"); return
end
-- locvars (ignored)
local sizelocvars1 = getint(c1)
if not sizelocvars1 then
bork("bad sizelocvars1"); return
end
local sizelocvars2 = getint(c2)
if not sizelocvars2 then
bork("bad sizelocvars2"); return
end
for _ = 1, sizelocvars1 do
if not getstring(c1) or not getint(c1) or not getint(c1) then
bork("bad locvars1"); return
end
end
for _ = 1, sizelocvars2 do
if not getstring(c2) or not getint(c2) or not getint(c2) then
bork("bad locvars2"); return
end
end
-- upvalues (ignored)
local sizeupvalues1 = getint(c1)
if not sizeupvalues1 then
bork("bad sizeupvalues1"); return
end
local sizeupvalues2 = getint(c2)
if not sizeupvalues2 then
bork("bad sizeupvalues2"); return
end
for _ = 1, sizeupvalues1 do
if not getstring(c1) then bork("bad upvalues1"); return end
end
for _ = 1, sizeupvalues2 do
if not getstring(c2) then bork("bad upvalues2"); return end
end
return true
end
-- Removes shbang line so that load runs.
local function zap_shbang(s)
local shbang = s:match("^(#[^\r\n]*\r?\n?)")
if shbang then -- cut out shbang
s = sub(s, #shbang + 1)
end
return s
end
-- Attempt to compile, then dump to get binary chunk string.
local cz = load(zap_shbang(z), "z")
if not cz then
bork("failed to compile original sources for binary chunk comparison")
return
end
local cdat = load(zap_shbang(dat), "z")
if not cdat then
bork("failed to compile compressed result for binary chunk comparison")
end
-- if load() works, dump assuming string.dump() is error-free
local c1 = { i = 1, dat = dump(cz) }
c1.len = #c1.dat
local c2 = { i = 1, dat = dump(cdat) }
c2.len = #c2.dat
-- Parse binary chunks to verify equivalence.
-- * For headers, handle sizes to allow a degree of flexibility.
-- * Assume a valid binary chunk is generated, since it was not
-- generated via external means.
if not (ensure(c1, 12) and ensure(c2, 12)) then
bork("header broken")
end
skip(c1, 6) -- skip signature(4), version, format
endian = getbyte(c1) -- 1 = little endian
sz_int = getbyte(c1) -- get data type sizes
sz_sizet = getbyte(c1)
sz_inst = getbyte(c1)
sz_number = getbyte(c1)
skip(c1) -- skip integral flag
skip(c2, 12) -- skip other header (assume similar)
if endian == 1 then -- set for endian sensitive data we need
getint = getint_l
getsizet = getsizet_l
else
getint = getint_b
getsizet = getsizet_b
end
getfunc(c1, c2) -- get prototype at root
if c1.i ~= c1.len + 1 then
bork("inconsistent binary chunk1"); return
elseif c2.i ~= c2.len + 1 then
bork("inconsistent binary chunk2"); return
end
-- Successful comparison if end is reached with no borks.
end
return M

View File

@@ -0,0 +1,74 @@
---------
-- Utility functions for operations on a file system.
--
-- **Note: This module is not part of public API!**
----
local fmt = string.format
local open = io.open
local UTF8_BOM = '\239\187\191'
local function normalize_io_error (name, err)
if err:sub(1, #name + 2) == name..': ' then
err = err:sub(#name + 3)
end
return err
end
local M = {}
--- Reads the specified file and returns its content as string.
--
-- @tparam string filename Path of the file to read.
-- @tparam string mode The mode in which to open the file, see @{io.open} (default: "r").
-- @treturn[1] string A content of the file.
-- @treturn[2] nil
-- @treturn[2] string An error message.
function M.read_file (filename, mode)
local handler, err = open(filename, mode or 'r')
if not handler then
return nil, fmt('Could not open %s for reading: %s',
filename, normalize_io_error(filename, err))
end
local content, err = handler:read('*a') --luacheck: ignore 411
if not content then
return nil, fmt('Could not read %s: %s', filename, normalize_io_error(filename, err))
end
handler:close()
if content:sub(1, #UTF8_BOM) == UTF8_BOM then
content = content:sub(#UTF8_BOM + 1)
end
return content
end
--- Writes the given data to the specified file.
--
-- @tparam string filename Path of the file to write.
-- @tparam string data The data to write.
-- @tparam ?string mode The mode in which to open the file, see @{io.open} (default: "w").
-- @treturn[1] true
-- @treturn[2] nil
-- @treturn[2] string An error message.
function M.write_file (filename, data, mode)
local handler, err = open(filename, mode or 'w')
if not handler then
return nil, fmt('Could not open %s for writing: %s',
filename, normalize_io_error(filename, err))
end
local _, err = handler:write(data) --luacheck: ignore 411
if err then
return nil, fmt('Could not write %s: %s', filename, normalize_io_error(filename, err))
end
handler:flush()
handler:close()
return true
end
return M

View File

@@ -0,0 +1,117 @@
---------
-- LuaSrcDiet API
----
local equiv = require 'luasrcdiet.equiv'
local llex = require 'luasrcdiet.llex'
local lparser = require 'luasrcdiet.lparser'
local optlex = require 'luasrcdiet.optlex'
local optparser = require 'luasrcdiet.optparser'
local utils = require 'luasrcdiet.utils'
local concat = table.concat
local merge = utils.merge
local _ -- placeholder
local function noop ()
return
end
local function opts_to_legacy (opts)
local res = {}
for key, val in pairs(opts) do
res['opt-'..key] = val
end
return res
end
local M = {}
--- The module's name.
M._NAME = 'luasrcdiet'
--- The module's version number.
M._VERSION = '0.3.0'
--- The module's homepage.
M._HOMEPAGE = 'https://github.com/jirutka/luasrcdiet'
--- All optimizations disabled.
M.NONE_OPTS = {
binequiv = false,
comments = false,
emptylines = false,
entropy = false,
eols = false,
experimental = false,
locals = false,
numbers = false,
srcequiv = false,
strings = false,
whitespace = false,
}
--- Basic optimizations enabled.
-- @table BASIC_OPTS
M.BASIC_OPTS = merge(M.NONE_OPTS, {
comments = true,
emptylines = true,
srcequiv = true,
whitespace = true,
})
--- Defaults.
-- @table DEFAULT_OPTS
M.DEFAULT_OPTS = merge(M.BASIC_OPTS, {
locals = true,
numbers = true,
})
--- Maximum optimizations enabled (all except experimental).
-- @table MAXIMUM_OPTS
M.MAXIMUM_OPTS = merge(M.DEFAULT_OPTS, {
entropy = true,
eols = true,
strings = true,
})
--- Optimizes the given Lua source code.
--
-- @tparam ?{[string]=bool,...} opts Optimizations to do (default is @{DEFAULT_OPTS}).
-- @tparam string source The Lua source code to optimize.
-- @treturn string Optimized source.
-- @raise if the source is malformed, source equivalence test failed, or some
-- other error occured.
function M.optimize (opts, source)
assert(source and type(source) == 'string',
'bad argument #2: expected string, got a '..type(source))
opts = opts and merge(M.NONE_OPTS, opts) or M.DEFAULT_OPTS
local legacy_opts = opts_to_legacy(opts)
local toklist, seminfolist, toklnlist = llex.lex(source)
local xinfo = lparser.parse(toklist, seminfolist, toklnlist)
optparser.print = noop
optparser.optimize(legacy_opts, toklist, seminfolist, xinfo)
local warn = optlex.warn -- use this as a general warning lookup
optlex.print = noop
_, seminfolist = optlex.optimize(legacy_opts, toklist, seminfolist, toklnlist)
local optim_source = concat(seminfolist)
if opts.srcequiv and not opts.experimental then
equiv.init(legacy_opts, llex, warn)
equiv.source(source, optim_source)
if warn.SRC_EQUIV then
error('Source equivalence test failed!')
end
end
return optim_source
end
return M

View File

@@ -0,0 +1,350 @@
---------
-- Lua 5.1+ lexical analyzer written in Lua.
--
-- This file is part of LuaSrcDiet, based on Yueliang material.
--
-- **Notes:**
--
-- * This is a version of the native 5.1.x lexer from Yueliang 0.4.0,
-- with significant modifications to handle LuaSrcDiet's needs:
-- (1) llex.error is an optional error function handler,
-- (2) seminfo for strings include their delimiters and no
-- translation operations are performed on them.
-- * ADDED shbang handling has been added to support executable scripts.
-- * NO localized decimal point replacement magic.
-- * NO limit to number of lines.
-- * NO support for compatible long strings (LUA\_COMPAT_LSTR).
-- * Added goto keyword and double-colon operator (Lua 5.2+).
----
local find = string.find
local fmt = string.format
local match = string.match
local sub = string.sub
local tonumber = tonumber
local M = {}
local kw = {}
for v in ([[
and break do else elseif end false for function goto if in
local nil not or repeat return then true until while]]):gmatch("%S+") do
kw[v] = true
end
local z, -- source stream
sourceid, -- name of source
I, -- position of lexer
buff, -- buffer for strings
ln, -- line number
tok, -- lexed token list
seminfo, -- lexed semantic information list
tokln -- line numbers for messages
--- Adds information to token listing.
--
-- @tparam string token
-- @tparam string info
local function addtoken(token, info)
local i = #tok + 1
tok[i] = token
seminfo[i] = info
tokln[i] = ln
end
--- Handles line number incrementation and end-of-line characters.
--
-- @tparam int i Position of lexer in the source stream.
-- @tparam bool is_tok
-- @treturn int
local function inclinenumber(i, is_tok)
local old = sub(z, i, i)
i = i + 1 -- skip '\n' or '\r'
local c = sub(z, i, i)
if (c == "\n" or c == "\r") and (c ~= old) then
i = i + 1 -- skip '\n\r' or '\r\n'
old = old..c
end
if is_tok then addtoken("TK_EOL", old) end
ln = ln + 1
I = i
return i
end
--- Returns a chunk name or id, no truncation for long names.
--
-- @treturn string
local function chunkid()
if sourceid and match(sourceid, "^[=@]") then
return sub(sourceid, 2) -- remove first char
end
return "[string]"
end
--- Formats error message and throws error.
--
-- A simplified version, does not report what token was responsible.
--
-- @tparam string s
-- @tparam int line The line number.
-- @raise
local function errorline(s, line)
local e = M.error or error
e(fmt("%s:%d: %s", chunkid(), line or ln, s))
end
--- Counts separators (`="` in a long string delimiter.
--
-- @tparam int i Position of lexer in the source stream.
-- @treturn int
local function skip_sep(i)
local s = sub(z, i, i)
i = i + 1
local count = #match(z, "=*", i)
i = i + count
I = i
return (sub(z, i, i) == s) and count or (-count) - 1
end
--- Reads a long string or long comment.
--
-- @tparam bool is_str
-- @tparam string sep
-- @treturn string
-- @raise if unfinished long string or comment.
local function read_long_string(is_str, sep)
local i = I + 1 -- skip 2nd '['
local c = sub(z, i, i)
if c == "\r" or c == "\n" then -- string starts with a newline?
i = inclinenumber(i) -- skip it
end
while true do
local p, _, r = find(z, "([\r\n%]])", i) -- (long range match)
if not p then
errorline(is_str and "unfinished long string" or
"unfinished long comment")
end
i = p
if r == "]" then -- delimiter test
if skip_sep(i) == sep then
buff = sub(z, buff, I)
I = I + 1 -- skip 2nd ']'
return buff
end
i = I
else -- newline
buff = buff.."\n"
i = inclinenumber(i)
end
end--while
end
--- Reads a string.
--
-- @tparam string del The delimiter.
-- @treturn string
-- @raise if unfinished string or too large escape sequence.
local function read_string(del)
local i = I
while true do
local p, _, r = find(z, "([\n\r\\\"\'])", i) -- (long range match)
if p then
if r == "\n" or r == "\r" then
errorline("unfinished string")
end
i = p
if r == "\\" then -- handle escapes
i = i + 1
r = sub(z, i, i)
if r == "" then break end -- (EOZ error)
p = find("abfnrtv\n\r", r, 1, true)
if p then -- special escapes
if p > 7 then
i = inclinenumber(i)
else
i = i + 1
end
elseif find(r, "%D") then -- other non-digits
i = i + 1
else -- \xxx sequence
local _, q, s = find(z, "^(%d%d?%d?)", i)
i = q + 1
if s + 1 > 256 then -- UCHAR_MAX
errorline("escape sequence too large")
end
end--if p
else
i = i + 1
if r == del then -- ending delimiter
I = i
return sub(z, buff, i - 1) -- return string
end
end--if r
else
break -- (error)
end--if p
end--while
errorline("unfinished string")
end
--- Initializes lexer for given source _z and source name _sourceid.
--
-- @tparam string _z The source code.
-- @tparam string _sourceid Name of the source.
local function init(_z, _sourceid)
z = _z -- source
sourceid = _sourceid -- name of source
I = 1 -- lexer's position in source
ln = 1 -- line number
tok = {} -- lexed token list*
seminfo = {} -- lexed semantic information list*
tokln = {} -- line numbers for messages*
-- Initial processing (shbang handling).
local p, _, q, r = find(z, "^(#[^\r\n]*)(\r?\n?)")
if p then -- skip first line
I = I + #q
addtoken("TK_COMMENT", q)
if #r > 0 then inclinenumber(I, true) end
end
end
--- Runs lexer on the given source code.
--
-- @tparam string source The Lua source to scan.
-- @tparam ?string source_name Name of the source (optional).
-- @treturn {string,...} A list of lexed tokens.
-- @treturn {string,...} A list of semantic information (lexed strings).
-- @treturn {int,...} A list of line numbers.
function M.lex(source, source_name)
init(source, source_name)
while true do--outer
local i = I
-- inner loop allows break to be used to nicely section tests
while true do --luacheck: ignore 512
local p, _, r = find(z, "^([_%a][_%w]*)", i)
if p then
I = i + #r
if kw[r] then
addtoken("TK_KEYWORD", r) -- reserved word (keyword)
else
addtoken("TK_NAME", r) -- identifier
end
break -- (continue)
end
local p, _, r = find(z, "^(%.?)%d", i)
if p then -- numeral
if r == "." then i = i + 1 end
local _, q, r = find(z, "^%d*[%.%d]*([eE]?)", i) --luacheck: ignore 421
i = q + 1
if #r == 1 then -- optional exponent
if match(z, "^[%+%-]", i) then -- optional sign
i = i + 1
end
end
local _, q = find(z, "^[_%w]*", i)
I = q + 1
local v = sub(z, p, q) -- string equivalent
if not tonumber(v) then -- handles hex test also
errorline("malformed number")
end
addtoken("TK_NUMBER", v)
break -- (continue)
end
local p, q, r, t = find(z, "^((%s)[ \t\v\f]*)", i)
if p then
if t == "\n" or t == "\r" then -- newline
inclinenumber(i, true)
else
I = q + 1 -- whitespace
addtoken("TK_SPACE", r)
end
break -- (continue)
end
local _, q = find(z, "^::", i)
if q then
I = q + 1
addtoken("TK_OP", "::")
break -- (continue)
end
local r = match(z, "^%p", i)
if r then
buff = i
local p = find("-[\"\'.=<>~", r, 1, true) --luacheck: ignore 421
if p then
-- two-level if block for punctuation/symbols
if p <= 2 then
if p == 1 then -- minus
local c = match(z, "^%-%-(%[?)", i)
if c then
i = i + 2
local sep = -1
if c == "[" then
sep = skip_sep(i)
end
if sep >= 0 then -- long comment
addtoken("TK_LCOMMENT", read_long_string(false, sep))
else -- short comment
I = find(z, "[\n\r]", i) or (#z + 1)
addtoken("TK_COMMENT", sub(z, buff, I - 1))
end
break -- (continue)
end
-- (fall through for "-")
else -- [ or long string
local sep = skip_sep(i)
if sep >= 0 then
addtoken("TK_LSTRING", read_long_string(true, sep))
elseif sep == -1 then
addtoken("TK_OP", "[")
else
errorline("invalid long string delimiter")
end
break -- (continue)
end
elseif p <= 5 then
if p < 5 then -- strings
I = i + 1
addtoken("TK_STRING", read_string(r))
break -- (continue)
end
r = match(z, "^%.%.?%.?", i) -- .|..|... dots
-- (fall through)
else -- relational
r = match(z, "^%p=?", i)
-- (fall through)
end
end
I = i + #r
addtoken("TK_OP", r) -- for other symbols, fall through
break -- (continue)
end
local r = sub(z, i, i)
if r ~= "" then
I = i + 1
addtoken("TK_OP", r) -- other single-char tokens
break
end
addtoken("TK_EOS", "") -- end of stream,
return tok, seminfo, tokln -- exit here
end--while inner
end--while outer
end
return M

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,852 @@
---------
-- This module does lexer-based optimizations.
--
-- **Notes:**
--
-- * TODO: General string delimiter conversion optimizer.
-- * TODO: (numbers) warn if overly significant digit.
----
local char = string.char
local find = string.find
local match = string.match
local rep = string.rep
local sub = string.sub
local tonumber = tonumber
local tostring = tostring
local print -- set in optimize()
local M = {}
-- error function, can override by setting own function into module
M.error = error
M.warn = {} -- table for warning flags
local stoks, sinfos, stoklns -- source lists
local is_realtoken = { -- significant (grammar) tokens
TK_KEYWORD = true,
TK_NAME = true,
TK_NUMBER = true,
TK_STRING = true,
TK_LSTRING = true,
TK_OP = true,
TK_EOS = true,
}
local is_faketoken = { -- whitespace (non-grammar) tokens
TK_COMMENT = true,
TK_LCOMMENT = true,
TK_EOL = true,
TK_SPACE = true,
}
local opt_details -- for extra information
--- Returns true if current token is at the start of a line.
--
-- It skips over deleted tokens via recursion.
--
-- @tparam int i
-- @treturn bool
local function atlinestart(i)
local tok = stoks[i - 1]
if i <= 1 or tok == "TK_EOL" then
return true
elseif tok == "" then
return atlinestart(i - 1)
end
return false
end
--- Returns true if current token is at the end of a line.
--
-- It skips over deleted tokens via recursion.
--
-- @tparam int i
-- @treturn bool
local function atlineend(i)
local tok = stoks[i + 1]
if i >= #stoks or tok == "TK_EOL" or tok == "TK_EOS" then
return true
elseif tok == "" then
return atlineend(i + 1)
end
return false
end
--- Counts comment EOLs inside a long comment.
--
-- In order to keep line numbering, EOLs need to be reinserted.
--
-- @tparam string lcomment
-- @treturn int
local function commenteols(lcomment)
local sep = #match(lcomment, "^%-%-%[=*%[")
local z = sub(lcomment, sep + 1, -(sep - 1)) -- remove delims
local i, c = 1, 0
while true do
local p, _, r, s = find(z, "([\r\n])([\r\n]?)", i)
if not p then break end -- if no matches, done
i = p + 1
c = c + 1
if #s > 0 and r ~= s then -- skip CRLF or LFCR
i = i + 1
end
end
return c
end
--- Compares two tokens (i, j) and returns the whitespace required.
--
-- See documentation for a reference table of interactions.
--
-- Only two grammar/real tokens are being considered:
--
-- * if `""`, no separation is needed,
-- * if `" "`, then at least one whitespace (or EOL) is required.
--
-- Note: This doesn't work at the start or the end or for EOS!
--
-- @tparam int i
-- @tparam int j
-- @treturn string
local function checkpair(i, j)
local t1, t2 = stoks[i], stoks[j]
if t1 == "TK_STRING" or t1 == "TK_LSTRING" or
t2 == "TK_STRING" or t2 == "TK_LSTRING" then
return ""
elseif t1 == "TK_OP" or t2 == "TK_OP" then
if (t1 == "TK_OP" and (t2 == "TK_KEYWORD" or t2 == "TK_NAME")) or
(t2 == "TK_OP" and (t1 == "TK_KEYWORD" or t1 == "TK_NAME")) then
return ""
end
if t1 == "TK_OP" and t2 == "TK_OP" then
-- for TK_OP/TK_OP pairs, see notes in technotes.txt
local op, op2 = sinfos[i], sinfos[j]
if (match(op, "^%.%.?$") and match(op2, "^%.")) or
(match(op, "^[~=<>]$") and op2 == "=") or
(op == "[" and (op2 == "[" or op2 == "=")) then
return " "
end
return ""
end
-- "TK_OP" + "TK_NUMBER" case
local op = sinfos[i]
if t2 == "TK_OP" then op = sinfos[j] end
if match(op, "^%.%.?%.?$") then
return " "
end
return ""
else-- "TK_KEYWORD" | "TK_NAME" | "TK_NUMBER" then
return " "
end
end
--- Repack tokens, removing deletions caused by optimization process.
local function repack_tokens()
local dtoks, dinfos, dtoklns = {}, {}, {}
local j = 1
for i = 1, #stoks do
local tok = stoks[i]
if tok ~= "" then
dtoks[j], dinfos[j], dtoklns[j] = tok, sinfos[i], stoklns[i]
j = j + 1
end
end
stoks, sinfos, stoklns = dtoks, dinfos, dtoklns
end
--- Does number optimization.
--
-- Optimization using string formatting functions is one way of doing this,
-- but here, we consider all cases and handle them separately (possibly an
-- idiotic approach...).
--
-- Scientific notation being generated is not in canonical form, this may or
-- may not be a bad thing.
--
-- Note: Intermediate portions need to fit into a normal number range.
--
-- Optimizations can be divided based on number patterns:
--
-- * hexadecimal:
-- (1) no need to remove leading zeros, just skip to (2)
-- (2) convert to integer if size equal or smaller
-- * change if equal size -> lose the 'x' to reduce entropy
-- (3) number is then processed as an integer
-- (4) note: does not make 0[xX] consistent
-- * integer:
-- (1) reduce useless fractional part, if present, e.g. 123.000 -> 123.
-- (2) remove leading zeros, e.g. 000123
-- * float:
-- (1) split into digits dot digits
-- (2) if no integer portion, take as zero (can omit later)
-- (3) handle degenerate .000 case, after which the fractional part
-- must be non-zero (if zero, it's matched as float .0)
-- (4) remove trailing zeros for fractional portion
-- (5) p.q where p > 0 and q > 0 cannot be shortened any more
-- (6) otherwise p == 0 and the form is .q, e.g. .000123
-- (7) if scientific shorter, convert, e.g. .000123 -> 123e-6
-- * scientific:
-- (1) split into (digits dot digits) [eE] ([+-] digits)
-- (2) if significand is zero, just use .0
-- (3) remove leading zeros for significand
-- (4) shift out trailing zeros for significand
-- (5) examine exponent and determine which format is best:
-- number with fraction, or scientific
--
-- Note: Number with fraction and scientific number is never converted
-- to integer, because Lua 5.3 distinguishes between integers and floats.
--
--
-- @tparam int i
local function do_number(i)
local before = sinfos[i] -- 'before'
local z = before -- working representation
local y -- 'after', if better
--------------------------------------------------------------------
if match(z, "^0[xX]") then -- hexadecimal number
local v = tostring(tonumber(z))
if #v <= #z then
z = v -- change to integer, AND continue
else
return -- no change; stick to hex
end
end
if match(z, "^%d+$") then -- integer
if tonumber(z) > 0 then
y = match(z, "^0*([1-9]%d*)$") -- remove leading zeros
else
y = "0" -- basic zero
end
elseif not match(z, "[eE]") then -- float
local p, q = match(z, "^(%d*)%.(%d*)$") -- split
if p == "" then p = 0 end -- int part zero
if q == "" then q = "0" end -- fraction part zero
if tonumber(q) == 0 and p == 0 then
y = ".0" -- degenerate .000 to .0
else
-- now, q > 0 holds and p is a number
local zeros_cnt = #match(q, "0*$") -- remove trailing zeros
if zeros_cnt > 0 then
q = sub(q, 1, #q - zeros_cnt)
end
-- if p > 0, nothing else we can do to simplify p.q case
if tonumber(p) > 0 then
y = p.."."..q
else
y = "."..q -- tentative, e.g. .000123
local v = #match(q, "^0*") -- # leading spaces
local w = #q - v -- # significant digits
local nv = tostring(#q)
-- e.g. compare 123e-6 versus .000123
if w + 2 + #nv < 1 + #q then
y = sub(q, -w).."e-"..nv
end
end
end
else -- scientific number
local sig, ex = match(z, "^([^eE]+)[eE]([%+%-]?%d+)$")
ex = tonumber(ex)
-- if got ".", shift out fractional portion of significand
local p, q = match(sig, "^(%d*)%.(%d*)$")
if p then
ex = ex - #q
sig = p..q
end
if tonumber(sig) == 0 then
y = ".0" -- basic float zero
else
local v = #match(sig, "^0*") -- remove leading zeros
sig = sub(sig, v + 1)
v = #match(sig, "0*$") -- shift out trailing zeros
if v > 0 then
sig = sub(sig, 1, #sig - v)
ex = ex + v
end
-- examine exponent and determine which format is best
local nex = tostring(ex)
if ex >= 0 and (ex <= 1 + #nex) then -- a float
y = sig..rep("0", ex).."."
elseif ex < 0 and (ex >= -#sig) then -- fraction, e.g. .123
v = #sig + ex
y = sub(sig, 1, v).."."..sub(sig, v + 1)
elseif ex < 0 and (#nex >= -ex - #sig) then
-- e.g. compare 1234e-5 versus .01234
-- gives: #sig + 1 + #nex >= 1 + (-ex - #sig) + #sig
-- -> #nex >= -ex - #sig
v = -ex - #sig
y = "."..rep("0", v)..sig
else -- non-canonical scientific representation
y = sig.."e"..ex
end
end--if sig
end
if y and y ~= sinfos[i] then
if opt_details then
print("<number> (line "..stoklns[i]..") "..sinfos[i].." -> "..y)
opt_details = opt_details + 1
end
sinfos[i] = y
end
end
--- Does string optimization.
--
-- Note: It works on well-formed strings only!
--
-- Optimizations on characters can be summarized as follows:
--
-- \a\b\f\n\r\t\v -- no change
-- \\ -- no change
-- \"\' -- depends on delim, other can remove \
-- \[\] -- remove \
-- \<char> -- general escape, remove \ (Lua 5.1 only)
-- \<eol> -- normalize the EOL only
-- \ddd -- if \a\b\f\n\r\t\v, change to latter
-- if other < ascii 32, keep ddd but zap leading zeros
-- but cannot have following digits
-- if >= ascii 32, translate it into the literal, then also
-- do escapes for \\,\",\' cases
-- <other> -- no change
--
-- Switch delimiters if string becomes shorter.
--
-- @tparam int I
local function do_string(I)
local info = sinfos[I]
local delim = sub(info, 1, 1) -- delimiter used
local ndelim = (delim == "'") and '"' or "'" -- opposite " <-> '
local z = sub(info, 2, -2) -- actual string
local i = 1
local c_delim, c_ndelim = 0, 0 -- "/' counts
while i <= #z do
local c = sub(z, i, i)
if c == "\\" then -- escaped stuff
local j = i + 1
local d = sub(z, j, j)
local p = find("abfnrtv\\\n\r\"\'0123456789", d, 1, true)
if not p then -- \<char> -- remove \ (Lua 5.1 only)
z = sub(z, 1, i - 1)..sub(z, j)
i = i + 1
elseif p <= 8 then -- \a\b\f\n\r\t\v\\
i = i + 2 -- no change
elseif p <= 10 then -- \<eol> -- normalize EOL
local eol = sub(z, j, j + 1)
if eol == "\r\n" or eol == "\n\r" then
z = sub(z, 1, i).."\n"..sub(z, j + 2)
elseif p == 10 then -- \r case
z = sub(z, 1, i).."\n"..sub(z, j + 1)
end
i = i + 2
elseif p <= 12 then -- \"\' -- remove \ for ndelim
if d == delim then
c_delim = c_delim + 1
i = i + 2
else
c_ndelim = c_ndelim + 1
z = sub(z, 1, i - 1)..sub(z, j)
i = i + 1
end
else -- \ddd -- various steps
local s = match(z, "^(%d%d?%d?)", j)
j = i + 1 + #s -- skip to location
local cv = tonumber(s)
local cc = char(cv)
p = find("\a\b\f\n\r\t\v", cc, 1, true)
if p then -- special escapes
s = "\\"..sub("abfnrtv", p, p)
elseif cv < 32 then -- normalized \ddd
if match(sub(z, j, j), "%d") then
-- if a digit follows, \ddd cannot be shortened
s = "\\"..s
else
s = "\\"..cv
end
elseif cc == delim then -- \<delim>
s = "\\"..cc
c_delim = c_delim + 1
elseif cc == "\\" then -- \\
s = "\\\\"
else -- literal character
s = cc
if cc == ndelim then
c_ndelim = c_ndelim + 1
end
end
z = sub(z, 1, i - 1)..s..sub(z, j)
i = i + #s
end--if p
else-- c ~= "\\" -- <other> -- no change
i = i + 1
if c == ndelim then -- count ndelim, for switching delimiters
c_ndelim = c_ndelim + 1
end
end--if c
end--while
-- Switching delimiters, a long-winded derivation:
-- (1) delim takes 2+2*c_delim bytes, ndelim takes c_ndelim bytes
-- (2) delim becomes c_delim bytes, ndelim becomes 2+2*c_ndelim bytes
-- simplifying the condition (1)>(2) --> c_delim > c_ndelim
if c_delim > c_ndelim then
i = 1
while i <= #z do
local p, _, r = find(z, "([\'\"])", i)
if not p then break end
if r == delim then -- \<delim> -> <delim>
z = sub(z, 1, p - 2)..sub(z, p)
i = p
else-- r == ndelim -- <ndelim> -> \<ndelim>
z = sub(z, 1, p - 1).."\\"..sub(z, p)
i = p + 2
end
end--while
delim = ndelim -- actually change delimiters
end
z = delim..z..delim
if z ~= sinfos[I] then
if opt_details then
print("<string> (line "..stoklns[I]..") "..sinfos[I].." -> "..z)
opt_details = opt_details + 1
end
sinfos[I] = z
end
end
--- Does long string optimization.
--
-- * remove first optional newline
-- * normalize embedded newlines
-- * reduce '=' separators in delimiters if possible
--
-- Note: warning flagged if trailing whitespace found, not trimmed.
--
-- @tparam int I
local function do_lstring(I)
local info = sinfos[I]
local delim1 = match(info, "^%[=*%[") -- cut out delimiters
local sep = #delim1
local delim2 = sub(info, -sep, -1)
local z = sub(info, sep + 1, -(sep + 1)) -- lstring without delims
local y = ""
local i = 1
while true do
local p, _, r, s = find(z, "([\r\n])([\r\n]?)", i)
-- deal with a single line
local ln
if not p then
ln = sub(z, i)
elseif p >= i then
ln = sub(z, i, p - 1)
end
if ln ~= "" then
-- flag a warning if there are trailing spaces, won't optimize!
if match(ln, "%s+$") then
M.warn.LSTRING = "trailing whitespace in long string near line "..stoklns[I]
end
y = y..ln
end
if not p then -- done if no more EOLs
break
end
-- deal with line endings, normalize them
i = p + 1
if p then
if #s > 0 and r ~= s then -- skip CRLF or LFCR
i = i + 1
end
-- skip first newline, which can be safely deleted
if not(i == 1 and i == p) then
y = y.."\n"
end
end
end--while
-- handle possible deletion of one or more '=' separators
if sep >= 3 then
local chk, okay = sep - 1
-- loop to test ending delimiter with less of '=' down to zero
while chk >= 2 do
local delim = "%]"..rep("=", chk - 2).."%]"
if not match(y, delim) then okay = chk end
chk = chk - 1
end
if okay then -- change delimiters
sep = rep("=", okay - 2)
delim1, delim2 = "["..sep.."[", "]"..sep.."]"
end
end
sinfos[I] = delim1..y..delim2
end
--- Does long comment optimization.
--
-- * trim trailing whitespace
-- * normalize embedded newlines
-- * reduce '=' separators in delimiters if possible
--
-- Note: It does not remove first optional newline.
--
-- @tparam int I
local function do_lcomment(I)
local info = sinfos[I]
local delim1 = match(info, "^%-%-%[=*%[") -- cut out delimiters
local sep = #delim1
local delim2 = sub(info, -(sep - 2), -1)
local z = sub(info, sep + 1, -(sep - 1)) -- comment without delims
local y = ""
local i = 1
while true do
local p, _, r, s = find(z, "([\r\n])([\r\n]?)", i)
-- deal with a single line, extract and check trailing whitespace
local ln
if not p then
ln = sub(z, i)
elseif p >= i then
ln = sub(z, i, p - 1)
end
if ln ~= "" then
-- trim trailing whitespace if non-empty line
local ws = match(ln, "%s*$")
if #ws > 0 then ln = sub(ln, 1, -(ws + 1)) end
y = y..ln
end
if not p then -- done if no more EOLs
break
end
-- deal with line endings, normalize them
i = p + 1
if p then
if #s > 0 and r ~= s then -- skip CRLF or LFCR
i = i + 1
end
y = y.."\n"
end
end--while
-- handle possible deletion of one or more '=' separators
sep = sep - 2
if sep >= 3 then
local chk, okay = sep - 1
-- loop to test ending delimiter with less of '=' down to zero
while chk >= 2 do
local delim = "%]"..rep("=", chk - 2).."%]"
if not match(y, delim) then okay = chk end
chk = chk - 1
end
if okay then -- change delimiters
sep = rep("=", okay - 2)
delim1, delim2 = "--["..sep.."[", "]"..sep.."]"
end
end
sinfos[I] = delim1..y..delim2
end
--- Does short comment optimization.
--
-- * trim trailing whitespace
--
-- @tparam int i
local function do_comment(i)
local info = sinfos[i]
local ws = match(info, "%s*$") -- just look from end of string
if #ws > 0 then
info = sub(info, 1, -(ws + 1)) -- trim trailing whitespace
end
sinfos[i] = info
end
--- Returns true if string found in long comment.
--
-- This is a feature to keep copyright or license texts.
--
-- @tparam bool opt_keep
-- @tparam string info
-- @treturn bool
local function keep_lcomment(opt_keep, info)
if not opt_keep then return false end -- option not set
local delim1 = match(info, "^%-%-%[=*%[") -- cut out delimiters
local sep = #delim1
local z = sub(info, sep + 1, -(sep - 1)) -- comment without delims
if find(z, opt_keep, 1, true) then -- try to match
return true
end
end
--- The main entry point.
--
-- * currently, lexer processing has 2 passes
-- * processing is done on a line-oriented basis, which is easier to
-- grok due to the next point...
-- * since there are various options that can be enabled or disabled,
-- processing is a little messy or convoluted
--
-- @tparam {[string]=bool,...} option
-- @tparam {string,...} toklist
-- @tparam {string,...} semlist
-- @tparam {int,...} toklnlist
-- @treturn {string,...} toklist
-- @treturn {string,...} semlist
-- @treturn {int,...} toklnlist
function M.optimize(option, toklist, semlist, toklnlist)
-- Set option flags.
local opt_comments = option["opt-comments"]
local opt_whitespace = option["opt-whitespace"]
local opt_emptylines = option["opt-emptylines"]
local opt_eols = option["opt-eols"]
local opt_strings = option["opt-strings"]
local opt_numbers = option["opt-numbers"]
local opt_x = option["opt-experimental"]
local opt_keep = option.KEEP
opt_details = option.DETAILS and 0 -- upvalues for details display
print = M.print or _G.print
if opt_eols then -- forced settings, otherwise won't work properly
opt_comments = true
opt_whitespace = true
opt_emptylines = true
elseif opt_x then
opt_whitespace = true
end
-- Variable initialization.
stoks, sinfos, stoklns -- set source lists
= toklist, semlist, toklnlist
local i = 1 -- token position
local tok, info -- current token
local prev -- position of last grammar token
-- on same line (for TK_SPACE stuff)
-- Changes a token, info pair.
local function settoken(tok, info, I) --luacheck: ignore 431
I = I or i
stoks[I] = tok or ""
sinfos[I] = info or ""
end
-- Experimental optimization for ';' operator.
if opt_x then
while true do
tok, info = stoks[i], sinfos[i]
if tok == "TK_EOS" then -- end of stream/pass
break
elseif tok == "TK_OP" and info == ";" then
-- ';' operator found, since it is entirely optional, set it
-- as a space to let whitespace optimization do the rest
settoken("TK_SPACE", " ")
end
i = i + 1
end
repack_tokens()
end
-- Processing loop (PASS 1)
i = 1
while true do
tok, info = stoks[i], sinfos[i]
local atstart = atlinestart(i) -- set line begin flag
if atstart then prev = nil end
if tok == "TK_EOS" then -- end of stream/pass
break
elseif tok == "TK_KEYWORD" or -- keywords, identifiers,
tok == "TK_NAME" or -- operators
tok == "TK_OP" then
-- TK_KEYWORD and TK_OP can't be optimized without a big
-- optimization framework; it would be more of an optimizing
-- compiler, not a source code compressor
-- TK_NAME that are locals needs parser to analyze/optimize
prev = i
elseif tok == "TK_NUMBER" then -- numbers
if opt_numbers then
do_number(i) -- optimize
end
prev = i
elseif tok == "TK_STRING" or -- strings, long strings
tok == "TK_LSTRING" then
if opt_strings then
if tok == "TK_STRING" then
do_string(i) -- optimize
else
do_lstring(i) -- optimize
end
end
prev = i
elseif tok == "TK_COMMENT" then -- short comments
if opt_comments then
if i == 1 and sub(info, 1, 1) == "#" then
-- keep shbang comment, trim whitespace
do_comment(i)
else
-- safe to delete, as a TK_EOL (or TK_EOS) always follows
settoken() -- remove entirely
end
elseif opt_whitespace then -- trim whitespace only
do_comment(i)
end
elseif tok == "TK_LCOMMENT" then -- long comments
if keep_lcomment(opt_keep, info) then
-- if --keep, we keep a long comment if <msg> is found;
-- this is a feature to keep copyright or license texts
if opt_whitespace then -- trim whitespace only
do_lcomment(i)
end
prev = i
elseif opt_comments then
local eols = commenteols(info)
-- prepare opt_emptylines case first, if a disposable token
-- follows, current one is safe to dump, else keep a space;
-- it is implied that the operation is safe for '-', because
-- current is a TK_LCOMMENT, and must be separate from a '-'
if is_faketoken[stoks[i + 1]] then
settoken() -- remove entirely
tok = ""
else
settoken("TK_SPACE", " ")
end
-- if there are embedded EOLs to keep and opt_emptylines is
-- disabled, then switch the token into one or more EOLs
if not opt_emptylines and eols > 0 then
settoken("TK_EOL", rep("\n", eols))
end
-- if optimizing whitespaces, force reinterpretation of the
-- token to give a chance for the space to be optimized away
if opt_whitespace and tok ~= "" then
i = i - 1 -- to reinterpret
end
else -- disabled case
if opt_whitespace then -- trim whitespace only
do_lcomment(i)
end
prev = i
end
elseif tok == "TK_EOL" then -- line endings
if atstart and opt_emptylines then
settoken() -- remove entirely
elseif info == "\r\n" or info == "\n\r" then
-- normalize the rest of the EOLs for CRLF/LFCR only
-- (note that TK_LCOMMENT can change into several EOLs)
settoken("TK_EOL", "\n")
end
elseif tok == "TK_SPACE" then -- whitespace
if opt_whitespace then
if atstart or atlineend(i) then
-- delete leading and trailing whitespace
settoken() -- remove entirely
else
-- at this point, since leading whitespace have been removed,
-- there should be a either a real token or a TK_LCOMMENT
-- prior to hitting this whitespace; the TK_LCOMMENT case
-- only happens if opt_comments is disabled; so prev ~= nil
local ptok = stoks[prev]
if ptok == "TK_LCOMMENT" then
-- previous TK_LCOMMENT can abut with anything
settoken() -- remove entirely
else
-- prev must be a grammar token; consecutive TK_SPACE
-- tokens is impossible when optimizing whitespace
local ntok = stoks[i + 1]
if is_faketoken[ntok] then
-- handle special case where a '-' cannot abut with
-- either a short comment or a long comment
if (ntok == "TK_COMMENT" or ntok == "TK_LCOMMENT") and
ptok == "TK_OP" and sinfos[prev] == "-" then
-- keep token
else
settoken() -- remove entirely
end
else--is_realtoken
-- check a pair of grammar tokens, if can abut, then
-- delete space token entirely, otherwise keep one space
local s = checkpair(prev, i + 1)
if s == "" then
settoken() -- remove entirely
else
settoken("TK_SPACE", " ")
end
end
end
end
end
else
error("unidentified token encountered")
end
i = i + 1
end--while
repack_tokens()
-- Processing loop (PASS 2)
if opt_eols then
i = 1
-- Aggressive EOL removal only works with most non-grammar tokens
-- optimized away because it is a rather simple scheme -- basically
-- it just checks 'real' token pairs around EOLs.
if stoks[1] == "TK_COMMENT" then
-- first comment still existing must be shbang, skip whole line
i = 3
end
while true do
tok = stoks[i]
if tok == "TK_EOS" then -- end of stream/pass
break
elseif tok == "TK_EOL" then -- consider each TK_EOL
local t1, t2 = stoks[i - 1], stoks[i + 1]
if is_realtoken[t1] and is_realtoken[t2] then -- sanity check
local s = checkpair(i - 1, i + 1)
if s == "" or t2 == "TK_EOS" then
settoken() -- remove entirely
end
end
end--if tok
i = i + 1
end--while
repack_tokens()
end
if opt_details and opt_details > 0 then print() end -- spacing
return stoks, sinfos, stoklns
end
return M

View File

@@ -0,0 +1,644 @@
---------
-- This module does parser-based optimizations.
--
-- **Notes:**
--
-- * The processing load is quite significant, but since this is an
-- off-line text processor, I believe we can wait a few seconds.
-- * TODO: Might process "local a,a,a" wrongly... need tests!
-- * TODO: Remove position handling if overlapped locals (rem < 0)
-- needs more study, to check behaviour.
-- * TODO: There are probably better ways to do allocation, e.g. by
-- choosing better methods to sort and pick locals...
-- * TODO: We don't need 53*63 two-letter identifiers; we can make
-- do with significantly less depending on how many that are really
-- needed and improve entropy; e.g. 13 needed -> choose 4*4 instead.
----
local byte = string.byte
local char = string.char
local concat = table.concat
local fmt = string.format
local pairs = pairs
local rep = string.rep
local sort = table.sort
local sub = string.sub
local M = {}
-- Letter frequencies for reducing symbol entropy (fixed version)
-- * Might help a wee bit when the output file is compressed
-- * See Wikipedia: http://en.wikipedia.org/wiki/Letter_frequencies
-- * We use letter frequencies according to a Linotype keyboard, plus
-- the underscore, and both lower case and upper case letters.
-- * The arrangement below (LC, underscore, %d, UC) is arbitrary.
-- * This is certainly not optimal, but is quick-and-dirty and the
-- process has no significant overhead
local LETTERS = "etaoinshrdlucmfwypvbgkqjxz_ETAOINSHRDLUCMFWYPVBGKQJXZ"
local ALPHANUM = "etaoinshrdlucmfwypvbgkqjxz_0123456789ETAOINSHRDLUCMFWYPVBGKQJXZ"
-- Names or identifiers that must be skipped.
-- (The first two lines are for keywords.)
local SKIP_NAME = {}
for v in ([[
and break do else elseif end false for function if in
local nil not or repeat return then true until while
self _ENV]]):gmatch("%S+") do
SKIP_NAME[v] = true
end
local toklist, seminfolist, -- token lists (lexer output)
tokpar, seminfopar, xrefpar, -- token lists (parser output)
globalinfo, localinfo, -- variable information tables
statinfo, -- statment type table
globaluniq, localuniq, -- unique name tables
var_new, -- index of new variable names
varlist -- list of output variables
--- Preprocesses information table to get lists of unique names.
--
-- @tparam {table,...} infotable
-- @treturn table
local function preprocess(infotable)
local uniqtable = {}
for i = 1, #infotable do -- enumerate info table
local obj = infotable[i]
local name = obj.name
if not uniqtable[name] then -- not found, start an entry
uniqtable[name] = {
decl = 0, token = 0, size = 0,
}
end
local uniq = uniqtable[name] -- count declarations, tokens, size
uniq.decl = uniq.decl + 1
local xref = obj.xref
local xcount = #xref
uniq.token = uniq.token + xcount
uniq.size = uniq.size + xcount * #name
if obj.decl then -- if local table, create first,last pairs
obj.id = i
obj.xcount = xcount
if xcount > 1 then -- if ==1, means local never accessed
obj.first = xref[2]
obj.last = xref[xcount]
end
else -- if global table, add a back ref
uniq.id = i
end
end--for
return uniqtable
end
--- Calculates actual symbol frequencies, in order to reduce entropy.
--
-- * This may help further reduce the size of compressed sources.
-- * Note that since parsing optimizations is put before lexing
-- optimizations, the frequency table is not exact!
-- * Yes, this will miss --keep block comments too...
--
-- @tparam table option
local function recalc_for_entropy(option)
-- table of token classes to accept in calculating symbol frequency
local ACCEPT = {
TK_KEYWORD = true, TK_NAME = true, TK_NUMBER = true,
TK_STRING = true, TK_LSTRING = true,
}
if not option["opt-comments"] then
ACCEPT.TK_COMMENT = true
ACCEPT.TK_LCOMMENT = true
end
-- Create a new table and remove any original locals by filtering.
local filtered = {}
for i = 1, #toklist do
filtered[i] = seminfolist[i]
end
for i = 1, #localinfo do -- enumerate local info table
local obj = localinfo[i]
local xref = obj.xref
for j = 1, obj.xcount do
local p = xref[j]
filtered[p] = "" -- remove locals
end
end
local freq = {} -- reset symbol frequency table
for i = 0, 255 do freq[i] = 0 end
for i = 1, #toklist do -- gather symbol frequency
local tok, info = toklist[i], filtered[i]
if ACCEPT[tok] then
for j = 1, #info do
local c = byte(info, j)
freq[c] = freq[c] + 1
end
end--if
end--for
-- Re-sorts symbols according to actual frequencies.
--
-- @tparam string symbols
-- @treturn string
local function resort(symbols)
local symlist = {}
for i = 1, #symbols do -- prepare table to sort
local c = byte(symbols, i)
symlist[i] = { c = c, freq = freq[c], }
end
sort(symlist, function(v1, v2) -- sort selected symbols
return v1.freq > v2.freq
end)
local charlist = {} -- reconstitute the string
for i = 1, #symlist do
charlist[i] = char(symlist[i].c)
end
return concat(charlist)
end
LETTERS = resort(LETTERS) -- change letter arrangement
ALPHANUM = resort(ALPHANUM)
end
--- Returns a string containing a new local variable name to use, and
-- a flag indicating whether it collides with a global variable.
--
-- Trapping keywords and other names like 'self' is done elsewhere.
--
-- @treturn string A new local variable name.
-- @treturn bool Whether the name collides with a global variable.
local function new_var_name()
local var
local cletters, calphanum = #LETTERS, #ALPHANUM
local v = var_new
if v < cletters then -- single char
v = v + 1
var = sub(LETTERS, v, v)
else -- longer names
local range, sz = cletters, 1 -- calculate # chars fit
repeat
v = v - range
range = range * calphanum
sz = sz + 1
until range > v
local n = v % cletters -- left side cycles faster
v = (v - n) / cletters -- do first char first
n = n + 1
var = sub(LETTERS, n, n)
while sz > 1 do
local m = v % calphanum
v = (v - m) / calphanum
m = m + 1
var = var..sub(ALPHANUM, m, m)
sz = sz - 1
end
end
var_new = var_new + 1
return var, globaluniq[var] ~= nil
end
--- Calculates and prints some statistics.
--
-- Note: probably better in main source, put here for now.
--
-- @tparam table globaluniq
-- @tparam table localuniq
-- @tparam table afteruniq
-- @tparam table option
local function stats_summary(globaluniq, localuniq, afteruniq, option) --luacheck: ignore 431
local print = M.print or print
local opt_details = option.DETAILS
if option.QUIET then return end
local uniq_g , uniq_li, uniq_lo = 0, 0, 0
local decl_g, decl_li, decl_lo = 0, 0, 0
local token_g, token_li, token_lo = 0, 0, 0
local size_g, size_li, size_lo = 0, 0, 0
local function avg(c, l) -- safe average function
if c == 0 then return 0 end
return l / c
end
-- Collect statistics (Note: globals do not have declarations!)
for _, uniq in pairs(globaluniq) do
uniq_g = uniq_g + 1
token_g = token_g + uniq.token
size_g = size_g + uniq.size
end
for _, uniq in pairs(localuniq) do
uniq_li = uniq_li + 1
decl_li = decl_li + uniq.decl
token_li = token_li + uniq.token
size_li = size_li + uniq.size
end
for _, uniq in pairs(afteruniq) do
uniq_lo = uniq_lo + 1
decl_lo = decl_lo + uniq.decl
token_lo = token_lo + uniq.token
size_lo = size_lo + uniq.size
end
local uniq_ti = uniq_g + uniq_li
local decl_ti = decl_g + decl_li
local token_ti = token_g + token_li
local size_ti = size_g + size_li
local uniq_to = uniq_g + uniq_lo
local decl_to = decl_g + decl_lo
local token_to = token_g + token_lo
local size_to = size_g + size_lo
-- Detailed stats: global list
if opt_details then
local sorted = {} -- sort table of unique global names by size
for name, uniq in pairs(globaluniq) do
uniq.name = name
sorted[#sorted + 1] = uniq
end
sort(sorted, function(v1, v2)
return v1.size > v2.size
end)
do
local tabf1, tabf2 = "%8s%8s%10s %s", "%8d%8d%10.2f %s"
local hl = rep("-", 44)
print("*** global variable list (sorted by size) ***\n"..hl)
print(fmt(tabf1, "Token", "Input", "Input", "Global"))
print(fmt(tabf1, "Count", "Bytes", "Average", "Name"))
print(hl)
for i = 1, #sorted do
local uniq = sorted[i]
print(fmt(tabf2, uniq.token, uniq.size, avg(uniq.token, uniq.size), uniq.name))
end
print(hl)
print(fmt(tabf2, token_g, size_g, avg(token_g, size_g), "TOTAL"))
print(hl.."\n")
end
-- Detailed stats: local list
do
local tabf1, tabf2 = "%8s%8s%8s%10s%8s%10s %s", "%8d%8d%8d%10.2f%8d%10.2f %s"
local hl = rep("-", 70)
print("*** local variable list (sorted by allocation order) ***\n"..hl)
print(fmt(tabf1, "Decl.", "Token", "Input", "Input", "Output", "Output", "Global"))
print(fmt(tabf1, "Count", "Count", "Bytes", "Average", "Bytes", "Average", "Name"))
print(hl)
for i = 1, #varlist do -- iterate according to order assigned
local name = varlist[i]
local uniq = afteruniq[name]
local old_t, old_s = 0, 0
for j = 1, #localinfo do -- find corresponding old names and calculate
local obj = localinfo[j]
if obj.name == name then
old_t = old_t + obj.xcount
old_s = old_s + obj.xcount * #obj.oldname
end
end
print(fmt(tabf2, uniq.decl, uniq.token, old_s, avg(old_t, old_s),
uniq.size, avg(uniq.token, uniq.size), name))
end
print(hl)
print(fmt(tabf2, decl_lo, token_lo, size_li, avg(token_li, size_li),
size_lo, avg(token_lo, size_lo), "TOTAL"))
print(hl.."\n")
end
end--if opt_details
-- Display output
do
local tabf1, tabf2 = "%-16s%8s%8s%8s%8s%10s", "%-16s%8d%8d%8d%8d%10.2f"
local hl = rep("-", 58)
print("*** local variable optimization summary ***\n"..hl)
print(fmt(tabf1, "Variable", "Unique", "Decl.", "Token", "Size", "Average"))
print(fmt(tabf1, "Types", "Names", "Count", "Count", "Bytes", "Bytes"))
print(hl)
print(fmt(tabf2, "Global", uniq_g, decl_g, token_g, size_g, avg(token_g, size_g)))
print(hl)
print(fmt(tabf2, "Local (in)", uniq_li, decl_li, token_li, size_li, avg(token_li, size_li)))
print(fmt(tabf2, "TOTAL (in)", uniq_ti, decl_ti, token_ti, size_ti, avg(token_ti, size_ti)))
print(hl)
print(fmt(tabf2, "Local (out)", uniq_lo, decl_lo, token_lo, size_lo, avg(token_lo, size_lo)))
print(fmt(tabf2, "TOTAL (out)", uniq_to, decl_to, token_to, size_to, avg(token_to, size_to)))
print(hl.."\n")
end
end
--- Does experimental optimization for f("string") statements.
--
-- It's safe to delete parentheses without adding whitespace, as both
-- kinds of strings can abut with anything else.
local function optimize_func1()
local function is_strcall(j) -- find f("string") pattern
local t1 = tokpar[j + 1] or ""
local t2 = tokpar[j + 2] or ""
local t3 = tokpar[j + 3] or ""
if t1 == "(" and t2 == "<string>" and t3 == ")" then
return true
end
end
local del_list = {} -- scan for function pattern,
local i = 1 -- tokens to be deleted are marked
while i <= #tokpar do
local id = statinfo[i]
if id == "call" and is_strcall(i) then -- found & mark ()
del_list[i + 1] = true -- '('
del_list[i + 3] = true -- ')'
i = i + 3
end
i = i + 1
end
-- Delete a token and adjust all relevant tables.
-- * Currently invalidates globalinfo and localinfo (not updated),
-- so any other optimization is done after processing locals
-- (of course, we can also lex the source data again...).
-- * Faster one-pass token deletion.
local del_list2 = {}
do
local i, dst, idend = 1, 1, #tokpar
while dst <= idend do -- process parser tables
if del_list[i] then -- found a token to delete?
del_list2[xrefpar[i]] = true
i = i + 1
end
if i > dst then
if i <= idend then -- shift table items lower
tokpar[dst] = tokpar[i]
seminfopar[dst] = seminfopar[i]
xrefpar[dst] = xrefpar[i] - (i - dst)
statinfo[dst] = statinfo[i]
else -- nil out excess entries
tokpar[dst] = nil
seminfopar[dst] = nil
xrefpar[dst] = nil
statinfo[dst] = nil
end
end
i = i + 1
dst = dst + 1
end
end
do
local i, dst, idend = 1, 1, #toklist
while dst <= idend do -- process lexer tables
if del_list2[i] then -- found a token to delete?
i = i + 1
end
if i > dst then
if i <= idend then -- shift table items lower
toklist[dst] = toklist[i]
seminfolist[dst] = seminfolist[i]
else -- nil out excess entries
toklist[dst] = nil
seminfolist[dst] = nil
end
end
i = i + 1
dst = dst + 1
end
end
end
--- Does local variable optimization.
--
-- @tparam {[string]=bool,...} option
local function optimize_locals(option)
var_new = 0 -- reset variable name allocator
varlist = {}
-- Preprocess global/local tables, handle entropy reduction.
globaluniq = preprocess(globalinfo)
localuniq = preprocess(localinfo)
if option["opt-entropy"] then -- for entropy improvement
recalc_for_entropy(option)
end
-- Build initial declared object table, then sort according to
-- token count, this might help assign more tokens to more common
-- variable names such as 'e' thus possibly reducing entropy.
-- * An object knows its localinfo index via its 'id' field.
-- * Special handling for "self" and "_ENV" special local (parameter) here.
local object = {}
for i = 1, #localinfo do
object[i] = localinfo[i]
end
sort(object, function(v1, v2) -- sort largest first
return v1.xcount > v2.xcount
end)
-- The special "self" and "_ENV" function parameters must be preserved.
-- * The allocator below will never use "self", so it is safe to
-- keep those implicit declarations as-is.
local temp, j, used_specials = {}, 1, {}
for i = 1, #object do
local obj = object[i]
if not obj.is_special then
temp[j] = obj
j = j + 1
else
used_specials[#used_specials + 1] = obj.name
end
end
object = temp
-- A simple first-come first-served heuristic name allocator,
-- note that this is in no way optimal...
-- * Each object is a local variable declaration plus existence.
-- * The aim is to assign short names to as many tokens as possible,
-- so the following tries to maximize name reuse.
-- * Note that we preserve sort order.
local nobject = #object
while nobject > 0 do
local varname, gcollide
repeat
varname, gcollide = new_var_name() -- collect a variable name
until not SKIP_NAME[varname] -- skip all special names
varlist[#varlist + 1] = varname -- keep a list
local oleft = nobject
-- If variable name collides with an existing global, the name
-- cannot be used by a local when the name is accessed as a global
-- during which the local is alive (between 'act' to 'rem'), so
-- we drop objects that collides with the corresponding global.
if gcollide then
-- find the xref table of the global
local gref = globalinfo[globaluniq[varname].id].xref
local ngref = #gref
-- enumerate for all current objects; all are valid at this point
for i = 1, nobject do
local obj = object[i]
local act, rem = obj.act, obj.rem -- 'live' range of local
-- if rem < 0, it is a -id to a local that had the same name
-- so follow rem to extend it; does this make sense?
while rem < 0 do
rem = localinfo[-rem].rem
end
local drop
for j = 1, ngref do
local p = gref[j]
if p >= act and p <= rem then drop = true end -- in range?
end
if drop then
obj.skip = true
oleft = oleft - 1
end
end--for
end--if gcollide
-- Now the first unassigned local (since it's sorted) will be the
-- one with the most tokens to rename, so we set this one and then
-- eliminate all others that collides, then any locals that left
-- can then reuse the same variable name; this is repeated until
-- all local declaration that can use this name is assigned.
--
-- The criteria for local-local reuse/collision is:
-- A is the local with a name already assigned
-- B is the unassigned local under consideration
-- => anytime A is accessed, it cannot be when B is 'live'
-- => to speed up things, we have first/last accesses noted
while oleft > 0 do
local i = 1
while object[i].skip do -- scan for first object
i = i + 1
end
-- First object is free for assignment of the variable name
-- [first,last] gives the access range for collision checking.
oleft = oleft - 1
local obja = object[i]
i = i + 1
obja.newname = varname
obja.skip = true
obja.done = true
local first, last = obja.first, obja.last
local xref = obja.xref
-- Then, scan all the rest and drop those colliding.
-- If A was never accessed then it'll never collide with anything
-- otherwise trivial skip if:
-- * B was activated after A's last access (last < act),
-- * B was removed before A's first access (first > rem),
-- if not, see detailed skip below...
if first and oleft > 0 then -- must have at least 1 access
local scanleft = oleft
while scanleft > 0 do
while object[i].skip do -- next valid object
i = i + 1
end
scanleft = scanleft - 1
local objb = object[i]
i = i + 1
local act, rem = objb.act, objb.rem -- live range of B
-- if rem < 0, extend range of rem thru' following local
while rem < 0 do
rem = localinfo[-rem].rem
end
if not(last < act or first > rem) then -- possible collision
-- B is activated later than A or at the same statement,
-- this means for no collision, A cannot be accessed when B
-- is alive, since B overrides A (or is a peer).
if act >= obja.act then
for j = 1, obja.xcount do -- ... then check every access
local p = xref[j]
if p >= act and p <= rem then -- A accessed when B live!
oleft = oleft - 1
objb.skip = true
break
end
end--for
-- A is activated later than B, this means for no collision,
-- A's access is okay since it overrides B, but B's last
-- access need to be earlier than A's activation time.
else
if objb.last and objb.last >= obja.act then
oleft = oleft - 1
objb.skip = true
end
end
end
if oleft == 0 then break end
end
end--if first
end--while
-- After assigning all possible locals to one variable name, the
-- unassigned locals/objects have the skip field reset and the table
-- is compacted, to hopefully reduce iteration time.
local temp, j = {}, 1
for i = 1, nobject do
local obj = object[i]
if not obj.done then
obj.skip = false
temp[j] = obj
j = j + 1
end
end
object = temp -- new compacted object table
nobject = #object -- objects left to process
end--while
-- After assigning all locals with new variable names, we can
-- patch in the new names, and reprocess to get 'after' stats.
for i = 1, #localinfo do -- enumerate all locals
local obj = localinfo[i]
local xref = obj.xref
if obj.newname then -- if got new name, patch it in
for j = 1, obj.xcount do
local p = xref[j] -- xrefs indexes the token list
seminfolist[p] = obj.newname
end
obj.name, obj.oldname -- adjust names
= obj.newname, obj.name
else
obj.oldname = obj.name -- for cases like 'self'
end
end
-- Deal with statistics output.
for _, name in ipairs(used_specials) do
varlist[#varlist + 1] = name
end
local afteruniq = preprocess(localinfo)
stats_summary(globaluniq, localuniq, afteruniq, option)
end
--- The main entry point.
--
-- @tparam table option
-- @tparam {string,...} _toklist
-- @tparam {string,...} _seminfolist
-- @tparam table xinfo
function M.optimize(option, _toklist, _seminfolist, xinfo)
-- set tables
toklist, seminfolist -- from lexer
= _toklist, _seminfolist
tokpar, seminfopar, xrefpar -- from parser
= xinfo.toklist, xinfo.seminfolist, xinfo.xreflist
globalinfo, localinfo, statinfo -- from parser
= xinfo.globalinfo, xinfo.localinfo, xinfo.statinfo
-- Optimize locals.
if option["opt-locals"] then
optimize_locals(option)
end
-- Other optimizations.
if option["opt-experimental"] then -- experimental
optimize_func1()
-- WARNING globalinfo and localinfo now invalidated!
end
end
return M

View File

@@ -0,0 +1,90 @@
---------
-- Example of a plugin for LuaSrcDiet.
--
-- WARNING: highly experimental! interface liable to change
--
-- **Notes:**
--
-- * Any function can be omitted and LuaSrcDiet won't call it.
-- * The functions are:
-- (1) init(_option, _srcfl, _destfl)
-- (2) post_load(z) can return z
-- (3) post_lex(toklist, seminfolist, toklnlist)
-- (4) post_parse(globalinfo, localinfo)
-- (5) post_optparse()
-- (6) post_optlex(toklist, seminfolist, toklnlist)
-- * Older tables can be copied and kept in the plugin and used later.
-- * If you modify 'option', remember that LuaSrcDiet might be
-- processing more than one file.
-- * Arrangement of the functions is not final!
-- * TODO: can't process additional options from command line yet
----
local M = {}
local option -- local reference to list of options
local srcfl, destfl -- filenames
local old_quiet
local function print(...) -- handle quiet option
if option.QUIET then return end
_G.print(...)
end
--- Initialization.
--
-- @tparam {[string]=bool,...} _option
-- @tparam string _srcfl Path of the source file.
-- @tparam string _destfl Path of the destination file.
function M.init(_option, _srcfl, _destfl)
option = _option
srcfl, destfl = _srcfl, _destfl
-- plugin can impose its own option starting from here
end
--- Message display, post-load processing, can return z.
function M.post_load(z)
-- this message will print after the LuaSrcDiet title message
print([[
Example plugin module for LuaSrcDiet
]])
print("Example: source file name is '"..srcfl.."'")
print("Example: destination file name is '"..destfl.."'")
print("Example: the size of the source file is "..#z.." bytes")
-- returning z is optional; this allows optional replacement of
-- the source data prior to lexing
return z
end
--- Post-lexing processing, can work on lexer table output.
function M.post_lex(toklist, seminfolist, toklnlist) --luacheck: ignore
print("Example: the number of lexed elements is "..#toklist)
end
--- Post-parsing processing, gives globalinfo, localinfo.
function M.post_parse(globalinfo, localinfo)
print("Example: size of globalinfo is "..#globalinfo)
print("Example: size of localinfo is "..#localinfo)
old_quiet = option.QUIET
option.QUIET = true
end
--- Post-parser optimization processing, can get tables from elsewhere.
function M.post_optparse()
option.QUIET = old_quiet
print("Example: pretend to do post-optparse")
end
--- Post-lexer optimization processing, can get tables from elsewhere.
function M.post_optlex(toklist, seminfolist, toklnlist) --luacheck: ignore
print("Example: pretend to do post-optlex")
-- restore old settings, other file might need original settings
option.QUIET = old_quiet
-- option.EXIT can be set at the end of any post_* function to stop
-- further processing and exit for the current file being worked on
-- in this case, final stats printout is disabled and the output will
-- not be written to the destination file
option.EXIT = true
end
return M

Some files were not shown because too many files have changed in this diff Show More