[variables] (v0.18) 	TIdentifier.AddValue now sets the variable's parent to itself 	Added SetParent and GetParent to TVaraible

Rem
	Copyright (c) 2009 Tim Howard

	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:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	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.
	-----------------------------------------------------------------------------

	variables.bmx (Contains: TV_INTEGER, TV_STRING, TV_FLOAT, TV_EVAL,
							TVariable, TStringVariable, TFloatVariable, TIntVariable, TEvalVariable, TIdentifier,
							TVariableMap, )

End Rem

SuperStrict

Rem
bbdoc: Variables module
End Rem
Module duct.variables

ModuleInfo "Version: 0.18"
ModuleInfo "Copyright: Tim Howard"
ModuleInfo "License: MIT"

ModuleInfo "History: Version 0.18"
ModuleInfo "History: TIdentifier.AddValue now sets the variable's parent to itself"
ModuleInfo "History: Added SetParent and GetParent to TVaraible"
ModuleInfo "History: Version 0.17"
ModuleInfo "History: General cleanup"
ModuleInfo "History: Version 0.16"
ModuleInfo "History: Added SetAmbiguous to all TVariable types"
ModuleInfo "History: Added option to always use quoting with TStringVariables (on by default)"
ModuleInfo "History: Fixed TStringVariable.ConvToString returning of Null strings"
ModuleInfo "History: Version 0.15"
ModuleInfo "History: Change the Create method definition for easier use"
ModuleInfo "History: Changed the '/eval::' recognizer to '/e:' (as per SNode format change)"
ModuleInfo "History: Added the Copy method to every variable type"
ModuleInfo "History: Changed some formatting"
ModuleInfo "History: Version 0.14"
ModuleInfo "History: Changed type tabbing"
ModuleInfo "History: Fixed script output for TStringVariable (quotes are now only added if whitespace is present)"
ModuleInfo "History: Fixed script output for TIdentifier (identifier names can now contain whitespace, in which case they must be quoted)"
ModuleInfo "History: Version 0.13"
ModuleInfo "History: Added the DeSerializeUniversal function to TVariable"
ModuleInfo "History: Added Serialize and DeSerialize methods to all variable types"
ModuleInfo "History: Version 0.12"
ModuleInfo "History: Changed module name from 'variablemap' to 'variables'"
ModuleInfo "History: Moved all of duct.utilparser here"
ModuleInfo "History: Added the GetTVType function to all variable types"
ModuleInfo "History: Moved TV_* constants from duct.template here"
ModuleInfo "History: Version 0.11"
ModuleInfo "History: Added the ReportType function to all variable types"
ModuleInfo "History: Version 0.10"
ModuleInfo "History: Added the RawToVariable function to TVariable"
ModuleInfo "History: General code cleanup"
ModuleInfo "History: Version 0.09"
ModuleInfo "History: Corrected usage of syntax (in Returns, Cases, News and Selects)"
ModuleInfo "History: Version 0.08"
ModuleInfo "History: Added the TEvalVariable type"
ModuleInfo "History: Initial release"

Import brl.stream
Import brl.linkedlist

Import duct.etc
Import duct.objectmap

Rem
	bbdoc: Template variable type for the TIntVariable type.
End Rem
Const TV_INTEGER:Int = 1
Rem
	bbdoc: Template variable type for the TTStringVariable type.
End Rem
Const TV_STRING:Int = 2
Rem
	bbdoc: Template variable type for the TFloatVariable type.
End Rem
Const TV_FLOAT:Int = 3
Rem
	bbdoc: Template variable type for the TEvalVariable type.
End Rem
Const TV_EVAL:Int = 4
Rem
	bbdoc: Template variable type for the TIdentifier type.
End Rem
Const TV_IDEN:Int = 5

Rem
	bbdoc: The Variable type.
	about: This is the base variable type, you should extend from this to use it.
End Rem
Type TVariable Abstract

	Field m_name:String
	Field m_parent:TVariable

'#region Field accessors

	Rem
		bbdoc: Set the variable's name.
		returns: Nothing.
	End Rem
	Method SetName(name:String)
		m_name = name
	End Method

	Rem
		bbdoc: Get the variable's name.
		returns: The variable's name.
	End Rem
	Method GetName:String()
		Return m_name
	End Method

	Rem
		bbdoc: Set the variable's parent.
		returns: Nothing.
	End Rem
	Method SetParent(parent:TVariable)
		m_parent = parent
	End Method

	Rem
		bbdoc: Get the variable's parent.
		returns: The variable's parent.
	End Rem
	Method GetParent:TVariable()
		Return m_parent
	End Method

	Rem
		bbdoc: Base method for setting the value of the variable to the given string (conversion).
	End Rem
	Method SetAmbiguous(value:String) Abstract

	Rem
		bbdoc: Base method for converting variable data to a script-ready string.
	End Rem
	Method ConvToString:String() Abstract

	Rem
		bbdoc: Base method for converting variable data to a printable/usable-in-code string.
	End Rem
	Method ValueAsString:String() Abstract

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable.
		returns: A clone of the variable.
	End Rem
	Method Copy:TVariable() Abstract

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False) Abstract

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method DeSerialize:TVariable(stream:TStream, tv:Int = True, name:Int = False) Abstract

'#end region (Data handling)

	Rem
		bbdoc: Base method: Get the type of this variable.
	End Rem
	Function ReportType:String() Abstract

	Rem
		bbdoc: Base method: Get the TV_* type of this variable.
	End Rem
	Function GetTVType:Int() Abstract

	Rem
		bbdoc: Convert raw data (raw data being things like: "/eval::(a+b/0.4181)*a-b" - an EvalVariable, "A String variable1!!", 3452134 - an IntVariable, 1204.00321 - a FloatVariable) into a Variable.
		returns: A new Variable, or Null if something whacky occured.
		about: @etype is optional, it is used to go automagically to one type of a Variable (1 & 4=String (will check for '/eval::' - EvalVariables), 2=Integer, 3=Float).

		@varname is also an optional parameter. It will be used as the name of the variable.
	End Rem
	Function RawToVariable:TVariable(vraw:String, etype:Int = 0, varname:String = "")
		Local variable:TVariable

		If vraw = Null
			DebugLog("(TVariable.RawToVariable) @vraw = Null; returning StringVariable (with @varname and Null value)")
			Return New TStringVariable.Create(varname, Null)
		End If

		'If etype = 1 ' Explicitly a string
		'
		'	variable = TVariable(New TStringVariable.Create("", vraw))
		'
		If etype = 0 ' Determine the value's type (must be either integer, double, or a string with no spaces)
			Local i:Int

			' ASCII '0' to '9' = 48-57; '-' = 45, '+' = 43; and '.' = 46
			For i = 0 To vraw.Length - 1
				Local c:Int

				c = vraw[i]
				If c >= 48 And c <= 57 Or c = 43 Or c = 45
					If etype = 0 ' Leave float and string alone
						etype = 2 ' Integer so far..
					End If
				Else If c = 46
					If etype = 2 ' Already declared as an integer?
						etype = 3
					End If
				Else ' If the character is not numerical there is nothing else to deduce and the value is a string
					etype = 4
					Exit
				End If
			Next

			Select etype
				Case 2 ' Integer
					variable = TVariable(New TIntVariable.Create(varname, Int(vraw)))
				Case 3 ' Double/Float
					variable = TVariable(New TFloatVariable.Create(varname, Float(vraw)))
				'Case 4 ' String - non-explicit
				'  Local evaltest:Int = vraw.ToLower().Find("/eval::")
				'
				'	If evaltest >= 0
				'		variable = TVariable(New TEvalVariable.Create(varname, vraw[evaltest + 7..]))
				'	Else
				'		variable = TVariable(New TStringVariable.Create(varname, vraw))
				'	End If
			End Select

		End If

		If etype = 1 Or etype = 4
			Local evaltest:Int = vraw.ToLower().Find("/e:")
			If evaltest >= 0
				variable = TVariable(New TEvalVariable.Create(varname, vraw[evaltest + 7..]))
			Else
				variable = TVariable(New TStringVariable.Create(varname, vraw))
			End If
		End If
		' DebugLog("TSNode.LoadScriptFromStream().RawToVariable(); vraw = ~q" + vraw + "~q \" + etype)
		?Debug
		If variable = Null Then DebugLog("( TVariable.RawToVariable() ) Unknown error, 'variable' is Null.")
		?
		Return variable
	End Function

	Rem
		bbdoc: Universally deserialize a variable from the given stream.
		returns: A DeSerialized variable.
		about: This will deserialize any variable from the stream.

		This requires the variable to have been serialized with the template type (see #Serialize parameters).

		@name tells the further DeSerialize calls if the name should be DeSerialized or not.
	End Rem
	Function DeSerializeUniversal:TVariable(stream:TStream, name:Int = False)
		Local tv:Int

		tv = Int(stream.ReadByte())

		Select tv
			Case TV_INTEGER
				Return New TIntVariable.DeSerialize(stream, True, name)
			Case TV_STRING
				Return New TStringVariable.DeSerialize(stream, True, name)
			Case TV_FLOAT
				Return New TFloatVariable.DeSerialize(stream, True, name)
			Case TV_EVAL
				Return New TEvalVariable.DeSerialize(stream, True, name)
			Case TV_IDEN
				Return New TIdentifier.DeSerialize(stream, True, name)

			Default
				DebugLog("(TVariable.DeSerializeUniversal) Failed to recognize the TV in the stream: " + tv)

		End Select
		Return Null
	End Function

End Type

Rem
	bbdoc: The TStringVariable type.
End Rem
Type TStringVariable Extends TVariable

	Global m_alwaysusequotes:Int = True

	Field m_value:String

	Rem
		bbdoc: Create a new StringVariable.
		returns: The new StringVariable (itself).
	End Rem
	Method Create:TStringVariable(name:String = Null, value:String)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the variable's value.
		returns: Nothing.
	End Rem
	Method Set(value:String)
		m_value = value
	End Method

	Rem
		bbdoc: Get the variable's value.
		returns: The value of the variable.
	End Rem
	Method Get:String()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the TStringVariable to the given string (ambiguous).
		returns: Nothing.
		about: For this type, this method is no different than calling #Set.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = value
	End Method

	Rem
		bbdoc: Convert the variable to a String.
		returns: A String representation of the variable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		If m_alwaysusequotes = True Or m_value.Contains("~t") Or m_value.Contains(" ") Or m_value = Null
			Return "~q" + m_value + "~q"
		Else
			Return m_value
		End If
	End Method

	Rem
		bbdoc: Get the StringVariable as a String.
		returns: The variable's value converted to a String.
		about: Here for complete-ness, no difference to `instance.Get()`.
	End Rem
	Method ValueAsString:String()
		Return m_value
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:TStringVariable()
		Return New TStringVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.WriteByte(TV_STRING)
		End If
		If name = True
			WriteLString(stream, m_name)
		End If

		WriteLString(stream, m_value)
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method DeSerialize:TStringVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.ReadByte()
		End If
		If name = True
			m_name = ReadLString(stream)
		End If

		m_value = ReadLString(stream)
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("string").
	End Rem
	Function ReportType:String()
		Return "string"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_STRING).
	End Rem
	Function GetTVType:Int()
		Return TV_STRING
	End Function

