- added the public-domain updaterevision so I can have access to git stuff

Sun, 19 Jan 2014 20:16:00 +0200

author
Teemu Piippo <crimsondusk64@gmail.com>
date
Sun, 19 Jan 2014 20:16:00 +0200
changeset 82
841562f5a32f
parent 81
071715c17296
child 83
4655d342a998

- added the public-domain updaterevision so I can have access to git stuff
- lexer #include now works properly! woot!
- merged commands.def and events.def to botc_defs.bts. This is essentially the "zcommon.acs" of botc.

.gitignore file | annotate | diff | comparison | revisions
CMakeLists.txt file | annotate | diff | comparison | revisions
botc_defs.bts file | annotate | diff | comparison | revisions
commands.def file | annotate | diff | comparison | revisions
events.def file | annotate | diff | comparison | revisions
src/commands.cc file | annotate | diff | comparison | revisions
src/commands.h file | annotate | diff | comparison | revisions
src/containers.h file | annotate | diff | comparison | revisions
src/events.cc file | annotate | diff | comparison | revisions
src/events.h file | annotate | diff | comparison | revisions
src/format.cc file | annotate | diff | comparison | revisions
src/lexer.cc file | annotate | diff | comparison | revisions
src/lexer.h file | annotate | diff | comparison | revisions
src/lexer_scanner.cc file | annotate | diff | comparison | revisions
src/lexer_scanner.h file | annotate | diff | comparison | revisions
src/main.cc file | annotate | diff | comparison | revisions
src/main.h file | annotate | diff | comparison | revisions
src/parser.cc file | annotate | diff | comparison | revisions
src/parser.h file | annotate | diff | comparison | revisions
src/str.cc file | annotate | diff | comparison | revisions
src/str.h file | annotate | diff | comparison | revisions
src/tokens.h file | annotate | diff | comparison | revisions
src/types.h file | annotate | diff | comparison | revisions
updaterevision/CMakeLists.txt file | annotate | diff | comparison | revisions
updaterevision/updaterevision.c file | annotate | diff | comparison | revisions
--- a/.gitignore	Sat Jan 18 02:11:45 2014 +0200
+++ b/.gitignore	Sun Jan 19 20:16:00 2014 +0200
@@ -1,1 +1,3 @@
 build
+gitinfo.h
+untracked
\ No newline at end of file
--- a/CMakeLists.txt	Sat Jan 18 02:11:45 2014 +0200
+++ b/CMakeLists.txt	Sun Jan 19 20:16:00 2014 +0200
@@ -1,3 +1,6 @@
+cmake_minimum_required (VERSION 2.8)
+
+add_subdirectory (updaterevision)
 add_executable (botc
 	src/commands.cc
 	src/data_buffer.cc
@@ -13,5 +16,15 @@
 	src/variables.cc
 )
 
+get_target_property (UPDATEREVISION_EXE updaterevision LOCATION)
+
+add_custom_target (revision_check ALL
+    COMMAND ${UPDATEREVISION_EXE} src/gitinfo.h
+    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+    DEPENDS updaterevision)
+
 set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -W -Wall")