End Type

Rem
	bbdoc: The TFloatVariable type.
End Rem
Type TFloatVariable Extends TVariable

	Field m_value:Float

	Rem
		bbdoc: Create a new FloatVariable.
		returns: The new FloatVariable (itself).
	End Rem
	Method Create:TFloatVariable(name:String = Null, value:Float)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the variable's value.
		returns: Nothing.
	End Rem
	Method Set(value:Float)
		m_value = value
	End Method

	Rem
		bbdoc: Get the variable's value.
		returns: The value of the variable.
	End Rem
	Method Get:Float()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the variable to the given string (ambiguous).
		returns: Nothing.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = Float(value)
	End Method

	Rem
		bbdoc: Convert the variable to a String.
		returns: A scriptable form of the StringVariable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		Local conv:String = String(m_value), i:Int, encountered:Int

		For i = conv.Find(".") To conv.Length - 1
			If conv[i] = 48
				If encountered = True
					conv = conv[..i]
					Exit
				End If
			Else If conv[i] <> 46
				encountered = True
			End If
		Next

		Return conv
	End Method

	Rem
		bbdoc: Get the FloatVariable as a String.
		returns: The variable's value converted to a String.
	End Rem
	Method ValueAsString:String()
		Return String(m_value)
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:TFloatVariable()
		Return New TFloatVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.WriteByte(TV_FLOAT)
		End If
		If name = True
			WriteLString(stream, m_name)
		End If
		stream.WriteFloat(m_value)
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method DeSerialize:TFloatVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.ReadByte()
		End If
		If name = True
			m_name = ReadLString(stream)
		End If
		m_value = stream.ReadFloat()
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("float").
	End Rem
	Function ReportType:String()
		Return "float"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_FLOAT).
	End Rem
	Function GetTVType:Int()
		Return TV_FLOAT
	End Function

End Type

Rem
	bbdoc: The TIntVariable type.
End Rem
Type TIntVariable Extends TVariable

	Field m_value:Int

	Rem
		bbdoc: Create a new IntVariable.
		returns: The new IntVariable (itself).
	End Rem
	Method Create:TIntVariable(name:String = Null, value:Int)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the variable's value.
		returns: Nothing.
	End Rem
	Method Set(value:Int)
		m_value = value
	End Method

	Rem
		bbdoc: Get the variable's value.
		returns: The value of the variable.
	End Rem
	Method Get:Int()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the variable to the given string (ambiguous).
		returns: Nothing.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = Int(value)
	End Method

	Rem
		bbdoc: Convert the variable to a String.
		returns: A string representation of the variable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		Return String(m_value)
	End Method

	Rem
		bbdoc: Get the IntVariable as a String.
		returns: The variable value converted to a String.
	End Rem
	Method ValueAsString:String()
		Return String(m_value)
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:TIntVariable()
		Return New TIntVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.WriteByte(TV_INTEGER)
		End If
		If name = True
			WriteLString(stream, m_name)
		End If

		stream.WriteInt(m_value)
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method DeSerialize:TIntVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.ReadByte()
		End If
		If name = True
			m_name = ReadLString(stream)
		End If

		m_value = stream.ReadInt()
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("int").
	End Rem
	Function ReportType:String()
		Return "int"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_INTEGER).
	End Rem
	Function GetTVType:Int()
		Return TV_INTEGER
	End Function

End Type

Rem
	bbdoc: The TEvalVariable type.
End Rem
Type TEvalVariable Extends TVariable

	Field m_value:String

	Rem
		bbdoc: Create a new EvalVariable.
		returns: The new EvalVariable (itself).
	End Rem
	Method Create:TEvalVariable(name:String = Null, value:String)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the equation string for the EvalVariable.
		returns: Nothing
	End Rem
	Method Set(value:String)
		m_value = value
	End Method

	Rem
		bbdoc: Get the equation string.
		returns: The equation string.
	End Rem
	Method Get:String()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the variable to the given string (ambiguous).
		returns: Nothing.
		about: For this type, this method is no different than calling #Set.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = value
	End Method

	Rem
		bbdoc: Convert the EvalVariable to a visual representation of its data.
		returns: The scriptable form of the EvalVariable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		Return "~q/e:" + m_value + "~q"
	End Method

	Rem
		bbdoc: Get the EvalVariable as a String.
		returns: The variable value converted to a String.
	End Rem
	Method ValueAsString:String()
		Return m_value
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:TEvalVariable()
		Return New TEvalVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.WriteByte(TV_EVAL)
		End If
		If name = True
			WriteLString(stream, m_name)
		End If

		WriteLString(stream, m_value)
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method DeSerialize:TEvalVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.ReadByte()
		End If
		If name = True
			m_name = ReadLString(stream)
		End If

		m_value = ReadLString(stream)
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("eval").
	End Rem
	Function ReportType:String()
		Return "eval"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_EVAL).
	End Rem
	Function GetTVType:Int()
		Return TV_EVAL
	End Function

End Type

Rem
	bbdoc: The Identifier type.
End Rem
Type TIdentifier Extends TVariable

	Field m_values:TList

	Method New()
	End Method

	Rem
		bbdoc: Create a new Identifier.
		returns: The new Identfier (itself).
	End Rem
	Method Create:TIdentifier()
		m_values = New TList
		Return Self
	End Method

	Rem
		bbdoc: Create a new Identifier with the given data.
		returns: The new Identfier (itself).
		about: If the @values parameter is Null a new list will be created.
	End Rem
	Method CreateByData:TIdentifier(name:String, values:TList = Null)
		SetName(name)
		If values = Null
			m_values = New TList
		Else
			m_values = values
		End If
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the Identifier's values.
		returns: Nothing.
	End Rem
	Method SetValues(values:TList)
		m_values = values
	End Method

	Rem
		bbdoc: Get the Identifier's values.
		returns: A list containing the values which the Identifier holds.
	End Rem
	Method GetValues:TList()
		Return m_values
	End Method

	Rem
		bbdoc: Deprecated for this type.
		returns: Nothing.
		about: This does nothing.
	End Rem
	Method SetAmbiguous(value:String)
	End Method

	Rem
		bbdoc: Convert the identifier to a string.
		returns: A string representation of the identifier.
	End Rem
	Method ConvToString:String()
		Local op:String, variable:TVariable

		If m_name.Contains("~t") = True Or m_name.Contains(" ") = True
			op = "~q" + m_name + "~q "
		Else
			op = m_name + " "
		End If

		For variable = EachIn m_values
			op:+variable.ConvToString() + " "
		Next
		op = op[..op.Length - 1]
		Return op
	End Method

	Rem
		bbdoc: Get the Identifier as a string.
		returns: The Identifier contents converted to a string.
		about: Here for complete-ness, simply calls #ConvToString.
	End Rem
	Method ValueAsString:String()
		Return ConvToString()
	End Method

'#end region (Field accessors)

'#region Value handling

	Rem
		bbdoc: Get a value at an index.
		returns: The value in the identifier's list at the given index, or Null if it could not be retrieved.
		about: The index is zero-based.
	End Rem
	Method GetValueAtIndex:TVariable(index:Int)
		If m_values <> Null And index > - 1 And index < m_values.Count()
			Return TVariable(m_values.ValueAtIndex(index))
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the number of values.
		returns: The number of values the Identifier contains.
	End Rem
	Method GetValueCount:Int()
		If m_values <> Null
			Return m_values.Count()
		End If
		Return 0
	End Method

	Rem
		bbdoc: Add a value to the Identifier.
		returns: True for success, or False for failure.
		about: NOTE: This will set the given value's parent.
	End Rem
	Method AddValue:Int(value:TVariable)
		If m_values <> Null And value <> Null
			m_values.AddLast(value)
			value.SetParent(Self)
			Return True
		End If
		Return False
	End Method

'#end region (Value handling)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:TIdentifier()
		Local clone:TIdentifier
		clone = New TIdentifier.CreateByData(m_name)
		For Local variable:TVariable = EachIn m_values
			clone.AddValue(variable.Copy())
		Next
		Return clone
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.

		In this case @name tells the method whether it should serialize the values' name (the Identifier's name is always read/written to the stream).
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		Local variable:TVariable

		If tv = True
			stream.WriteByte(TV_STRING)
		End If
		WriteLString(stream, m_name)

		If m_values = Null
			stream.WriteInt(0)
		Else
			stream.WriteInt(m_values.Count())
			For variable = EachIn m_values
				variable.Serialize(stream, True, name)
			Next
		End If
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.

		In this case @name tells the method whether it should deserialize the values' name (the Identifier's name is always read/written to the stream).
	End Rem
	Method DeSerialize:TIdentifier(stream:TStream, tv:Int = True, name:Int = False)
		Local count:Int, n:Int

		If tv = True
			stream.ReadByte()
		End If
		m_name = ReadLString(stream)

		count = stream.ReadInt()
		If count > 0
			For n = 0 To count - 1
				DeSerializeUniversal(stream, name)
			Next
		End If

		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("identifier").
	End Rem
	Function ReportType:String()
		Return "identifier"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_IDEN).
	End Rem
	Function GetTVType:Int()
		Return TV_IDEN
	End Function

End Type

Rem
	bbdoc: The VariableMap type.
EndRem
Type TVariableMap Extends TObjectMap

	Rem
		bbdoc: Create a new VariableMap.
		returns: The new VariableMap (itself).
	End Rem
	Method Create:TVariableMap()
		Return Self
	End Method

'#region Collections

	Rem
		bbdoc: Insert a variable into the map.
		returns: True if the variable was added, or False if it was not (the variable's name is Null).
	End Rem
	Method InsertVariable:Int(variable:TVariable)
		If variable.GetName() <> Null
			_Insert(variable.GetName(), variable)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Remove a variable by the name given.
		returns: True if a variable was removed, or False if it was not (the map contains no variable with the name given).
		about: The name is case-sensitive.
	End Rem
	Method RemoveVariableByName:Int(name:String)
		Return _Remove(name)
	End Method

	Rem
		bbdoc: Get a variable from the map by its name.
		returns: The variable object, or if the variable was not found, Null.
		about: The name is case-sensitive.
	End Rem
	Method GetVariableByName:TVariable(name:String)
		Return TVariable(_ValueByKey(name))
	End Method

'#end region (Collections)

End Type

Rem
	Copyright (c) 2009 Tim Howard

	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:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	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.
	-----------------------------------------------------------------------------

	variables.bmx (Contains: TV_INTEGER, TV_STRING, TV_FLOAT, TV_EVAL,
							TVariable, TStringVariable, TFloatVariable, TIntVariable, TEvalVariable, TIdentifier,
							TVariableMap, )

End Rem

SuperStrict

Rem
bbdoc: Variables module
End Rem
Module duct.variables

ModuleInfo "Version: 0.17"
ModuleInfo "Copyright: Tim Howard"
ModuleInfo "License: MIT"

ModuleInfo "History: Version 0.17"
ModuleInfo "History: General cleanup"
ModuleInfo "History: Version 0.16"
ModuleInfo "History: Added SetAmbiguous to all TVariable types"
ModuleInfo "History: Added option to always use quoting with TStringVariables (on by default)"
ModuleInfo "History: Fixed TStringVariable.ConvToString returning of Null strings"
ModuleInfo "History: Version 0.15"
ModuleInfo "History: Change the Create method definition for easier use"
ModuleInfo "History: Changed the '/eval::' recognizer to '/e:' (as per SNode format change)"
ModuleInfo "History: Added the Copy method to every variable type"
ModuleInfo "History: Changed some formatting"
ModuleInfo "History: Version 0.14"
ModuleInfo "History: Changed type tabbing"
ModuleInfo "History: Fixed script output for TStringVariable (quotes are now only added if whitespace is present)"
ModuleInfo "History: Fixed script output for TIdentifier (identifier names can now contain whitespace, in which case they must be quoted)"
ModuleInfo "History: Version 0.13"
ModuleInfo "History: Added the DeSerializeUniversal function to TVariable"
ModuleInfo "History: Added Serialize and DeSerialize methods to all variable types"
ModuleInfo "History: Version 0.12"
ModuleInfo "History: Changed module name from 'variablemap' to 'variables'"
ModuleInfo "History: Moved all of duct.utilparser here"
ModuleInfo "History: Added the GetTVType function to all variable types"
ModuleInfo "History: Moved TV_* constants from duct.template here"
ModuleInfo "History: Version 0.11"
ModuleInfo "History: Added the ReportType function to all variable types"
ModuleInfo "History: Version 0.10"
ModuleInfo "History: Added the RawToVariable function to TVariable"
ModuleInfo "History: General code cleanup"
ModuleInfo "History: Version 0.09"
ModuleInfo "History: Corrected usage of syntax (in Returns, Cases, News and Selects)"
ModuleInfo "History: Version 0.08"
ModuleInfo "History: Added the TEvalVariable type"
ModuleInfo "History: Initial release"

Import brl.stream
Import brl.linkedlist

Import duct.etc
Import duct.objectmap

Rem
	bbdoc: Template variable type for the TIntVariable type.
End Rem
Const TV_INTEGER:Int = 1
Rem
	bbdoc: Template variable type for the TTStringVariable type.
End Rem
Const TV_STRING:Int = 2
Rem
	bbdoc: Template variable type for the TFloatVariable type.
End Rem
Const TV_FLOAT:Int = 3
Rem
	bbdoc: Template variable type for the TEvalVariable type.
End Rem
Const TV_EVAL:Int = 4
Rem
	bbdoc: Template variable type for the TIdentifier type.
End Rem
Const TV_IDEN:Int = 5

Rem
	bbdoc: The Variable type.
	about: This is the base variable type, you should extend from this to use it.
End Rem
Type TVariable Abstract

	Field m_name:String

'#region Field accessors

	Rem
		bbdoc: Set the variable's name.
		returns: Nothing.
	End Rem
	Method SetName(name:String)
		m_name = name
	End Method

	Rem
		bbdoc: Get the variable's name.
		returns: The variable's name.
	End Rem
	Method GetName:String()
		Return m_name
	End Method

	Rem
		bbdoc: Base method for setting the value of the variable to the given string (conversion).
	End Rem
	Method SetAmbiguous(value:String) Abstract

	Rem
		bbdoc: Base method for converting variable data to a script-ready string.
	End Rem
	Method ConvToString:String() Abstract

	Rem
		bbdoc: Base method for converting variable data to a printable/usable-in-code string.
	End Rem
	Method ValueAsString:String() Abstract

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable.
		returns: A clone of the variable.
	End Rem
	Method Copy:TVariable() Abstract

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False) Abstract

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method DeSerialize:TVariable(stream:TStream, tv:Int = True, name:Int = False) Abstract

'#end region (Data handling)

	Rem
		bbdoc: Base method: Get the type of this variable.
	End Rem
	Function ReportType:String() Abstract

	Rem
		bbdoc: Base method: Get the TV_* type of this variable.
	End Rem
	Function GetTVType:Int() Abstract

	Rem
		bbdoc: Convert raw data (raw data being things like: "/eval::(a+b/0.4181)*a-b" - an EvalVariable, "A String variable1!!", 3452134 - an IntVariable, 1204.00321 - a FloatVariable) into a Variable.
		returns: A new Variable, or Null if something whacky occured.
		about: @etype is optional, it is used to go automagically to one type of a Variable (1 & 4=String (will check for '/eval::' - EvalVariables), 2=Integer, 3=Float).

		@varname is also an optional parameter. It will be used as the name of the variable.
	End Rem
	Function RawToVariable:TVariable(vraw:String, etype:Int = 0, varname:String = "")
		Local variable:TVariable

		If vraw = Null
			DebugLog("(TVariable.RawToVariable) @vraw = Null; returning StringVariable (with @varname and Null value)")
			Return New TStringVariable.Create(varname, Null)
		End If

		'If etype = 1 ' Explicitly a string
		'
		'	variable = TVariable(New TStringVariable.Create("", vraw))
		'
		If etype = 0 ' Determine the value's type (must be either integer, double, or a string with no spaces)
			Local i:Int

			' ASCII '0' to '9' = 48-57; '-' = 45, '+' = 43; and '.' = 46
			For i = 0 To vraw.Length - 1
				Local c:Int

				c = vraw[i]
				If c >= 48 And c <= 57 Or c = 43 Or c = 45
					If etype = 0 ' Leave float and string alone
						etype = 2 ' Integer so far..
					End If
				Else If c = 46
					If etype = 2 ' Already declared as an integer?
						etype = 3
					End If
				Else ' If the character is not numerical there is nothing else to deduce and the value is a string
					etype = 4
					Exit
				End If
			Next

			Select etype
				Case 2 ' Integer
					variable = TVariable(New TIntVariable.Create(varname, Int(vraw)))
				Case 3 ' Double/Float
					variable = TVariable(New TFloatVariable.Create(varname, Float(vraw)))
				'Case 4 ' String - non-explicit
				'  Local evaltest:Int = vraw.ToLower().Find("/eval::")
				'
				'	If evaltest >= 0
				'		variable = TVariable(New TEvalVariable.Create(varname, vraw[evaltest + 7..]))
				'	Else
				'		variable = TVariable(New TStringVariable.Create(varname, vraw))
				'	End If
			End Select

		End If

		If etype = 1 Or etype = 4
			Local evaltest:Int = vraw.ToLower().Find("/e:")
			If evaltest >= 0
				variable = TVariable(New TEvalVariable.Create(varname, vraw[evaltest + 7..]))
			Else
				variable = TVariable(New TStringVariable.Create(varname, vraw))
			End If
		End If
		' DebugLog("TSNode.LoadScriptFromStream().RawToVariable(); vraw = ~q" + vraw + "~q \" + etype)
		?Debug
		If variable = Null Then DebugLog("( TVariable.RawToVariable() ) Unknown error, 'variable' is Null.")
		?
		Return variable
	End Function

	Rem
		bbdoc: Universally deserialize a variable from the given stream.
		returns: A DeSerialized variable.
		about: This will deserialize any variable from the stream.

		This requires the variable to have been serialized with the template type (see #Serialize parameters).

		@name tells the further DeSerialize calls if the name should be DeSerialized or not.
	End Rem
	Function DeSerializeUniversal:TVariable(stream:TStream, name:Int = False)
		Local tv:Int

		tv = Int(stream.ReadByte())

		Select tv
			Case TV_INTEGER
				Return New TIntVariable.DeSerialize(stream, True, name)
			Case TV_STRING
				Return New TStringVariable.DeSerialize(stream, True, name)
			Case TV_FLOAT
				Return New TFloatVariable.DeSerialize(stream, True, name)
			Case TV_EVAL
				Return New TEvalVariable.DeSerialize(stream, True, name)
			Case TV_IDEN
				Return New TIdentifier.DeSerialize(stream, True, name)

			Default
				DebugLog("(TVariable.DeSerializeUniversal) Failed to recognize the TV in the stream: " + tv)

		End Select
		Return Null
	End Function

End Type

Rem
	bbdoc: The TStringVariable type.
End Rem
Type TStringVariable Extends TVariable

	Global m_alwaysusequotes:Int = True

	Field m_value:String

	Rem
		bbdoc: Create a new StringVariable.
		returns: The new StringVariable (itself).
	End Rem
	Method Create:TStringVariable(name:String = Null, value:String)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the variable's value.
		returns: Nothing.
	End Rem
	Method Set(value:String)
		m_value = value
	End Method

	Rem
		bbdoc: Get the variable's value.
		returns: The value of the variable.
	End Rem
	Method Get:String()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the TStringVariable to the given string (ambiguous).
		returns: Nothing.
		about: For this type, this method is no different than calling #Set.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = value
	End Method

	Rem
		bbdoc: Convert the variable to a String.
		returns: A String representation of the variable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		If m_alwaysusequotes = True Or m_value.Contains("~t") Or m_value.Contains(" ") Or m_value = Null
			Return "~q" + m_value + "~q"
		Else
			Return m_value
		End If
	End Method

	Rem
		bbdoc: Get the StringVariable as a String.
		returns: The variable's value converted to a String.
		about: Here for complete-ness, no difference to `instance.Get()`.
	End Rem
	Method ValueAsString:String()
		Return m_value
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:TStringVariable()
		Return New TStringVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.WriteByte(TV_STRING)
		End If
		If name = True
			WriteLString(stream, m_name)
		End If

		WriteLString(stream, m_value)
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method DeSerialize:TStringVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.ReadByte()
		End If
		If name = True
			m_name = ReadLString(stream)
		End If

		m_value = ReadLString(stream)
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("string").
	End Rem
	Function ReportType:String()
		Return "string"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_STRING).
	End Rem
	Function GetTVType:Int()
		Return TV_STRING
	End Function