-set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
\ No newline at end of file
+
+if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
+	set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDEBUG")
+endif()
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/botc_defs.bts	Sun Jan 19 20:16:00 2014 +0200
@@ -0,0 +1,172 @@
+#!botc 1.0
+
+// This file defines the functions and events for botc
+// Do not edit unless you know what you are doing!
+
+// =============================================================================
+// Function definitions
+// Syntax: number:name:returntype:numargs:maxargs[:argumentlist]
+//
+funcdef 0:changestate:void:1:1:int(newstate);
+funcdef 1:delay:void:1:1:int(tics);
+funcdef 2:rand:int:2:2:int(a):int(b);
+funcdef 3:StringsAreEqual:bool:2:2:str(string1):str(string2);
+funcdef 4:LookForPowerups:int:2:2:int(start):bool(visibilitycheck);
+funcdef 5:LookForWeapons:int:2:2:int(start):bool(visibilitycheck);
+funcdef 6:LookForAmmo:int:2:2:int(start):bool(visibilitycheck);
+funcdef 7:LookForBaseHealth:int:2:2:int(start):bool(visibilitycheck);
+funcdef 8:LookForBaseArmor:int:2:2:int(start):bool(visibilitycheck);
+funcdef 9:LookForSuperHealth:int:2:2:int(start):bool(visibilitycheck);
+funcdef 10:LookForSuperArmor:int:2:2:int(start):bool(visibilitycheck);
+funcdef 11:LookForPlayerEnemies:int:1:1:int(start);
+funcdef 12:GetClosestPlayerEnemy:int:0:0;
+funcdef 13:MoveLeft:void:1:1:int(speed);
+funcdef 14:MoveRight:void:1:1:int(speed);
+funcdef 15:MoveForward:void:1:1:int(speed);
+funcdef 16:MoveBackwards:void:1:1:int(speed);
+funcdef 17:StopMovement:void:0:0;
+funcdef 18:StopForwardMovement:void:0:0;
+funcdef 19:StopSidewaysMovement:void:0:0;
+funcdef 20:CheckTerrain:int:2:2:int(distance):int(angle);
+funcdef 21:PathToGoal:int:1:1:int(speed);
+funcdef 22:PathToLastKnownEnemyPosition:int:1:1:int(speed);
+funcdef 23:PathToLastHeardSound:int:1:1:int(speed);
+funcdef 24:Roam:int:1:1:int(speed);
+funcdef 25:GetPathingCostToItem:int:1:1:int(item);
+funcdef 26:GetDistanceToItem:int:1:1:int(item);
+funcdef 27:GetItemName:str:1:1:int(item);
+funcdef 28:IsItemVisible:bool:1:1:int(item);
+funcdef 29:SetGoal:void:1:1:int(item);
+funcdef 30:BeginAimingAtEnemy:void:0:0;
+funcdef 31:StopAimingAtEnemy:void:0:0;
+funcdef 32:Turn:void:1:1:int(turnangle);
+funcdef 33:GetCurrentAngle:int:0:0;
+funcdef 34:SetEnemy:void:1:1:int(player);
+funcdef 35:ClearEnemy:void:0:0;
+funcdef 36:IsEnemyAlive:bool:0:0;
+funcdef 37:IsEnemyVisible:bool:0:0;
+funcdef 38:GetDistanceToEnemy:int:0:0;
+funcdef 39:GetPlayerDamagedBy:int:0:0;
+funcdef 40:GetEnemyInvulnerabilityTicks:int:0:0;
+funcdef 41:FireWeapon:void:0:0;
+funcdef 42:BeginFiringWeapon:void:0:0;
+funcdef 43:StopFiringWeapon:void:0:0;
+funcdef 44:GetCurrentWeapon:str:0:0;
+funcdef 45:ChangeWeapon:void:1:1:str(weapon);
+funcdef 46:GetWeaponFromItem:str:1:1:int(item);
+funcdef 47:IsWeaponOwned:bool:1:1:int(item);
+funcdef 48:IsFavoriteWeapon:bool:1:1:str(weapon);
+funcdef 49:Say:void:1:1:str(message);
+funcdef 50:SayFromFile:void:2:2:str(filename):str(section);
+funcdef 51:SayFromChatFile:void:1:1:str(section);
+funcdef 52:BeginChatting:void:0:0;
+funcdef 53:StopChatting:void:0:0;
+funcdef 54:ChatSectionExists:bool:1:1:str(section);
+funcdef 55:ChatSectionExistsInFile:bool:2:2:str(filename):str(section);
+funcdef 56:GetLastChatString:str:0:0;
+funcdef 57:GetLastChatPlayer:str:0:0;
+funcdef 58:GetChatFrequency:int:0:0;
+funcdef 59:Jump:void:0:0;
+funcdef 60:BeginJumping:void:0:0;
+funcdef 61:StopJumping:void:0:0;
+funcdef 62:Taunt:void:0:0;
+funcdef 63:Respawn:void:0:0;
+funcdef 64:TryToJoinGame:void:0:0;
+funcdef 65:IsDead:bool:0:0;
+funcdef 66:IsSpectating:bool:0:0;
+funcdef 67:GetHealth:int:0:0;
+funcdef 68:GetArmor:int:0:0;
+funcdef 69:GetBaseHealth:int:0:0;
+funcdef 70:GetBaseArmor:int:0:0;
+funcdef 71:GetBotskill:int:0:0;
+funcdef 72:GetAccuracy:int:0:0;
+funcdef 73:GetIntellect:int:0:0;
+funcdef 74:GetAnticipation:int:0:0;
+funcdef 75:GetEvade:int:0:0;
+funcdef 76:GetReactionTime:int:0:0;
+funcdef 77:GetPerception:int:0:0;
+funcdef 78:SetSkillIncrease:void:1:1:bool(increase);
+funcdef 79:IsSkillIncreased:bool:0:0;
+funcdef 80:SetSkillDecrease:void:1:1:bool(decrease);
+funcdef 81:IsSkillDecreased:bool:0:0;
+funcdef 82:GetGameMode:int:0:0;
+funcdef 83:GetSpread:int:0:0;
+funcdef 84:GetLastJoinedPlayer:str:0:0;
+funcdef 85:GetPlayerName:str:1:1:int(player);
+funcdef 86:GetReceivedMedal:int:0:0;
+funcdef 87:ACS_Execute:void:1:5:int(script):int(map=0):int(arg0=0):int(arg1=0):int(arg2=0);
+funcdef 88:GetFavoriteWeapon:str:0:0;
+funcdef 89:SayFromLump:void:2:2:str(lump):str(section);
+funcdef 90:SayFromChatLump:void:1:1:str(section);
+funcdef 91:ChatSectionExistsInLump:bool:2:2:str(lump):str(section);
+funcdef 92:ChatSectionExistsInChatLump:bool:1:1:str(section);
+
+// =============================================================================
+// Events:
+// eventdef <number>:<name>();
+//
+eventdef 0:KilledByEnemy();
+eventdef 1:KilledByPlayer();
+eventdef 2:KilledBySelf();
+eventdef 3:KilledByEnvironment();
+eventdef 4:ReachedGoal();
+eventdef 5:GoalRemoved();
+eventdef 6:DamagedByPlayer();
+eventdef 7:PlayerSay();
+eventdef 8:EnemyKilled();
+eventdef 9:Respawned();
+eventdef 10:Intermission();
+eventdef 11:NewMap();
+eventdef 12:EnemyUsedFist();
+eventdef 13:EnemyUsedChainsaw();
+eventdef 14:EnemyFiredPistol();
+eventdef 15:EnemyFiredShotgun();
+eventdef 16:EnemyFiredSSG();
+eventdef 17:EnemyFiredChaingun();
+eventdef 18:EnemyFiredMinigun();
+eventdef 19:EnemyFiredRocket();
+eventdef 20:EnemyFiredGrenade();
+eventdef 21:EnemyFiredRailgun();
+eventdef 22:EnemyFiredPlasma();
+eventdef 23:EnemyFiredBFG();
+eventdef 24:EnemyFiredBFG10k();
+eventdef 25:PlayerUsedFist();
+eventdef 26:PlayerUsedChainsaw();
+eventdef 27:PlayerFiredPistol();
+eventdef 28:PlayerFiredShotgun();
+eventdef 29:PlayerFiredSSG();
+eventdef 30:PlayerFiredChaingun();
+eventdef 31:PlayerFiredMinigun();
+eventdef 32:PlayerFiredRocket();
+eventdef 33:PlayerFiredGrenade();
+eventdef 34:PlayerFiredRailgun();
+eventdef 35:PlayerFiredPlasma();
+eventdef 36:PlayerFiredBFG();
+eventdef 37:PlayerFiredBFG10k();
+eventdef 38:UsedFist();
+eventdef 39:UsedChainsaw();
+eventdef 40:FiredPistol();
+eventdef 41:FiredShotgun();
+eventdef 42:FiredSSG();
+eventdef 43:FiredChaingun();
+eventdef 44:FiredMinigun();
+eventdef 45:FiredRocket();
+eventdef 46:FiredGrenade();
+eventdef 47:FiredRailgun();
+eventdef 48:FiredPlasma();
+eventdef 49:FiredBFG();
+eventdef 50:FiredBFG10k();
+eventdef 51:PlayerJoinedGame();
+eventdef 52:JoinedGame();
+eventdef 53:DuelStartingCountdown();
+eventdef 54:DuelFight();
+eventdef 55:DuelWinSequence();
+eventdef 56:Spectating();
+eventdef 57:LMSStartingCountdown();
+eventdef 58:LMSFight();
+eventdef 59:LMSWinSequence();
+eventdef 60:WeaponChange();
+eventdef 61:EnemyBFGExplode();
+eventdef 62:PlayerBFGExplode();
+eventdef 63:BFGExplode();
+eventdef 64:ReceivedMedal();
\ No newline at end of file
--- a/commands.def	Sat Jan 18 02:11:45 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +0,0 @@
-/* This file defines the commands botc will treat as valid.
- * Do not edit unless you know what you are doing!
- *
- * Syntax: number:name:returntype:numargs:maxargs[:argumentlist]
- */
-0:changestate:void:1:1:int(newstate)
-1:delay:void:1:1:int(tics)
-2:rand:int:2:2:int(a):int(b)
-3:StringsAreEqual:bool:2:2:str(string1):str(string2)
-4:LookForPowerups:int:2:2:int(start):bool(visibilitycheck)
-5:LookForWeapons:int:2:2:int(start):bool(visibilitycheck)
-6:LookForAmmo:int:2:2:int(start):bool(visibilitycheck)
-7:LookForBaseHealth:int:2:2:int(start):bool(visibilitycheck)
-8:LookForBaseArmor:int:2:2:int(start):bool(visibilitycheck)
-9:LookForSuperHealth:int:2:2:int(start):bool(visibilitycheck)
-10:LookForSuperArmor:int:2:2:int(start):bool(visibilitycheck)
-11:LookForPlayerEnemies:int:1:1:int(start)
-12:GetClosestPlayerEnemy:int:0:0
-13:MoveLeft:void:1:1:int(speed)
-14:MoveRight:void:1:1:int(speed)
-15:MoveForward:void:1:1:int(speed)
-16:MoveBackwards:void:1:1:int(speed)
-17:StopMovement:void:0:0
-18:StopForwardMovement:void:0:0
-19:StopSidewaysMovement:void:0:0
-20:CheckTerrain:int:2:2:int(distance):int(angle)
-21:PathToGoal:int:1:1:int(speed)
-22:PathToLastKnownEnemyPosition:int:1:1:int(speed)
-23:PathToLastHeardSound:int:1:1:int(speed)
-24:Roam:int:1:1:int(speed)
-25:GetPathingCostToItem:int:1:1:int(item)
-26:GetDistanceToItem:int:1:1:int(item)
-27:GetItemName:str:1:1:int(item)
-28:IsItemVisible:bool:1:1:int(item)
-29:SetGoal:void:1:1:int(item)
-30:BeginAimingAtEnemy:void:0:0
-31:StopAimingAtEnemy:void:0:0
-32:Turn:void:1:1:int(turnangle)
-33:GetCurrentAngle:int:0:0
-34:SetEnemy:void:1:1:int(player)
-35:ClearEnemy:void:0:0
-36:IsEnemyAlive:bool:0:0
-37:IsEnemyVisible:bool:0:0
-38:GetDistanceToEnemy:int:0:0
-39:GetPlayerDamagedBy:int:0:0
-40:GetEnemyInvulnerabilityTicks:int:0:0
-41:FireWeapon:void:0:0
-42:BeginFiringWeapon:void:0:0
-43:StopFiringWeapon:void:0:0
-44:GetCurrentWeapon:str:0:0
-45:ChangeWeapon:void:1:1:str(weapon)
-46:GetWeaponFromItem:str:1:1:int(item)
-47:IsWeaponOwned:bool:1:1:int(item)
-48:IsFavoriteWeapon:bool:1:1:str(weapon)
-49:Say:void:1:1:str(message)
-50:SayFromFile:void:2:2:str(filename):str(section)
-51:SayFromChatFile:void:1:1:str(section)
-52:BeginChatting:void:0:0
-53:StopChatting:void:0:0
-54:ChatSectionExists:bool:1:1:str(section)
-55:ChatSectionExistsInFile:bool:2:2:str(filename):str(section)
-56:GetLastChatString:str:0:0
-57:GetLastChatPlayer:str:0:0
-58:GetChatFrequency:int:0:0
-59:Jump:void:0:0
-60:BeginJumping:void:0:0
-61:StopJumping:void:0:0
-62:Taunt:void:0:0
-63:Respawn:void:0:0
-64:TryToJoinGame:void:0:0
-65:IsDead:bool:0:0
-66:IsSpectating:bool:0:0
-67:GetHealth:int:0:0
-68:GetArmor:int:0:0
-69:GetBaseHealth:int:0:0
-70:GetBaseArmor:int:0:0
-71:GetBotskill:int:0:0
-72:GetAccuracy:int:0:0
-73:GetIntellect:int:0:0
-74:GetAnticipation:int:0:0
-75:GetEvade:int:0:0
-76:GetReactionTime:int:0:0
-77:GetPerception:int:0:0
-78:SetSkillIncrease:void:1:1:bool(increase)
-79:IsSkillIncreased:bool:0:0
-80:SetSkillDecrease:void:1:1:bool(decrease)
-81:IsSkillDecreased:bool:0:0
-82:GetGameMode:int:0:0
-83:GetSpread:int:0:0
-84:GetLastJoinedPlayer:str:0:0
-85:GetPlayerName:str:1:1:int(player)
-86:GetReceivedMedal:int:0:0
-87:ACS_Execute:void:1:5:int(script):int(map=0):int(arg0=0):int(arg1=0):int(arg2=0)
-88:GetFavoriteWeapon:str:0:0
-89:SayFromLump:void:2:2:str(lump):str(section)
-90:SayFromChatLump:void:1:1:str(section)
-91:ChatSectionExistsInLump:bool:2:2:str(lump):str(section)
-92:ChatSectionExistsInChatLump:bool:1:1:str(section)
\ No newline at end of file
--- a/events.def	Sat Jan 18 02:11:45 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-KilledByEnemy
-KilledByPlayer
-KilledBySelf
-KilledByEnvironment
-ReachedGoal
-GoalRemoved
-DamagedByPlayer
-PlayerSay
-EnemyKilled
-Respawned
-Intermission
-NewMap
-EnemyUsedFist
-EnemyUsedChainsaw
-EnemyFiredPistol
-EnemyFiredShotgun
-EnemyFiredSSG
-EnemyFiredChaingun
-EnemyFiredMinigun
-EnemyFiredRocket
-EnemyFiredGrenade
-EnemyFiredRailgun
-EnemyFiredPlasma
-EnemyFiredBFG
-EnemyFiredBFG10k
-PlayerUsedFist
-PlayerUsedChainsaw
-PlayerFiredPistol
-PlayerFiredShotgun
-PlayerFiredSSG
-PlayerFiredChaingun
-PlayerFiredMinigun
-PlayerFiredRocket
-PlayerFiredGrenade
-PlayerFiredRailgun
-PlayerFiredPlasma
-PlayerFiredBFG
-PlayerFiredBFG10k
-UsedFist
-UsedChainsaw
-FiredPistol
-FiredShotgun
-FiredSSG
-FiredChaingun
-FiredMinigun
-FiredRocket
-FiredGrenade
-FiredRailgun
-FiredPlasma
-FiredBFG
-FiredBFG10k
-PlayerJoinedGame
-JoinedGame
-DuelStartingCountdown
-DuelFight
-DuelWinSequence
-Spectating
-LMSStartingCountdown
-LMSFight
-LMSWinSequence
-WeaponChange
-EnemyBFGExplode
-PlayerBFGExplode
-BFGExplode
-ReceivedMedal
\ No newline at end of file
--- a/src/commands.cc	Sat Jan 18 02:11:45 2014 +0200
+++ b/src/commands.cc	Sun Jan 19 20:16:00 2014 +0200
@@ -40,100 +40,16 @@
 static list<command_info*> g_commands;
 
 // ============================================================================
-// Reads command definitions from commands.def and stores them to memory.
-void init_commands ()
+//
+void add_command_definition (command_info* comm)
 {
-	lexer lx;
-	lx.process_file ("commands.def");
-
-	while (lx.get_next())
-	{
-		command_info* comm = new command_info;
-
-		// Number
-		lx.must_be (tk_number);
-		comm->number = lx.get_token()->text.to_long();
-
-		lx.must_get_next (tk_colon);
-
-		// Name
-		lx.must_get_next (tk_symbol);
-		comm->name = lx.get_token()->text;
-
-		if (IsKeyword (comm->name))
-			error ("command name `%1` conflicts with keyword", comm->name);
-
-		lx.must_get_next (tk_colon);
-
-		// Return value
-		lx.must_get_any_of ({tk_int, tk_void, tk_bool, tk_str});
-		comm->returnvalue = GetTypeByName (lx.get_token()->text); // TODO
-		assert (comm->returnvalue != -1);
-
-		lx.must_get_next (tk_colon);
-
-		// Num args
-		lx.must_get_next (tk_number);
-		comm->numargs = lx.get_token()->text.to_long();
-
-		lx.must_get_next (tk_colon);
-
-		// Max args
-		lx.must_get_next (tk_number);
-		comm->maxargs = lx.get_token()->text.to_long();
-
-		// Argument types
-		int curarg = 0;
+	// Ensure that there is no conflicts
+	for (command_info* it : g_commands)
+		if (it->number == comm->number)
+			error ("Attempted to redefine command #%1 (%2) as %3",
+				g_commands[comm->number]->name, comm->name);
 
-		while (curarg < comm->maxargs)
-		{
-			command_argument arg;
-			lx.must_get_next (tk_colon);
-			lx.must_get_any_of ({tk_int, tk_bool, tk_str});
-			type_e type = GetTypeByName (lx.get_token()->text);
-			assert (type != -1 && type != TYPE_VOID);
-			arg.type = type;
-
-			lx.must_get_next (tk_paren_start);
-			lx.must_get_next (tk_symbol);
-			arg.name = lx.get_token()->text;
-
-			// If this is an optional parameter, we need the default value.
-			if (curarg >= comm->numargs)
-			{
-				lx.must_get_next (tk_assign);
-
-				switch (type)
-				{
-					case TYPE_INT:
-					case TYPE_BOOL:
-						lx.must_get_next (tk_number);
-						break;
-
-					case TYPE_STRING:
-						lx.must_get_next (tk_string);
-						break;
-
-					case TYPE_UNKNOWN:
-					case TYPE_VOID:
-						break;
-				}
-
-				arg.defvalue = lx.get_token()->text.to_long();
-			}
-
-			lx.must_get_next (tk_paren_end);
-			comm->args << arg;
-			curarg++;
-		}
-
-		g_commands << comm;
-	}
-
-	if (g_commands.is_empty())
-		error ("no commands defined!\n");
-
-	print ("%1 command definitions read.\n", g_commands.size());
+	g_commands << comm;
 }
 
 // ============================================================================