End Type

Rem
	bbdoc: The TFloatVariable type.
End Rem
Type TFloatVariable Extends TVariable

	Field m_value:Float

	Rem
		bbdoc: Create a new FloatVariable.
		returns: The new FloatVariable (itself).
	End Rem
	Method Create:TFloatVariable(name:String = Null, value:Float)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the variable's value.
		returns: Nothing.
	End Rem
	Method Set(value:Float)
		m_value = value
	End Method

	Rem
		bbdoc: Get the variable's value.
		returns: The value of the variable.
	End Rem
	Method Get:Float()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the variable to the given string (ambiguous).
		returns: Nothing.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = Float(value)
	End Method

	Rem
		bbdoc: Convert the variable to a String.
		returns: A scriptable form of the StringVariable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		Local conv:String = String(m_value), i:Int, encountered:Int

		For i = conv.Find(".") To conv.Length - 1
			If conv[i] = 48
				If encountered = True
					conv = conv[..i]
					Exit
				End If
			Else If conv[i] <> 46
				encountered = True
			End If
		Next

		Return conv
	End Method

	Rem
		bbdoc: Get the FloatVariable as a String.
		returns: The variable's value converted to a String.
	End Rem
	Method ValueAsString:String()
		Return String(m_value)
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:TFloatVariable()
		Return New TFloatVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.WriteByte(TV_FLOAT)
		End If
		If name = True
			WriteLString(stream, m_name)
		End If
		stream.WriteFloat(m_value)
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method DeSerialize:TFloatVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.ReadByte()
		End If
		If name = True
			m_name = ReadLString(stream)
		End If
		m_value = stream.ReadFloat()
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("float").
	End Rem
	Function ReportType:String()
		Return "float"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_FLOAT).
	End Rem
	Function GetTVType:Int()
		Return TV_FLOAT
	End Function

End Type

Rem
	bbdoc: The TIntVariable type.
End Rem
Type TIntVariable Extends TVariable

	Field m_value:Int

	Rem
		bbdoc: Create a new IntVariable.
		returns: The new IntVariable (itself).
	End Rem
	Method Create:TIntVariable(name:String = Null, value:Int)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the variable's value.
		returns: Nothing.
	End Rem
	Method Set(value:Int)
		m_value = value
	End Method

	Rem
		bbdoc: Get the variable's value.
		returns: The value of the variable.
	End Rem
	Method Get:Int()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the variable to the given string (ambiguous).
		returns: Nothing.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = Int(value)
	End Method

	Rem
		bbdoc: Convert the variable to a String.
		returns: A string representation of the variable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		Return String(m_value)
	End Method

	Rem
		bbdoc: Get the IntVariable as a String.
		returns: The variable value converted to a String.
	End Rem
	Method ValueAsString:String()
		Return String(m_value)
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:TIntVariable()
		Return New TIntVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.WriteByte(TV_INTEGER)
		End If
		If name = True
			WriteLString(stream, m_name)
		End If

		stream.WriteInt(m_value)
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method DeSerialize:TIntVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.ReadByte()
		End If
		If name = True
			m_name = ReadLString(stream)
		End If

		m_value = stream.ReadInt()
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("int").
	End Rem
	Function ReportType:String()
		Return "int"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_INTEGER).
	End Rem
	Function GetTVType:Int()
		Return TV_INTEGER
	End Function

End Type

Rem
	bbdoc: The TEvalVariable type.
End Rem
Type TEvalVariable Extends TVariable

	Field m_value:String

	Rem
		bbdoc: Create a new EvalVariable.
		returns: The new EvalVariable (itself).
	End Rem
	Method Create:TEvalVariable(name:String = Null, value:String)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the equation string for the EvalVariable.
		returns: Nothing
	End Rem
	Method Set(value:String)
		m_value = value
	End Method

	Rem
		bbdoc: Get the equation string.
		returns: The equation string.
	End Rem
	Method Get:String()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the variable to the given string (ambiguous).
		returns: Nothing.
		about: For this type, this method is no different than calling #Set.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = value
	End Method

	Rem
		bbdoc: Convert the EvalVariable to a visual representation of its data.
		returns: The scriptable form of the EvalVariable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		Return "~q/e:" + m_value + "~q"
	End Method

	Rem
		bbdoc: Get the EvalVariable as a String.
		returns: The variable value converted to a String.
	End Rem
	Method ValueAsString:String()
		Return m_value
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:TEvalVariable()
		Return New TEvalVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.WriteByte(TV_EVAL)
		End If
		If name = True
			WriteLString(stream, m_name)
		End If

		WriteLString(stream, m_value)
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method DeSerialize:TEvalVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.ReadByte()
		End If
		If name = True
			m_name = ReadLString(stream)
		End If

		m_value = ReadLString(stream)
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("eval").
	End Rem
	Function ReportType:String()
		Return "eval"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_EVAL).
	End Rem
	Function GetTVType:Int()
		Return TV_EVAL
	End Function

End Type

Rem
	bbdoc: The Identifier type.
End Rem
Type TIdentifier Extends TVariable

	Field m_values:TList

	Method New()
	End Method

	Rem
		bbdoc: Create a new Identifier.
		returns: The new Identfier (itself).
	End Rem
	Method Create:TIdentifier()
		m_values = New TList
		Return Self
	End Method

	Rem
		bbdoc: Create a new Identifier with the given data.
		returns: The new Identfier (itself).
		about: If the @values parameter is Null a new list will be created.
	End Rem
	Method CreateByData:TIdentifier(name:String, values:TList = Null)
		SetName(name)
		If values = Null
			m_values = New TList
		Else
			m_values = values
		End If
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the Identifier's values.
		returns: Nothing.
	End Rem
	Method SetValues(values:TList)
		m_values = values
	End Method

	Rem
		bbdoc: Get the Identifier's values.
		returns: A list containing the values which the Identifier holds.
	End Rem
	Method GetValues:TList()
		Return m_values
	End Method

	Rem
		bbdoc: Deprecated for this type.
		returns: Nothing.
		about: This does nothing.
	End Rem
	Method SetAmbiguous(value:String)
	End Method

	Rem
		bbdoc: Convert the identifier to a string.
		returns: A string representation of the identifier.
	End Rem
	Method ConvToString:String()
		Local op:String, variable:TVariable

		If m_name.Contains("~t") = True Or m_name.Contains(" ") = True
			op = "~q" + m_name + "~q "
		Else
			op = m_name + " "
		End If

		For variable = EachIn m_values
			op:+variable.ConvToString() + " "
		Next
		op = op[..op.Length - 1]
		Return op
	End Method

	Rem
		bbdoc: Get the Identifier as a string.
		returns: The Identifier contents converted to a string.
		about: Here for complete-ness, simply calls #ConvToString.
	End Rem
	Method ValueAsString:String()
		Return ConvToString()
	End Method

'#end region (Field accessors)

'#region Value handling

	Rem
		bbdoc: Get a value at an index.
		returns: The value in the identifier's list at the given index, or Null if it could not be retrieved.
		about: The index is zero-based.
	End Rem
	Method GetValueAtIndex:TVariable(index:Int)
		If m_values <> Null And index > - 1 And index < m_values.Count()
			Return TVariable(m_values.ValueAtIndex(index))
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the number of values.
		returns: The number of values the Identifier contains.
	End Rem
	Method GetValueCount:Int()
		If m_values <> Null
			Return m_values.Count()
		End If
		Return 0
	End Method

	Rem
		bbdoc: Add a value to the Identifier.
		returns: True for success, or False for failure.
	End Rem
	Method AddValue:Int(value:TVariable)
		If m_values <> Null And value <> Null
			m_values.AddLast(value)
			Return True
		End If
		Return False
	End Method