--- a/src/commands.h	Sat Jan 18 02:11:45 2014 +0200
+++ b/src/commands.h	Sun Jan 19 20:16:00 2014 +0200
@@ -51,7 +51,7 @@
 	list<command_argument>	args;
 };
 
-void						init_commands ();
+void						add_command_definition (command_info* comm);
 command_info*				find_command_by_name (string a);
 string						get_command_signature (command_info* comm);
 const list<command_info*>	get_commands();
--- a/src/containers.h	Sat Jan 18 02:11:45 2014 +0200
+++ b/src/containers.h	Sun Jan 19 20:16:00 2014 +0200
@@ -323,7 +323,7 @@
 		//
 		const element_type& last() const
 		{
-			return *(m_data.end());
+			return *(m_data.end() - 1);
 		}
 
 		// =====================================================================
--- a/src/events.cc	Sat Jan 18 02:11:45 2014 +0200
+++ b/src/events.cc	Sun Jan 19 20:16:00 2014 +0200
@@ -40,32 +40,17 @@
 static list<event_info*> g_events;
 
 // ============================================================================
-// Read event definitions from file
-void init_events()
+//
+void add_event (event_info* e)
 {
-	lexer lx;
-	lx.process_file ("events.def");
-	int num_events = 0;
-
-	while (lx.get_next())
-	{
-		lx.must_be (tk_symbol);
-		event_info* e = new event_info;
-		e->name = lx.get_token()->text;
-		e->number = num_events++;
-		g_events << e;
-	}
-
-	printf ("%d event definitions read.\n", num_events);
-	atexit (&unlink_events);
+	g_events << e;
 }
 
 // ============================================================================
 // Delete event definitions recursively
+//
 static void unlink_events()
 {
-	print ("Freeing event information.\n");
-
 	for (event_info* e : g_events)
 		delete e;
 
--- a/src/events.h	Sat Jan 18 02:11:45 2014 +0200
+++ b/src/events.h	Sun Jan 19 20:16:00 2014 +0200
@@ -39,7 +39,7 @@
 	int number;
 };
 
-void init_events();
+void add_event (event_info* e);
 event_info* find_event_by_index (int idx);
 event_info* find_event_by_name (string a);
 