'#end region (Value handling)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:TIdentifier()
		Local clone:TIdentifier
		clone = New TIdentifier.CreateByData(m_name)
		For Local variable:TVariable = EachIn m_values
			clone.AddValue(variable.Copy())
		Next
		Return clone
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.

		In this case @name tells the method whether it should serialize the values' name (the Identifier's name is always read/written to the stream).
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		Local variable:TVariable

		If tv = True
			stream.WriteByte(TV_STRING)
		End If
		WriteLString(stream, m_name)

		If m_values = Null
			stream.WriteInt(0)
		Else
			stream.WriteInt(m_values.Count())
			For variable = EachIn m_values
				variable.Serialize(stream, True, name)
			Next
		End If
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.

		In this case @name tells the method whether it should deserialize the values' name (the Identifier's name is always read/written to the stream).
	End Rem
	Method DeSerialize:TIdentifier(stream:TStream, tv:Int = True, name:Int = False)
		Local count:Int, n:Int

		If tv = True
			stream.ReadByte()
		End If
		m_name = ReadLString(stream)

		count = stream.ReadInt()
		If count > 0
			For n = 0 To count - 1
				DeSerializeUniversal(stream, name)
			Next
		End If

		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("identifier").
	End Rem
	Function ReportType:String()
		Return "identifier"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_IDEN).
	End Rem
	Function GetTVType:Int()
		Return TV_IDEN
	End Function

End Type

Rem
	bbdoc: The VariableMap type.
EndRem
Type TVariableMap Extends TObjectMap

	Rem
		bbdoc: Create a new VariableMap.
		returns: The new VariableMap (itself).
	End Rem
	Method Create:TVariableMap()
		Return Self
	End Method

'#region Collections

	Rem
		bbdoc: Insert a variable into the map.
		returns: True if the variable was added, or False if it was not (the variable's name is Null).
	End Rem
	Method InsertVariable:Int(variable:TVariable)
		If variable.GetName() <> Null
			_Insert(variable.GetName(), variable)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Remove a variable by the name given.
		returns: True if a variable was removed, or False if it was not (the map contains no variable with the name given).
		about: The name is case-sensitive.
	End Rem
	Method RemoveVariableByName:Int(name:String)
		Return _Remove(name)
	End Method

	Rem
		bbdoc: Get a variable from the map by its name.
		returns: The variable object, or if the variable was not found, Null.
		about: The name is case-sensitive.
	End Rem
	Method GetVariableByName:TVariable(name:String)
		Return TVariable(_ValueByKey(name))
	End Method

'#end region (Collections)

End Type


Update pick sample to API change.

Update pick sample to API change.

Strict

Import MaxB3D.Drivers

Graphics 800,600
HideMouse()
SeedRnd MilliSecs()

Local sphere:TMesh = CreateSphere()
SetEntityPickMode sphere, PICKMODE_SPHERE

For Local k = 0 To 99
	Local model:TEntity = CopyEntity(sphere)
	SetEntityColor model, Rand(255), Rand(255), Rand(255)
	SetEntityShine model, Rnd()

	Local rad# = Rnd(1, 2)
	SetEntityRadius model, rad
	SetEntityScale model, rad, rad, rad
	TurnEntity model, Rand(360), Rand(360), 0
	MoveEntity model, 0, 0, Rand(20)+20
Next

FreeEntity sphere

Local light:TLight = CreateLight()
TurnEntity light, 45, 45, 0

Local camera:TCamera = CreateCamera()
SetCameraRange camera, 0.1, 1000

Local zoom# = 1, entity:TEntity

While Not KeyDown(KEY_ESCAPE) And Not AppTerminate()
	If KeyDown(KEY_A) zoom :* 1.1
	If KeyDown(KEY_Z) zoom :/ 1.1
	SetCameraZoom camera, zoom

	Local x# = MouseX(), y# = MouseY()

	If y<32 TurnEntity camera,-2,0,0
	If y>GraphicsHeight()-32 TurnEntity camera,2,0,0

	If x<32 TurnEntity camera,0,2,0
	If x>GraphicsWidth()-32 TurnEntity camera,0,-2,0

	Local pick:TPick = WorldPick(camera, [x, y])
	If pick
		SetEntityColor pick.entity, 255,0,0
	EndIf
	Rem
	e=CameraPick( camera,x,y )

	If e<>entity

		If entity Then EntityAlpha entity,1

		entity=e

	EndIf
	End Rem

	If entity SetEntityAlpha entity,Sin( MilliSecs() )*.5+.5

	RenderWorld
	DoMax2D
	DrawRect x,y-3,1,7
	DrawRect x-3,y,7,1

	Flip
Wend

Strict

Import MaxB3D.Drivers

Graphics 800,600
HideMouse()
SeedRnd MilliSecs()

Local sphere:TMesh = CreateSphere()
SetEntityPickMode sphere, PICKMODE_SPHERE

For Local k = 0 To 99
	Local model:TEntity = CopyEntity(sphere)
	SetEntityColor model, Rand(255), Rand(255), Rand(255)
	SetEntityShine model, Rnd()

	Local rad# = Rnd(1, 2)
	SetEntityRadius model, rad
	SetEntityScale model, rad, rad, rad
	TurnEntity model, Rand(360), Rand(360), 0
	MoveEntity model, 0, 0, Rand(20)+20
Next

FreeEntity sphere

Local light:TLight = CreateLight()
TurnEntity light, 45, 45, 0

Local camera:TCamera = CreateCamera()
SetCameraRange camera, 0.1, 1000

Local zoom# = 1, entity:TEntity

While Not KeyDown(KEY_ESCAPE) And Not AppTerminate()
	If KeyDown(KEY_A) zoom :* 1.1
	If KeyDown(KEY_Z) zoom :/ 1.1
	SetCameraZoom camera, zoom

	Local x# = MouseX(), y# = MouseY()

	If y<32 TurnEntity camera,-2,0,0
	If y>GraphicsHeight()-32 TurnEntity camera,2,0,0

	If x<32 TurnEntity camera,0,2,0
	If x>GraphicsWidth()-32 TurnEntity camera,0,-2,0

	Local picks:TPick[] = WorldPick(camera, [x, y])
	For Local pick:TPick = EachIn picks
		SetEntityColor pick.entity, 255,0,0
	Next
	Rem
	e=CameraPick( camera,x,y )

	If e<>entity

		If entity Then EntityAlpha entity,1

		entity=e

	EndIf
	End Rem

	If entity SetEntityAlpha entity,Sin( MilliSecs() )*.5+.5

	RenderWorld
	DoMax2D
	DrawRect x,y-3,1,7
	DrawRect x-3,y,7,1

	Flip
Wend


Don't add extra newlines, mxLogger handles this

refs gh-17

Type mxLoggerObserverGUI

	Field m_output:Object

	Method Reset()
		If TGadget(m_output)
			SetGadgetText(TGadget(m_output), "")
		End If
	End Method

	Method SendMessage:Object(message:Object, context:Object)
		Local msg:String = String(message)
		If String(context) = "error"
			Notify(msg, True)
		Else If TGadget(m_output)
			Local gadget:TGadget = TGadget(m_output)
			SetGadgetText(gadget, GadgetText(gadget) + String(message))
			'Refresh GUI
			Driver.Poll
		Else If m_output = Null
			Notify(msg)
		End If
	End Method

End Type

Type mxLoggerObserverGUI

	Field m_output:Object

	Method Reset()
		If TGadget(m_output)
			SetGadgetText(TGadget(m_output), "")
		End If
	End Method

	Method SendMessage:Object(message:Object, context:Object)
		Local msg:String = String(message)
		If String(context) = "error"
			Notify(msg, True)
		Else If TGadget(m_output)
			Local gadget:TGadget = TGadget(m_output)
			SetGadgetText(gadget, GadgetText(gadget) + "~n" + String(message))
			'Refresh GUI
			Driver.Poll
		Else If m_output = Null
			Notify(msg)
		End If
	End Method

End Type


[argparser] (v0.4) 	Changed parsing for single-dash options

Rem
Copyright (c) 2010 Tim Howard

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:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

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.
End Rem

SuperStrict

Rem
bbdoc: Application argument parser
End Rem
Module duct.argparser

ModuleInfo "Version: 0.4"
ModuleInfo "Copyright: Tim Howard"
ModuleInfo "License: MIT"

ModuleInfo "History: Version 0.4"
ModuleInfo "History: Changed parsing for single-dash options"
ModuleInfo "History: Version 0.3"
ModuleInfo "History: Fixed argument parsing for commands"
ModuleInfo "History: Version 0.2"
ModuleInfo "History: Updated for API change"
ModuleInfo "History: Version 0.1"
ModuleInfo "History: Initial version."

Import duct.variables

Rem
	bbdoc: duct argument parser.
End Rem
Type dArgParser

	Rem
		bbdoc: Parse the given arguments string array.
		returns: The root identifier containing the parsed arguments.
		about: The expected value does not (by default) contain the first argument of AppArgs (the application location).

		You can override this by passing @fullargs as True. If you leave it as False, the root identifier's name will be set to the first argument (which should be the application location - if you're passing AppArgs).

		@optarglimit limits how many arguments can be given to an option (options start with "--" or "-"). If set to -1, there is no limit.

		Single-dash options (e.g. "-a") are not parsed for arguments, whereas double-dash options (e.g. "--foobar wakka") will be.
	End Rem
	Function ParseArray:dIdentifier(args:String[], fullargs:Int = False, optarglimit:Int = 1)
		Local root:dIdentifier = New dIdentifier.Create()
		If fullargs = True
			root.SetName(args[0])
			args = args[1..]
		End If
		Local arg:String, i:Int, length:Int = args.Length - 1
		Local sub:dIdentifier, subset:Int = False
		optarglimit = (optarglimit = -1) And length Or optarglimit
		For i = 0 To length
			arg = args[i]
			sub = New dIdentifier.CreateByData(arg, Null)
			If arg <> Null And arg[0] = 45 ' "-"
				If arg[1] = 45 ' "-"
					Local lim:Int = Min(length, i + optarglimit)
					i:+1
					While i <= length
						arg = args[i]
						If arg = Null Or arg[0] <> 45 ' "-"
							sub.AddValue(dVariable.RawToVariable(arg))
							i:+1
							If i > lim
								i:-1
								Exit
							End If
						Else
							i:-1
							Exit
						End If
					End While
					root.AddValue(sub)
				Else
					root.AddValue(sub)
				End If
			Else
				If subset = False
					root.AddValue(sub)
					root = sub
					subset = True
				Else
					root.AddValue(dVariable.RawToVariable(arg))
				End If
			End If
		Next
		While Not root.GetParent() = Null
			root = dIdentifier(root.GetParent())
		End While
		Return root
	End Function

End Type

Rem
Copyright (c) 2010 Tim Howard

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:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

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.
End Rem