--- a/src/format.cc	Sat Jan 18 02:11:45 2014 +0200
+++ b/src/format.cc	Sun Jan 19 20:16:00 2014 +0200
@@ -33,6 +33,8 @@
 #include "format.h"
 #include "lexer.h"
 
+// =============================================================================
+//
 static void draw_pos (const string& fmt, int pos)
 {
 	string rep (fmt);
@@ -111,12 +113,16 @@
 	return fmt;
 }
 
+// =============================================================================
+//
 void print_args (FILE* fp, const list<format_arg>& args)
 {
 	string out = format_args (args);
 	fprintf (fp, "%s", out.chars());
 }
 
+// =============================================================================
+//
 void do_error (string msg)
 {
 	lexer* lx = lexer::get_main_lexer();
--- a/src/lexer.cc	Sat Jan 18 02:11:45 2014 +0200
+++ b/src/lexer.cc	Sun Jan 19 20:16:00 2014 +0200
@@ -34,25 +34,33 @@
 static string_list	g_file_name_stack;
 static lexer*		g_main_lexer = null;
 
+// =============================================================================
+//
 lexer::lexer()
 {
 	assert (g_main_lexer == null);
 	g_main_lexer = this;
 }
 
+// =============================================================================
+//
 lexer::~lexer()
 {
 	g_main_lexer = null;
 }
 
+// =============================================================================
+//
 void lexer::process_file (string file_name)
 {
+	g_file_name_stack << file_name;
 	FILE* fp = fopen (file_name, "r");
 
 	if (fp == null)
 		error ("couldn't open %1 for reading: %2", file_name, strerror (errno));
 
 	lexer_scanner sc (fp);
+	check_file_header (sc);
 
 	while (sc.get_next_token())
 	{
@@ -82,13 +90,58 @@
 			tok.column = sc.get_column();
 			tok.type = sc.get_token_type();
 			tok.text = sc.get_token_text();
-			//	devf ("Token #%1: %2:%3:%4: %5 (%6)\n", m_tokens.size(),
-			//		tok.file, tok.line, tok.column, describe_token (&tok), describe_token_type (tok.type));
+
+			// devf ("Token #%1: %2:%3:%4: %5 (%6)\n", m_tokens.size(),
+			//	tok.file, tok.line, tok.column, describe_token (&tok), describe_token_type (tok.type));
+
 			m_tokens << tok;
 		}
 	}
 
 	m_token_position = m_tokens.begin() - 1;
+	g_file_name_stack.remove (file_name);
+}
+
+// ============================================================================
+//
+static bool is_valid_header (string header)
+{
+	if (header.ends_with ("\n"))
+		header.remove_from_end (1);
+
+	string_list tokens = header.split (" ");
+
+	if (tokens.size() != 2 || tokens[0] != "#!botc" || tokens[1].empty())
+		return false;
+
+	string_list nums = tokens[1].split (".");
+
+	if (nums.size() == 2)
+		nums << "0";
+	elif (nums.size() != 3)
+		return false;
+
+	bool ok_a, ok_b, ok_c;
+	long major = nums[0].to_long (&ok_a);
+	long minor = nums[1].to_long (&ok_b);
+	long patch = nums[2].to_long (&ok_c);
+
+	if (!ok_a || !ok_b || !ok_c)
+		return false;
+
+	if (VERSION_NUMBER < MAKE_VERSION_NUMBER (major, minor, patch))
+		error ("The script file requires " APPNAME " v%1, this is v%2",
+			make_version_string (major, minor, patch), get_version_string (e_short_form));
+
+	return true;
+}
+
+// ============================================================================
+//
+void lexer::check_file_header (lexer_scanner& sc)
+{
+	if (!is_valid_header (sc.read_line()))
+		error ("Not a valid botscript file! File must start with '#!botc <version>'");
 }
 
 // =============================================================================
@@ -125,14 +178,23 @@
 // =============================================================================
 // eugh..
 //
-void lexer::must_get_next_from_scanner (lexer_scanner& sc, e_token tok)
+void lexer::must_get_next_from_scanner (lexer_scanner& sc, e_token tt)
 {
 	if (!sc.get_next_token())
 		error ("unexpected EOF");
 
-	if (tok != tk_any && sc.get_token_type() != tok)
-		error ("expected %1, got %2", describe_token_type (tok),
-			   describe_token (get_token()));
+	if (tt != tk_any && sc.get_token_type() != tt)
+	{	// TODO
+		token tok;
+		tok.type = sc.get_token_type();
+		tok.text = sc.get_token_text();
+
+		error ("at %1:%2: expected %3, got %4",
+			g_file_name_stack.last(),
+			sc.get_line(),
+			describe_token_type (tt),
+			describe_token (&tok));
+	}
 }
 
 // =============================================================================
@@ -199,20 +261,11 @@
 
 	switch (tok_type)
 	{
-		case tk_symbol:
-			return tok ? tok->text : "a symbol";
-
-		case tk_number:
-			return tok ? tok->text : "a number";
-
-		case tk_string:
-			return tok ? ("\"" + tok->text + "\"") : "a string";
-
-		case tk_any:
-			return tok ? tok->text : "any token";
-
-		default:
-			break;
+		case tk_symbol:	return tok ? tok->text : "a symbol";
+		case tk_number:	return tok ? tok->text : "a number";
+		case tk_string:	return tok ? ("\"" + tok->text + "\"") : "a string";
+		case tk_any:	return tok ? tok->text : "any token";
+		default: break;
 	}
 
 	return "";
--- a/src/lexer.h	Sat Jan 18 02:11:45 2014 +0200
+++ b/src/lexer.h	Sun Jan 19 20:16:00 2014 +0200
@@ -46,20 +46,20 @@
 		int			column;
 	};
 
-	using token_list = list<token>;
-	using iterator = token_list::iterator;
+	using token_list	= list<token>;
+	using iterator		= token_list::iterator;
 
 public:
 	lexer();
 	~lexer();
 
-	void process_file (string file_name);
-	bool get_next (e_token req = tk_any);
-	void must_get_next (e_token tok = tk_any);
-	void must_get_any_of (const list<e_token>& toks);
-	int get_one_symbol (const string_list& syms);
-	void must_be (e_token tok);
-	bool peek_next (token* tk = null);
+	void	process_file (string file_name);
+	bool	get_next (e_token req = tk_any);
+	void	must_get_next (e_token tok = tk_any);
+	void	must_get_any_of (const list<e_token>& toks);
+	int		get_one_symbol (const string_list& syms);
+	void	must_be (e_token tok);
+	bool	peek_next (token* tk = null);
 
 	inline bool has_valid_token() const
 	{
@@ -107,7 +107,8 @@
 	iterator		m_token_position;
 
 	// read a mandatory token from scanner
-	void must_get_next_from_scanner (lexer_scanner& sc, e_token tok = tk_any);
+	void must_get_next_from_scanner (lexer_scanner& sc, e_token tt = tk_any);
+	void check_file_header (lexer_scanner& sc);
 
 	static string describe_token_private (e_token tok_type, token* tok);
 };
--- a/src/lexer_scanner.cc	Sat Jan 18 02:11:45 2014 +0200
+++ b/src/lexer_scanner.cc	Sun Jan 19 20:16:00 2014 +0200
@@ -77,7 +77,9 @@
 	"do",
 	"else",
 	"event",
+	"eventdef",
 	"for",
+	"funcdef",
 	"goto",
 	"if",
 	"int",
@@ -194,7 +196,7 @@
 		while (*m_ptr != '\"')
 		{
 			if (!*m_ptr)
-				return false;
+				error ("unterminated string");
 
 			if (check_string ("\\n"))
 			{
@@ -216,7 +218,7 @@
 		}
 
 		m_token_type = tk_string;
-		m_ptr++; // skip the final quote
+		skip(); // skip the final quote
 		return true;
 	}
 
@@ -233,13 +235,13 @@
 	{
 		m_token_type = tk_symbol;
 
-		while (m_ptr != '\0')
+		do
 		{
 			if (!is_symbol_char (*m_ptr, true))
 				break;
 
 			m_token_text += *m_ptr++;
-		}
+		} while (*m_ptr != '\0');
 
 		return true;
 	}
@@ -276,3 +278,15 @@
 	assert ((int) a <= tk_last_named_token);
 	return g_token_strings[a];
 }
+
+// =============================================================================
+//
+string lexer_scanner::read_line()
+{
+	string line;
+
+	while (*m_ptr != '\n')
+		line += *(m_ptr++);
+
+	return line;
+}
\ No newline at end of file
--- a/src/lexer_scanner.h	Sat Jan 18 02:11:45 2014 +0200
+++ b/src/lexer_scanner.h	Sun Jan 19 20:16:00 2014 +0200
@@ -65,6 +65,7 @@
 		lexer_scanner (FILE* fp);
 		~lexer_scanner();
 		bool get_next_token();