SuperStrict

Rem
bbdoc: Application argument parser
End Rem
Module duct.argparser

ModuleInfo "Version: 0.3"
ModuleInfo "Copyright: Tim Howard"
ModuleInfo "License: MIT"

ModuleInfo "History: Version 0.3"
ModuleInfo "History: Fixed argument parsing for commands"
ModuleInfo "History: Version 0.2"
ModuleInfo "History: Updated for API change"
ModuleInfo "History: Version 0.1"
ModuleInfo "History: Initial version."

Import duct.variables

Rem
	bbdoc: duct argument parser.
End Rem
Type dArgParser

	Rem
		bbdoc: Parse the given arguments string array.
		returns: The root identifier containing the parsed arguments.
		about: The expected value does not (by default) contain the first argument of AppArgs (the application location).

		You can override this by passing @fullargs as True. If you leave it as False, the root identifier's name will be set to the first argument (which should be the application location - if you're passing AppArgs).

		@optarglimit limits how many arguments can be given to an option (options start with "--" or "-"). If set to -1, there is no limit.
	End Rem
	Function ParseArray:dIdentifier(args:String[], fullargs:Int = False, optarglimit:Int = 1)
		Local root:dIdentifier = New dIdentifier.Create()
		If fullargs = True
			root.SetName(args[0])
			args = args[1..]
		End If
		Local arg:String, i:Int, length:Int = args.Length - 1
		Local sub:dIdentifier, subset:Int = False
		optarglimit = (optarglimit = -1) And length Or optarglimit
		For i = 0 To length
			arg = args[i]
			sub = New dIdentifier.CreateByData(arg, Null)
			If arg <> Null And arg[0] = 45 ' "-"
				Local lim:Int = Min(length, i + optarglimit)
				i:+1
				While i <= length
					arg = args[i]
					If arg = Null Or arg[0] <> 45 ' "-"
						sub.AddValue(dVariable.RawToVariable(arg))
						i:+1
						If i > lim
							i:-1
							Exit
						End If
					Else
						i:-1
						Exit
					End If
				End While
				root.AddValue(sub)
			Else
				If subset = False
					root.AddValue(sub)
					root = sub
					subset = True
				Else
					root.AddValue(dVariable.RawToVariable(arg))
				End If
			End If
		Next
		While Not root.GetParent() = Null
			root = dIdentifier(root.GetParent())
		End While
		Return root
	End Function

End Type


Create 4Djulia.bmx

Create 4Djulia.bmx

SuperStrict
Framework brl.GLGraphics
Import brl.TIMER
Import brl.TextStream
Import pub.OpenGL
Import pub.Glew
Import brl.Retro
Import brl.PNGLoader

Const GW:Int = 640, GH:Int = 360

If AppArgs.Length > 1
	TAnimFrame.LoadFromFile(AppArgs[1])
End If

Global TIMER:TTimer = CreateTimer(30)
Global VERTEX_SHADER_CODE:String = LoadText("vs.glsl")
Global FRAGMENT_SHADER_CODE:String = LoadText("fs.glsl")

InitGL
Global ProgramObj:Int
InitShaders
glUseProgram(ProgramObj)

Global camS:Float = 0.04
Global camX:Float = -0.9, camY:Float = -1.35, camZ:Float = -3.6, camW:Float = 0.0
Global camP:Float = 0.2, camT:Float = -0.37

Global lightX:Float = -1.6, lightY:Float = -0.9, lightZ:Float = -2.4

Global camZoom:Float = 1.0

Global cx:Float = 0, cy:Float = 0, cz:Float = 0, cw:Float = 0
Global itr:Int = 11
Global eps:Float = 0.001

Global disphelp:Int = 0

Global cf:TAnimFrame, cflink:TLink

'Render()
'SaveSceenshot()
'End

Global MDown:Int, MXS:Int, MYS:Int
Global zwsp:Float
While Not KeyHit(KEY_ESCAPE)
	MDown = MouseDown(MOUSE_LEFT) Shl 2 + MouseDown(MOUSE_MIDDLE) Shl 1 + MouseDown(MOUSE_RIGHT)
	MXS = MouseXSpeed()
	MYS = MouseYSpeed()
	If MDown & %100
		MoveMouse GW / 2, GH / 2
		MouseXSpeed()
		MouseYSpeed()
	End If

	CheckInput()

	Render()

	If disphelp Then glUseProgram(0) DrawHelp() glUseProgram(ProgramObj)

	Flip 0
	glCls
	WaitTimer TIMER
Wend
End

Function CheckInput()
	If KeyDown(KEY_2) camS:*1.05 Else If KeyDown(KEY_1) camS:/1.05

	If KeyDown(KEY_4) eps:*1.1 Else If KeyDown(KEY_3) eps:/1.1

	itr:+KeyHit(KEY_6) - KeyHit(KEY_5)

	If KeyDown(KEY_8) camZoom:*1.05 Else If KeyDown(KEY_7) camZoom:/1.05

	If MDown & 010
		lightX = camX
		lightY = camY
		lightZ = camZ
	End If

	If KeyDown(KEY_W)
		camX = camX + cos_(camT) * sin_(camP) * camS
		camY = camY - sin_(camT) * camS
		camZ = camZ + cos_(camT) * cos_(camP) * camS
	End If
	If KeyDown(KEY_S)
		camX = camX - cos_(camT) * sin_(camP) * camS
		camY = camY + sin_(camT) * camS
		camZ = camZ - cos_(camT) * cos_(camP) * camS
	End If
	If KeyDown(KEY_D)
		camX = camX + sin_(camP + 1.57079633) * camS
		camZ = camZ + cos_(camP + 1.57079633) * camS
	End If
	If KeyDown(KEY_A)
		camX = camX - sin_(camP + 1.57079633) * camS
		camZ = camZ - cos_(camP + 1.57079633) * camS
	End If
	camY:+(KeyDown(KEY_LSHIFT) - KeyDown(KEY_SPACE)) * camS
	camW:+(KeyDown(KEY_E) - KeyDown(KEY_Q)) * camS

	If MDown & %100
		camT:-MYS * 0.01'(KeyDown(KEY_UP) - KeyDown(KEY_DOWN)) * 0.01
		camP:+MXS * 0.01'(KeyDown(KEY_RIGHT) - KeyDown(KEY_LEFT)) * 0.01
	End If

	cx:+(KeyDown(KEY_R) - KeyDown(KEY_F)) * camS
	cy:+(KeyDown(KEY_T) - KeyDown(KEY_G)) * camS
	cz:+(KeyDown(KEY_Z) - KeyDown(KEY_H)) * camS
	cw:+(KeyDown(KEY_U) - KeyDown(KEY_J)) * camS

	If KeyHit(KEY_ENTER) disphelp = 1 - disphelp

	If KeyHit(KEY_DOWN)
		If KeyDown(KEY_RCONTROL)
			TAnimFrame.AddFrame(cflink, 1)
			Print "frame modified"
		Else
			cf = TAnimFrame.AddFrame(cflink) Print "frame added"
			If cflink = Null Then cflink = TAnimFrame.list.FindLink(cf) Else cflink = cflink.NextLink()
		End If
	End If

	If KeyHit(KEY_RIGHT)
		If KeyDown(KEY_RCONTROL)
			cflink = TAnimFrame.list.LastLink()
			cf = TAnimFrame(TAnimFrame.list.Last())
			If cf Then cf.setGlobalsToFrame()
		Else If cflink.NextLink()
			cflink = cflink.NextLink()
			cf = TAnimFrame(TAnimFrame(cflink.Value()))
			If cf Then cf.setGlobalsToFrame()
		End If
	End If

	If KeyHit(KEY_LEFT)
		If KeyDown(KEY_RCONTROL)
			cflink = TAnimFrame.list.FirstLink()
			cf = TAnimFrame(TAnimFrame.list.First())
			If cf Then cf.setGlobalsToFrame()
		Else If cflink.PrevLink()
			cflink = cflink.PrevLink()
			cf = TAnimFrame(TAnimFrame(cflink.Value()))
			If cf Then cf.setGlobalsToFrame()
		End If
	End If

	If KeyHit(KEY_BACKSPACE) And cf
		cflink = cflink.PrevLink()
		cf.list.Remove(cf)
		If cf Then cf = TAnimFrame(cflink.Value())
		Print "frame deleted"
	End If

	If cf
		Local timevar:TFloat = TFloat(cf.getVar("time"))
		If KeyDown(KEY_PAGEUP) Then timevar.v:*1.1 Print timevar.v Else ..
		If KeyDown(KEY_PAGEDOWN) Then timevar.v:/1.1 Print timevar.v
	End If

	If KeyHit(KEY_RSHIFT) And cf Then cf.setGlobalsToFrame()

	If KeyHit(KEY_UP) Then TAnimFrame.PlayAnim(0.04)

	If KeyHit(KEY_F10) Then TAnimFrame.SaveToFile("anim" + MilliSecs() + ".txt")

	If KeyHit(KEY_F11)
		If KeyDown(KEY_RCONTROL)
			TAnimFrame.SaveAnim(0.02, "anim" + MilliSecs(), Input("Skip Nr Frames:").ToInt())
		Else
			TAnimFrame.SaveAnim(0.02, "anim" + MilliSecs())
		End If
	End If

	If KeyHit(KEY_F12)
		If KeyDown(KEY_RCONTROL)
			SaveSceenshot(Input("resolution multiplier:").ToInt())
		Else
			SaveSceenshot(6)
		End If
	End If
End Function