+		string read_line();
 
 		inline const string& get_token_text() const
 		{
--- a/src/main.cc	Sat Jan 18 02:11:45 2014 +0200
+++ b/src/main.cc	Sun Jan 19 20:16:00 2014 +0200
@@ -39,6 +39,7 @@
 #include "object_writer.h"
 #include "parser.h"
 #include "lexer.h"
+#include "gitinfo.h"
 
 // List of keywords
 const string_list g_Keywords =
@@ -84,7 +85,6 @@
 		// I guess there should be a better way to do this.
 		if (argc == 2 && !strcmp (argv[1], "-l"))
 		{
-			init_commands();
 			printf ("Begin list of commands:\n");
 			printf ("------------------------------------------------------\n");
 
@@ -99,13 +99,17 @@
 		// Print header
 		string header;
 		string headerline;
-		header = format ("%1 version %2.%3", APPNAME, VERSION_MAJOR, VERSION_MINOR);
+		header = format (APPNAME " version %1", get_version_string (e_long_form));
 
-		for (int i = 0; i < (header.len() / 2) - 1; ++i)
+#ifdef DEBUG
+		header += " (debug build)";
+#endif
+
+		for (int i = 0; i < header.len() / 2; ++i)
 			headerline += "-=";
 
 		headerline += '-';
-		print ("%1\n%2\n", header, headerline);
+		print ("%2\n\n%1\n\n%2\n\n", header, headerline);
 
 		if (argc < 2)
 		{
@@ -151,11 +155,6 @@
 			}
 		}
 
-		// Read definitions
-		printf ("Reading definitions...\n");
-		init_events();
-		init_commands();
-
 		// Prepare reader and writer
 		botscript_parser* r = new botscript_parser;
 		object_writer* w = new object_writer;
@@ -256,16 +255,40 @@
 {
 	switch (type)
 	{
-	case TYPE_INT: return "int"; break;
-
-	case TYPE_STRING: return "str"; break;
-
-	case TYPE_VOID: return "void"; break;
-
-	case TYPE_BOOL: return "bool"; break;
-
-	case TYPE_UNKNOWN: return "???"; break;
+		case TYPE_INT: return "int"; break;
+		case TYPE_STRING: return "str"; break;
+		case TYPE_VOID: return "void"; break;
+		case TYPE_BOOL: return "bool"; break;
+		case TYPE_UNKNOWN: return "???"; break;
 	}
 
 	return "";
 }
+// =============================================================================
+//
+
+string make_version_string (int major, int minor, int patch)
+{
+	string ver = format ("%1.%2", major, minor);
+
+	if (patch != 0)
+	{
+		ver += ".";
+		ver += patch;
+	}
+
+	return ver;
+}
+
+// =============================================================================
+//
+string get_version_string (form_length_e len)
+{
+	string tag (GIT_DESCRIPTION);
+	string version = tag;
+
+	if (tag.ends_with ("-pre") && len == e_long_form)
+		version += "-" + string (GIT_HASH).mid (0, 8);
+
+	return version;
+}
\ No newline at end of file
--- a/src/main.h	Sat Jan 18 02:11:45 2014 +0200
+++ b/src/main.h	Sun Jan 19 20:16:00 2014 +0200
@@ -47,8 +47,12 @@
 
 // Application name and version
 #define APPNAME "botc"
-#define VERSION_MAJOR 0
-#define VERSION_MINOR 999
+#define VERSION_MAJOR	1
+#define VERSION_MINOR	0
+#define VERSION_PATCH 	0
+
+#define MAKE_VERSION_NUMBER(MAJ, MIN, PAT) ((MAJ * 10000) + (MIN * 100) + PAT)
+#define VERSION_NUMBER MAKE_VERSION_NUMBER (VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH)
 
 // On Windows, files are case-insensitive
 #if (defined(WIN32) || defined(_WIN32) || defined(__WIN32)) && !defined(__CYGWIN__)
@@ -90,10 +94,14 @@
 // Shortcut for zeroing something
 #define ZERO(obj) memset (&obj, 0, sizeof (obj));
 
+enum form_length_e { e_long_form, e_short_form };
+
 string ObjectFileName (string s);
 bool fexists (string path);
 type_e GetTypeByName (string token);
 string GetTypeName (type_e type);
+string get_version_string (form_length_e len);
+string make_version_string (int major, int minor, int patch);
 
 // Make the parser's variables globally available
 extern int g_NumStates;
--- a/src/parser.cc	Sat Jan 18 02:11:45 2014 +0200
+++ b/src/parser.cc	Sun Jan 19 20:16:00 2014 +0200
@@ -185,6 +185,14 @@
 				parse_const();
 				break;
 
+			case tk_eventdef:
+				parse_eventdef();
+				break;
+
+			case tk_funcdef:
+				parse_funcdef();
+				break;
+
 			default:
 			{
 				// Check for labels
@@ -882,6 +890,110 @@
 	m_lx->must_get_next (tk_colon);
 }
 
+// =============================================================================
+//
+void botscript_parser::parse_eventdef()
+{
+	event_info* e = new event_info;
+
+	m_lx->must_get_next (tk_number);
+	e->number = token_string().to_long();
+	m_lx->must_get_next (tk_colon);
+	m_lx->must_get_next (tk_symbol);
+	e->name = m_lx->get_token()->text;
+	m_lx->must_get_next (tk_paren_start);
+	m_lx->must_get_next (tk_paren_end);
+	m_lx->must_get_next (tk_semicolon);
+	add_event (e);
+}
+
+// =============================================================================
+//
+void botscript_parser::parse_funcdef()
+{
+	command_info* comm = new command_info;
+
+	// Number
+	m_lx->must_get_next (tk_number);
+	comm->number = m_lx->get_token()->text.to_long();
+
+	m_lx->must_get_next (tk_colon);
+
+	// Name
+	m_lx->must_get_next (tk_symbol);
+	comm->name = m_lx->get_token()->text;
+
+	if (IsKeyword (comm->name))
+		error ("function name `%1` conflicts with keyword", comm->name);
+
+	m_lx->must_get_next (tk_colon);
+
+	// Return value
+	m_lx->must_get_any_of ({tk_int, tk_void, tk_bool, tk_str});
+	comm->returnvalue = GetTypeByName (m_lx->get_token()->text); // TODO
+	assert (comm->returnvalue != -1);
+
+	m_lx->must_get_next (tk_colon);
+
+	// Num args
+	m_lx->must_get_next (tk_number);
+	comm->numargs = m_lx->get_token()->text.to_long();
+
+	m_lx->must_get_next (tk_colon);
+
+	// Max args
+	m_lx->must_get_next (tk_number);
+	comm->maxargs = m_lx->get_token()->text.to_long();
+
+	// Argument types
+	int curarg = 0;
+
+	while (curarg < comm->maxargs)
+	{
+		command_argument arg;
+		m_lx->must_get_next (tk_colon);
+		m_lx->must_get_any_of ({tk_int, tk_bool, tk_str});
+		type_e type = GetTypeByName (m_lx->get_token()->text);
+		assert (type != -1 && type != TYPE_VOID);
+		arg.type = type;
+
+		m_lx->must_get_next (tk_paren_start);
+		m_lx->must_get_next (tk_symbol);
+		arg.name = m_lx->get_token()->text;
+
+		// If this is an optional parameter, we need the default value.
+		if (curarg >= comm->numargs)
+		{
+			m_lx->must_get_next (tk_assign);
+
+			switch (type)
+			{
+				case TYPE_INT:
+				case TYPE_BOOL:
+					m_lx->must_get_next (tk_number);
+					break;
+
+				case TYPE_STRING:
+					m_lx->must_get_next (tk_string);
+					break;
+
+				case TYPE_UNKNOWN:
+				case TYPE_VOID:
+					break;
+			}
+
+			arg.defvalue = m_lx->get_token()->text.to_long();
+		}
+
+		m_lx->must_get_next (tk_paren_end);
+		comm->args << arg;
+		curarg++;
+	}
+
+	m_lx->must_get_next (tk_semicolon);
+	add_command_definition (comm);
+}
+
 // ============================================================================
 // Parses a command call
 data_buffer* botscript_parser::ParseCommand (command_info* comm)
--- a/src/parser.h	Sat Jan 18 02:11:45 2014 +0200
+++ b/src/parser.h	Sun Jan 19 20:16:00 2014 +0200
@@ -203,6 +203,8 @@
 		void parse_block_end();
 		void parse_const();
 		void parse_label();
+		void parse_eventdef();
+		void parse_funcdef();
 };
 
 constant_info* find_constant (const string& tok);
--- a/src/str.cc	Sat Jan 18 02:11:45 2014 +0200
+++ b/src/str.cc	Sun Jan 19 20:16:00 2014 +0200
@@ -297,7 +297,7 @@
 
 // =============================================================================
 //