Function Render(x:Int = 0, y:Int = 0, s:Int = 1)
	glUniform4f(glGetUniformLocation(ProgramObj, "cpos"), camX, camY, camZ, camW)
	glUniform2f(glGetUniformLocation(ProgramObj, "crot"), camP, camT)
	glUniform3f(glGetUniformLocation(ProgramObj, "light"), lightX, lightY, lightZ)
	glUniform4f(glGetUniformLocation(ProgramObj, "c"), cx, cy, cz, cw)
	glUniform1i(glGetUniformLocation(ProgramObj, "itr"), itr)
	glUniform1f(glGetUniformLocation(ProgramObj, "eps"), eps)

	Local w:Float = 2.0 * camZoom / Float(s), h:Float = GH / Float(GW)
	Local vx:Float = x * w - camZoom, vy:Float = -camZoom * h
	h = w * h
	vy = vy + y * h

	glBegin(GL_QUADS)
		glTexCoord2f(vx, vy) ;glVertex2i(0, 0)
		glTexCoord2f(vx + w, vy) ;glVertex2i(GW, 0)
		glTexCoord2f(vx + w, vy + h) ;glVertex2i(GW, GH)
		glTexCoord2f(vx, vy + h) ;glVertex2i(0, GH)
	glEnd
End Function

Function InitGL()
	Graphics GW, GH', 32, 60, 0
	SetGraphicsDriver(GLGraphicsDriver())
	glewInit()

	glMatrixMode(GL_PROJECTION)
	glLoadIdentity()

	glOrtho(0, GW, GH, 0, -1, 1)

	glMatrixMode(GL_MODELVIEW)
	glLoadIdentity()
End Function

Function glCls()
	glClear GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT
End Function

Function InitShaders()
	ProgramObj = glCreateProgram()
	Local VertexShdr:Int = glCreateShader(GL_VERTEX_SHADER_ARB)
	Local FragmentShdr:Int = glCreateShader(GL_FRAGMENT_SHADER_ARB)

	Local vsc:Byte Ptr = VERTEX_SHADER_CODE.ToCString()
	Local vsl:Int = VERTEX_SHADER_CODE.Length
	Local fsc:Byte Ptr = FRAGMENT_SHADER_CODE.ToCString()
	Local fsl:Int = FRAGMENT_SHADER_CODE.Length

	glShaderSource(VertexShdr, 1, Varptr vsc, Varptr vsl)
		glShaderSource(FragmentShdr, 1, Varptr fsc, Varptr fsl)

		MemFree(vsc)
	MemFree(fsc)

	glCompileShader(VertexShdr) ;CheckForErrors(VertexShdr)
	glCompileShader(FragmentShdr) ;CheckForErrors(FragmentShdr)

	glAttachShader(ProgramObj, VertexShdr)
	glAttachShader(ProgramObj, FragmentShdr)

	glDeleteShader(VertexShdr)
	glDeleteShader(FragmentShdr)

	glLinkProgram(ProgramObj) ;CheckForErrors(ProgramObj)
End Function

Function CheckForErrors(ShaderObject:Int)
	Local ErrorLength:Int

	glGetShaderiv(ShaderObject, GL_OBJECT_INFO_LOG_LENGTH_ARB, Varptr ErrorLength)

		If ErrorLength
		Local Message:Byte[ErrorLength], Dummy:Int

		glGetShaderInfoLog(ShaderObject, ErrorLength, Varptr Dummy, Varptr Message[0])
		'		SaveText("Shader object '" + ShaderObject + "':~n" + StringFromCharArray(Message), "error.txt")
		WriteStdout "Shader object '" + ShaderObject + "':~n" + StringFromCharArray(Message)
		WriteStdout "~n"
	EndIf
End Function

Function StringFromCharArray:String(Array:Byte[])
	Local Output:String
	For Local I:Int = 0 To Array.Length - 1
		Output:+Chr(Array[I])
	Next

	Return Output
End Function

Function SaveSceenshot(s:Int = 3, fn:String = "", compression:Int = 5, infosdtout:Int = 1)
	Local cnt:Int
	Local pm:TPixmap = CreatePixmap(GW * s, GH * s, PF_BGRA8888)
	Local c:Byte Ptr = MemAlloc(3 * (GW + GW Mod 4) * (GH + GH Mod 4))

	For Local x1:Int = 0 Until s
		If infosdtout WriteStdout (x1 + 1) + " of " + s + "~n"
		For Local y1:Int = 0 Until s
			Render(x1, y1, s)
			glReadPixels(0, 0, (GW + GW Mod 4), (GH + GH Mod 4), GL_RGB, GL_UNSIGNED_BYTE, c)
			cnt = 0
			For Local y:Int = GH - 1 To 0 Step - 1
				For Local x:Int = 0 Until GW
					WritePixel(pm, x1 * GW + x, y1 * GH + y, $ff000000 + $10000 * c[cnt] + $100 * c[cnt + 1] + c[cnt + 2])
					cnt:+3
				Next
				cnt:+3 * (GW Mod 4)
			Next
		Next
	Next

	If infosdtout WriteStdout "Saving..."
	If fn = "" Then fn = (MilliSecs()) + ".png"
	SavePixmapPNG(pm, fn, compression)
	If infosdtout WriteStdout "Done.~n~n"
End Function

Function DrawHelp()
	GLDrawText "cam =" + camX + ", " + camY + ", " + camZ, 0, 0
	GLDrawText "c   =" + cx + ", " + cy + ", " + cz, 0, 16
	GLDrawText "itr =" + itr, 0, 32
	GLDrawText "eps =" + eps, 0, 48
End Function

Function Catmul:Float(v0:Float, v1:Float, v2:Float, v3:Float, x:Float)
	Local a0:Float, a1:Float, a2:Float, a3:Float, x2:Float

	x2 = x * x
'	a0 = -0.5 * v0 + 1.5 * v1 - 1.5 * v2 + 0.5 * v3
'	a1 = v0 - 2.5 * v1 + 2 * v2 - 0.5 * v3
'	a2 = -0.5 * v0 + 0.5 * v2
'	a3 = v1
	a0 = v3 - v2 - v0 + v1
	a1 = v0 - v1 - a0
	a2 = v2 - v0
	a3 = v1

	Return a0 * x * x2 + a1 * x2 + a2 * x + a3
End Function

Function Clip:Float(v1:Float, v2:Float, x:Float)
	Return x * v2 + v1 * (1.0 - x)
End Function