-string string::operator+ (const string data) const
+string string::operator+ (const string& data) const
 {
     string newString = *this;
     newString += data;
@@ -315,6 +315,17 @@
 
 // =============================================================================
 //
+string string::operator+ (int num) const
+{
+	string newstr = *this;
+	string numstr;
+	numstr.sprintf ("%d", num);
+	newstr += numstr;
+	return newstr;
+}
+
+// =============================================================================
+//
 string& string::operator+= (const string data)
 {
     append (data);
@@ -331,6 +342,15 @@
 
 // =============================================================================
 //
+string& string::operator+= (int num)
+{
+	string numstr;
+	numstr.sprintf ("%d", num);
+	return operator+= (numstr);
+}
+
+// =============================================================================
+//
 bool string::is_numeric() const
 {
     bool gotDot = false;
--- a/src/str.h	Sat Jan 18 02:11:45 2014 +0200
+++ b/src/str.h	Sun Jan 19 20:16:00 2014 +0200
@@ -90,10 +90,12 @@
 		void               trim (length_type n);
 		string             to_uppercase() const;
 
-		string             operator+ (const string data) const;
+		string             operator+ (const string& data) const;
 		string             operator+ (const char* data) const;
+		string             operator+ (int num) const;
 		string&            operator+= (const string data);
 		string&            operator+= (const char* data);
+		string&            operator+= (int num);
 
 		static string		from_number (int a);
 		static string		from_number (long a);
--- a/src/tokens.h	Sat Jan 18 02:11:45 2014 +0200
+++ b/src/tokens.h	Sun Jan 19 20:16:00 2014 +0200
@@ -79,30 +79,32 @@
 	tk_do,					// - 36
 	tk_else,				// - 37
 	tk_event,				// - 38
-	tk_for,					// - 39
-	tk_goto,				// ----- 40
-	tk_if,					// - 41
-	tk_int,					// - 42
-	tk_mainloop,			// - 43
-	tk_onenter,				// - 44
-	tk_onexit,				// ----- 45
-	tk_state,				// - 46
-	tk_switch,				// - 47
-	tk_str,					// - 48
-	tk_void,				// - 49
-	tk_while,				// ----- 50
+	tk_eventdef,			// - 39
+	tk_for,					// ----- 40
+	tk_funcdef,				// - 41
+	tk_goto,				// - 42
+	tk_if,					// - 43
+	tk_int,					// - 44
+	tk_mainloop,			// ----- 45
+	tk_onenter,				// - 46
+	tk_onexit,				// - 47
+	tk_state,				// - 48
+	tk_switch,				// - 49
+	tk_str,					// ----- 50
+	tk_void,				// - 51
+	tk_while,				// - 52
 
 	// These ones aren't implemented yet but I plan to do so, thus they are
 	// reserved. Also serves as a to-do list of sorts for me. >:F
-	tk_enum,				// - 51
-	tk_func,				// - 52
-	tk_return,				// - 53
+	tk_enum,				// - 53
+	tk_func,				// - 54
+	tk_return,				// ----- 55
 
 	// --------------
 	// Generic tokens
-	tk_symbol,				// - 54
-	tk_number,				// ----- 55
-	tk_string,				// - 56
+	tk_symbol,				// - 56
+	tk_number,				// - 57
+	tk_string,				// - 58
 
 	tk_first_named_token	= tk_bool,
 	tk_last_named_token		= (int) tk_symbol - 1,
--- a/src/types.h	Sat Jan 18 02:11:45 2014 +0200
+++ b/src/types.h	Sun Jan 19 20:16:00 2014 +0200
@@ -41,6 +41,20 @@
 	return (a >= 0) ? a : -a;
 }
 
+// =============================================================================
+// A simple basic exception
+//
+class simple_exception : public std::exception
+{
+	public:
+		simple_exception();
+
+		inline const char* what() const throw()
+		{
+			return "simple exception";
+		}
+};
+
 #ifdef IN_IDE_PARSER
 using FILE = void;
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/updaterevision/CMakeLists.txt	Sun Jan 19 20:16:00 2014 +0200
@@ -0,0 +1,3 @@
+cmake_minimum_required( VERSION 2.4 )
+
+add_executable (updaterevision updaterevision.c)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/updaterevision/updaterevision.c	Sun Jan 19 20:16:00 2014 +0200
@@ -0,0 +1,136 @@
+/* updaterevision.c
+ *
+ * Public domain. This program uses git commands command to get
+ * various bits of repository status for a particular directory
+ * and writes it into a header file so that it can be used for a
+ * project's versioning.
+ */
+
+#define _CRT_SECURE_NO_DEPRECATE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#ifdef _WIN32
+#define popen _popen
+#define pclose _pclose
+#endif
+
+// Used to strip newline characters from lines read by fgets.
+void stripnl(char *str)
+{
+	if (*str != '\0')
+	{
+		size_t len = strlen(str);
+		if (str[len - 1] == '\n')
+		{
+			str[len - 1] = '\0';
+		}
+	}
+}
+
+int main(int argc, char **argv)
+{
+	char vertag[64], lastlog[64], lasthash[64], *hash = NULL;
+	FILE *stream = NULL;
+	int gotrev = 0, needupdate = 1;
+
+	vertag[0] = '\0';
+	lastlog[0] = '\0';
+
+	if (argc != 2)
+	{
+		fprintf(stderr, "Usage: %s <path to gitinfo.h>\n", argv[0]);
+		return 1;
+	}
+
+	// Use git describe --tags to get a version string. If we are sitting directly
+	// on a tag, it returns that tag. Otherwise it returns <most recent tag>-<number of
+	// commits since the tag>-<short hash>.
+	// Use git log to get the time of the latest commit in ISO 8601 format and its full hash.
+	stream = popen("git describe --tags && git log -1 --format=%ai*%H", "r");
+
+	if (NULL != stream)
+	{
+		if (fgets(vertag, sizeof vertag, stream) == vertag &&
+			fgets(lastlog, sizeof lastlog, stream) == lastlog)
+		{
+			stripnl(vertag);
+			stripnl(lastlog);
+			gotrev = 1;
+		}
+
+		pclose(stream);
+	}
+
+	if (gotrev)
+	{
+		hash = strchr(lastlog, '*');
+		if (hash != NULL)
+		{
+			*hash = '\0';
+			hash++;
+		}
+	}
+	if (hash == NULL)
+	{
+		fprintf(stderr, "Failed to get commit info: %s\n", strerror(errno));
+		strcpy(vertag, "<unknown version>");
+		lastlog[0] = '\0';
+		lastlog[1] = '0';
+		lastlog[2] = '\0';
+		hash = lastlog + 1;
+	}
+
+	stream = fopen (argv[1], "r");
+	if (stream != NULL)
+	{
+		if (!gotrev)
+		{ // If we didn't get a revision but the file does exist, leave it alone.
+			fclose (stream);
+			return 0;
+		}
+		// Read the revision that's in this file already. If it's the same as
+		// what we've got, then we don't need to modify it and can avoid rebuilding
+		// dependant files.
+		if (fgets(lasthash, sizeof lasthash, stream) == lasthash)
+		{
+			stripnl(lasthash);
+			if (strcmp(hash, lasthash + 3) == 0)
+			{
+				needupdate = 0;
+			}
+		}
+		fclose (stream);
+	}
+
+	if (needupdate)
+	{
+		stream = fopen (argv[1], "w");
+		if (stream == NULL)
+		{
+			return 1;
+		}
+		fprintf(stream,
+"// %s\n"
+"//\n"
+"// This file was automatically generated by the\n"
+"// updaterevision tool. Do not edit by hand.\n"
+"\n"
+"#define GIT_DESCRIPTION \"%s\"\n"
+"#define GIT_HASH \"%s\"\n"
+"#define GIT_TIME \"%s\"\n",
+			hash, vertag, hash, lastlog);
+		fclose(stream);
+		fprintf(stderr, "%s updated to commit %s.\n", argv[1], vertag);
+	}
+	else
+	{
+		fprintf (stderr, "%s is up to date at commit %s.\n", argv[1], vertag);
+	}
+
+	return 0;
+}

mercurial