Type TAnimFrame
	Global list:TList = CreateList()

	Field varlist:TList

	Method New()
		varlist = CreateList()
	End Method

	Method Del()
		list.Remove(Self)
	End Method

	Function AddFrame:TAnimFrame(link:TLink = Null, modify:Int = 0)
		Local t:TAnimFrame
		If modify
			If link = Null
				t = New TAnimFrame
				t.list.AddLast(t)
			Else
				t = TAnimFrame(link.Value())
			End If
		Else
			t = New TAnimFrame
			If link = Null
				t.list.AddLast(t)
			Else
				t.list.InsertAfterLink(t, link)
			End If
		End If

		Local timevar:Object = t.getVar("time")
		If timevar = Null
			t.setVar("time", TFloat.N(1.0))
		Else
			t.setVar("time", TFloat(timevar))
		End If

		t.setVar("camX", TFloat.N(camX))
		t.setVar("camY", TFloat.N(camY))
		t.setVar("camZ", TFloat.N(camZ))
		t.setVar("camP", TFloat.N(camP))
		t.setVar("camT", TFloat.N(camT))

		t.setVar("camZoom", TFloat.N(camZoom))

		t.setVar("lightX", TFloat.N(lightX))
		t.setVar("lightY", TFloat.N(lightY))
		t.setVar("lightZ", TFloat.N(lightZ))

		t.setVar("eps", TFloat.N(eps))

		t.setVar("itr", TInt.N(itr))

		t.setVar("cx", TFloat.N(cx))
		t.setVar("cy", TFloat.N(cy))
		t.setVar("cz", TFloat.N(cz))
		Return t
	End Function

	Method getVar:Object(name:String)
		For Local t:TAnimFrameVar = EachIn varlist
			If t.name = name
				Return t.v
			End If
		Next
		Return Null
	End Method

	Method setVar(name:String, v:Object)
		For Local t:TAnimFrameVar = EachIn varlist
			If t.name = name
				t.v = v
				Return
			End If
		Next
		Local t:TAnimFrameVar = New TAnimFrameVar
		t.name = name
		t.v = v
		varlist.AddLast(t)
		Return
	End Method

	Function PlayAnim(speed:Float)
		Local f1:TAnimFrame, f1link:TLink
		Local f2:TAnimFrame, f2link:TLink
		Local time:Float
		Local x:Float = 0

		f1 = TAnimFrame(f1.list.First())
		f1link = f1.list.FirstLink()
		If f1link = Null Then Return
		f2link = f1link.NextLink()
		If f2link = Null Then Return
		f2 = TAnimFrame(f2link.Value())

		While Not KeyHit(KEY_ESCAPE)

			setGlobals(f1, f1link, f2, f2link, x)

			Render()

			If disphelp DrawHelp()

			time = Clip(TFloat(f1.getVar("time")).v,  ..
			TFloat(f2.getVar("time")).v, x)

			x:+speed / time
			If x >= 1
				f1link = f2link
				f1 = f2
				f2link = f1link.NextLink()
				If f2link = Null Return
				f2 = TAnimFrame(f2link.Value())
				x = x - 1.0
			End If

			Flip 0
			glCls
			WaitTimer TIMER
		Wend
	End Function

	Function SaveAnim(speed:Float, folder:String, skip:Int = 0)
		Local framenr:Int = 0

		CreateDir(folder)

		Local f1:TAnimFrame, f1link:TLink
		Local f2:TAnimFrame, f2link:TLink
		Local time:Float
		Local x:Float = 0

		f1 = TAnimFrame(f1.list.First())
		f1link = f1.list.FirstLink()
		If f1link = Null Then Return
		f2link = f1link.NextLink()
		If f2link = Null Then Return
		f2 = TAnimFrame(f2link.Value())

		While Not KeyHit(KEY_ESCAPE)
			setGlobals(f1, f1link, f2, f2link, x)

			If framenr >= skip
				SaveSceenshot(3, folder + "/" + framenr + ".png", 1, 0)
				Print "Frame Nr. " + framenr
			End If

			time = Clip(TFloat(f1.getVar("time")).v,  ..
			TFloat(f2.getVar("time")).v, x)

			x:+speed / time
			If x >= 1
				f1link = f2link
				f1 = f2
				f2link = f1link.NextLink()
				If f2link = Null Then End
				f2 = TAnimFrame(f2link.Value())
				x = x - 1.0
			End If

			framenr:+1
			'Flip 0
			'glCls
			'WaitTimer TIMER
		Wend
	End Function

	Function setGlobals(f1:TAnimFrame, f1link:TLink, f2:TAnimFrame, f2link:TLink, x:Float)
		Local f0:TAnimFrame, f3:TAnimFrame
		Local f0link:TLink, f3link:TLink

		f0link = f1link.PrevLink()
		f3link = f2link.NextLink()

		If f0link = Null Then f0link = f1link
		If f3link = Null Then f3link = f2link

		f0 = TAnimFrame(f0link.Value())
		f3 = TAnimFrame(f3link.Value())

		camX = Catmul(TFloat(f0.getVar("camX")).v,  ..
		TFloat(f1.getVar("camX")).v,  ..
		TFloat(f2.getVar("camX")).v,  ..
		TFloat(f3.getVar("camX")).v, x)

		camY = Catmul(TFloat(f0.getVar("camY")).v,  ..
		TFloat(f1.getVar("camY")).v,  ..
		TFloat(f2.getVar("camY")).v,  ..
		TFloat(f3.getVar("camY")).v, x)

		camZ = Catmul(TFloat(f0.getVar("camZ")).v,  ..
		TFloat(f1.getVar("camZ")).v,  ..
		TFloat(f2.getVar("camZ")).v,  ..
		TFloat(f3.getVar("camZ")).v, x)

		camP = Catmul(TFloat(f0.getVar("camP")).v,  ..
		TFloat(f1.getVar("camP")).v,  ..
		TFloat(f2.getVar("camP")).v,  ..
		TFloat(f3.getVar("camP")).v, x)

		camT = Catmul(TFloat(f0.getVar("camT")).v,  ..
		TFloat(f1.getVar("camT")).v,  ..
		TFloat(f2.getVar("camT")).v,  ..
		TFloat(f3.getVar("camT")).v, x)

		camZoom = Catmul(TFloat(f0.getVar("camZoom")).v,  ..
		TFloat(f1.getVar("camZoom")).v,  ..
		TFloat(f2.getVar("camZoom")).v,  ..
		TFloat(f3.getVar("camZoom")).v, x)

		lightX = Catmul(TFloat(f0.getVar("lightX")).v,  ..
		TFloat(f1.getVar("lightX")).v,  ..
		TFloat(f2.getVar("lightX")).v,  ..
		TFloat(f3.getVar("lightX")).v, x)

		lightY = Catmul(TFloat(f0.getVar("lightY")).v,  ..
		TFloat(f1.getVar("lightY")).v,  ..
		TFloat(f2.getVar("lightY")).v,  ..
		TFloat(f3.getVar("lightY")).v, x)

		lightZ = Catmul(TFloat(f0.getVar("lightZ")).v,  ..
		TFloat(f1.getVar("lightZ")).v,  ..
		TFloat(f2.getVar("lightZ")).v,  ..
		TFloat(f3.getVar("lightZ")).v, x)

		eps = Clip(TFloat(f1.getVar("eps")).v,  ..
		TFloat(f2.getVar("eps")).v, x)

		itr = Clip(TInt(f1.getVar("itr")).v,  ..
		TInt(f2.getVar("itr")).v, x)

		cx = Clip(TFloat(f1.getVar("cx")).v,  ..
		TFloat(f2.getVar("cx")).v, x)

		cy = Clip(TFloat(f1.getVar("cy")).v,  ..
		TFloat(f2.getVar("cy")).v, x)

		cz = Clip(TFloat(f1.getVar("cz")).v,  ..
		TFloat(f2.getVar("cz")).v, x)
	End Function

	Method setGlobalsToFrame()
		camX = TFloat(getVar("camX")).v
		camY = TFloat(getVar("camY")).v
		camZ = TFloat(getVar("camZ")).v
		camP = TFloat(getVar("camP")).v
		camT = TFloat(getVar("camT")).v
		camZoom = TFloat(getVar("camZoom")).v

		lightX = TFloat(getVar("lightX")).v
		lightY = TFloat(getVar("lightY")).v
		lightZ = TFloat(getVar("lightZ")).v

		eps = TFloat(getVar("eps")).v
		itr = TInt(getVar("itr")).v

		cx = TFloat(getVar("cx")).v
		cy = TFloat(getVar("cy")).v
		cz = TFloat(getVar("cz")).v
	End Method

	Function SaveToFile(filename:String)
		Local f:TStream = WriteFile(filename)
		For Local t:TAnimFrame = EachIn TAnimFrame.list
			f.WriteLine "{"
			For Local v:TAnimFrameVar = EachIn t.varlist
				If TFloat(v.v) <> Null
					f.WriteLine "~t" + v.name + " = " + TFloat(v.v).v + "f;"
				Else If TInt(v.v) <> Null
					f.WriteLine "~t" + v.name + " = " + TInt(v.v).v + "i;"
				Else If TDouble(v.v) <> Null
					f.WriteLine "~t" + v.name + " = " + TDouble(v.v).v + "d;"
				End If
			Next
			f.WriteLine "}"
		Next
		f.Close
	End Function

	Function LoadFromFile(file:String)
		If FileType(file) = 1
			file = LoadText(file)
			file = Replace(file, " ", "")
			file = Replace(file, Chr(9), "")
			file = Replace(file, Chr(10), "")
			file = Replace(file, Chr(13), "")

			Local gs:Int = 1, ge:Int = 0, content:String

			While ge < Len(file)
				gs = Instr(file, "{", ge)
				ge = Instr(file, "}", gs + 1)
				content = Mid(file, gs + 1, ge - gs - 1)

				Local cs:Int = 1, ce:Int = 0, eq:Int
				Local name:String, value:String, dt:String

				Local t:TAnimFrame = New TAnimFrame
				t.list.AddLast(t)

				While ce < Len(content)
					ce = Instr(content, ";", cs)
					value = Mid(content, cs, ce - cs)
					eq = Instr(value, "=")

					name = Left(value, eq - 1)
					dt = Right(value, 1)
					value = Mid(Left(value, value.Length - 1), eq + 1)

					Select dt
						Case "f"
							t.setVar(name, TFloat.N(value.ToFloat()))
						Case "i"
							t.setVar(name, TInt.N(value.ToInt()))
						Case "d"
							t.setVar(name, TDouble.N(value.ToDouble()))
					End Select

					cs = ce + 1
				Wend

			Wend
		Else
			RuntimeError("File does not exist!")
		End If
	End Function

End Type

Type TAnimFrameVar
	Field name:String
	Field v:Object
End Type

Type TInt
	Field v:Int
	Function N:TInt(v:Int)
		Local t:TInt = New TInt
		t.v = v
		Return t
	End Function
End Type

Type TFloat
	Field v:Float
	Function N:TFloat(v:Float)
		Local t:TFloat = New TFloat
		t.v = v
		Return t
	End Function
End Type

Type TDouble
	Field v:Double
	Function N:TDouble(v:Double)
		Local t:TDouble = New TDouble
		t.v = v
		Return t
	End Function
End Type

error: pathspec '4DJuliaShader/4Djulia.bmx' did not match any file(s) known to git


Added BlitzMax (MonkeyMax) native file

Added BlitzMax (MonkeyMax) native file

' ***** Start diddy.bmax.bmx ******
Global diddy_mouseWheel:Float = 0.0

Type diddy
	Function systemMillisecs:Float()
		Return Millisecs()
	EndFunction

	Function setGraphics(w:int, h:int, fullScreen:Int = False)
		local d% = 0
		if fullScreen then d = 32
		Graphics w, h, d, 60
	EndFunction

	Function setMouse(x:Int, y:Int)
		MoveMouse(x, y)
	EndFunction

	Function showKeyboard()
	EndFunction

	Function launchBrowser(address:String, windowName:String)
	EndFunction

	Function launchEmail(email:String, subject:String, text:String)
	EndFunction

	Function realMod:Float( value:Float, amount:Float )
		Return value Mod amount
	EndFunction

	Function startVibrate(millisecs:int)
	EndFunction

	Function stopVibrate()
	EndFunction

	Function getDayOfMonth:Int()
		Local date$ = CurrentDate()
		Return Int(date[..2])
	EndFunction

	Function getDayOfWeek:Int()
		Return 0
	EndFunction

	Function getMonth:Int()
		Local date$ = CurrentDate()
		Return (Instr("JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC", date$[3..6].ToUpper(), 1) / 3) + 1
	EndFunction

	Function getYear:Int()
		Local date$ = CurrentDate()
		Return Int(date[date.length - 4..]);
	EndFunction

	Function getHours:Int()
		Local time$ = CurrentTime()
		Return Int(time[..2])
	EndFunction

	Function getMinutes:Int()
		Local time$ = CurrentTime()
		Return Int(time[3..5])
	EndFunction

	Function getSeconds:Int()
		Local time$ = CurrentTime()
		Return Int(time[6..8])
	EndFunction

	Function getMilliSeconds:Int()
		Return Millisecs()
	EndFunction

	Function startGps()
	EndFunction

	Function getLatitiude:String()
		Return ""
	EndFunction

	Function getLongitude:String()
		Return ""
	EndFunction

	Function showAlertDialog(title:String, message:String)
	EndFunction

	Function getInputString:String()
		Return "";
	EndFunction

	Function getPixel:Int( x:int, y:int )
'		unsigned char pix[4];
'		glReadPixels(x, app->graphics->height-y ,1 ,1 ,GL_RGBA ,GL_UNSIGNED_BYTE ,pix);
'		return (pix[3]<<24) | (pix[0]<<16) | (pix[1]<<8) |  pix[2];
		Return 0
	EndFunction

	Function mouseZInit()
	EndFunction

	Function mouseZ:Float()
		Local ret:Float = BlitzMaxMouseZ() - diddy_mouseWheel
		diddy_mouseWheel = BlitzMaxMouseZ()
		Return ret
	EndFunction
EndType

Function BlitzMaxMouseZ:Float()
	Return MouseZ()
EndFunction

' ***** End diddy.bmax.bmx ******

error: pathspec 'src/diddy/native/diddy.bmax.bmx' did not match any file(s) known to git