code:
//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:33

/* --- Traffic Control Scripting Language --- */
	// Nanotrasen TCS Language - Made by Doohl

/n_Interpreter/TCS_Interpreter
	var/datum/TCS_Compiler/Compiler

	HandleError(runtimeError/e)
		Compiler.Holder.add_entry(e.ToString(), "Execution Error")

	GC()
		..()
		Compiler = null

/datum/TCS_Compiler

	var/n_Interpreter/TCS_Interpreter/interpreter
	var/obj/machinery/telecomms/server/Holder	// the server that is running the code
	var/ready = 1 // 1 if ready to run code

	/* -- Set ourselves to Garbage Collect -- */

	proc/GC()

		Holder = null
		if(interpreter)
			interpreter.GC()

	/* -- Compile a raw block of text -- */

	proc/Compile(code as message)
		var/n_scriptOptions/nS_Options/options = new()
		var/n_Scanner/nS_Scanner/scanner       = new(code, options)
		var/list/tokens                        = scanner.Scan()
		var/n_Parser/nS_Parser/parser          = new(tokens, options)
		var/node/BlockDefinition/GlobalBlock/program   	 = parser.Parse()

		var/list/returnerrors = list()

		returnerrors += scanner.errors
		returnerrors += parser.errors

		if(returnerrors.len)
			return returnerrors

		interpreter 		= new(program)
		interpreter.persist	= 1
		interpreter.Compiler= src

		return returnerrors

	/* -- Execute the compiled code -- */

	proc/Run(var/datum/signal/signal)

		if(!ready)
			return

		if(!interpreter)
			return

		interpreter.container = src

		interpreter.SetVar("PI"		, 	3.141592653)	// value of pi
		interpreter.SetVar("E" 		, 	2.718281828)	// value of e
		interpreter.SetVar("SQURT2" , 	1.414213562)	// value of the square root of 2
		interpreter.SetVar("FALSE"  , 	0)				// boolean shortcut to 0
		interpreter.SetVar("false"  , 	0)				// boolean shortcut to 0
		interpreter.SetVar("TRUE"	,	1)				// boolean shortcut to 1
		interpreter.SetVar("true"	,	1)				// boolean shortcut to 1

		interpreter.SetVar("NORTH" 	, 	NORTH)			// NORTH (1)
		interpreter.SetVar("SOUTH" 	, 	SOUTH)			// SOUTH (2)
		interpreter.SetVar("EAST" 	, 	EAST)			// EAST  (4)
		interpreter.SetVar("WEST" 	, 	WEST)			// WEST  (8)

		// Channel macros
		interpreter.SetVar("$common",	1459)
		interpreter.SetVar("$science",	1351)
		interpreter.SetVar("$command",	1353)
		interpreter.SetVar("$medical",	1355)
		interpreter.SetVar("$engineering",1357)
		interpreter.SetVar("$security",	1359)
		interpreter.SetVar("$supply",	1347)
		interpreter.SetVar("$service",	1349)

		// Signal data

		interpreter.SetVar("$content", 	html_decode(signal.data["message"]))
		interpreter.SetVar("$freq"   , 	signal.frequency)
		interpreter.SetVar("$source" , 	signal.data["name"])
		interpreter.SetVar("$job"    , 	signal.data["job"])
		interpreter.SetVar("$sign"   ,	signal)
		interpreter.SetVar("$pass"	 ,  !(signal.data["reject"])) // if the signal isn't rejected, pass = 1; if the signal IS rejected, pass = 0

		//Language bitflags
		interpreter.SetVar("HUMAN"   ,	HUMAN)
		interpreter.SetVar("MONKEY"   ,	MONKEY)
		interpreter.SetVar("ALIEN"   ,	ALIEN)
		interpreter.SetVar("ROBOT"   ,	ROBOT)
		interpreter.SetVar("SLIME"   ,	SLIME)
		interpreter.SetVar("DRONE"   ,	DRONE)

		var/curlang = HUMAN
		if(istype(signal.data["mob"], /atom/movable))
			var/atom/movable/M = signal.data["mob"]
			curlang = M.languages

		interpreter.SetVar("$language", curlang)

		/*
		Telecomms procs
		*/

		/*
			-> Send another signal to a server
					@format: broadcast(content, frequency, source, job, lang)

					@param content:		Message to broadcast
					@param frequency:	Frequency to broadcast to
					@param source:		The name of the source you wish to imitate. Must be stored in stored_names list.
					@param job:				The name of the job.
		*/
		interpreter.SetProc("broadcast", "tcombroadcast", signal, list("message", "freq", "source", "job"))

		/*
			-> Send a code signal.
					@format: signal(frequency, code)

					@param frequency:		Frequency to send the signal to
					@param code:			Encryption code to send the signal with
		*/
		interpreter.SetProc("signal", "signaler", signal, list("freq", "code"))

		/*
			-> Store a value permanently to the server machine (not the actual game hosting machine, the ingame machine)
					@format: mem(address, value)

					@param address:		The memory address (string index) to store a value to
					@param value:		The value to store to the memory address
		*/
		interpreter.SetProc("mem", "mem", signal, list("address", "value"))

		/*
		General NTSL procs
		Should probably be moved to its own place
		*/
		// Vector
		interpreter.SetProc("vector", /proc/n_list)
		interpreter.SetProc("at", /proc/n_listpos)
		interpreter.SetProc("copy", /proc/n_listcopy)
		interpreter.SetProc("push_back", /proc/n_listadd)
		interpreter.SetProc("remove", /proc/n_listremove)
		interpreter.SetProc("cut", /proc/n_listcut)
		interpreter.SetProc("swap", /proc/n_listswap)
		interpreter.SetProc("insert", /proc/n_listinsert)
		interpreter.SetProc("pick", /proc/n_pick)
		interpreter.SetProc("prob", /proc/n_prob)
		interpreter.SetProc("substr", /proc/n_substr)
		interpreter.SetProc("find", /proc/n_smartfind)
		interpreter.SetProc("length", /proc/n_smartlength)

		// Strings
		interpreter.SetProc("lower", /proc/n_lower)
		interpreter.SetProc("upper", /proc/n_upper)
		interpreter.SetProc("explode", /proc/n_explode)
		interpreter.SetProc("implode", /proc/n_implode)
		interpreter.SetProc("repeat", /proc/n_repeat)
		interpreter.SetProc("reverse", /proc/n_reverse)
		interpreter.SetProc("tonum", /proc/n_str2num)
		interpreter.SetProc("replace", /proc/n_replace)
		interpreter.SetProc("proper", /proc/n_proper)

		// Numbers
		interpreter.SetProc("tostring", /proc/n_num2str)
		interpreter.SetProc("sqrt", /proc/n_sqrt)
		interpreter.SetProc("abs", /proc/n_abs)
		interpreter.SetProc("floor", /proc/n_floor)
		interpreter.SetProc("ceil", /proc/n_ceil)
		interpreter.SetProc("round", /proc/n_round)
		interpreter.SetProc("clamp", /proc/n_clamp)
		interpreter.SetProc("inrange", /proc/n_inrange)
		interpreter.SetProc("rand", /proc/n_rand)
		interpreter.SetProc("randseed", /proc/n_randseed)
		interpreter.SetProc("min", /proc/n_min)
		interpreter.SetProc("max", /proc/n_max)
		interpreter.SetProc("sin", /proc/n_sin)
		interpreter.SetProc("cos", /proc/n_cos)
		interpreter.SetProc("asin", /proc/n_asin)
		interpreter.SetProc("acos", /proc/n_acos)
		interpreter.SetProc("log", /proc/n_log)

		// Time
		interpreter.SetProc("time", /proc/n_time)
		interpreter.SetProc("sleep", /proc/n_delay)
		interpreter.SetProc("timestamp", /proc/gameTimestamp)

		// Run the compiled code
		interpreter.Run()

		// Backwards-apply variables onto signal data
		/* sanitize EVERYTHING. fucking players can't be trusted with SHIT */

		signal.data["message"] 	= interpreter.GetCleanVar("$content", signal.data["message"])
		signal.frequency 		= interpreter.GetCleanVar("$freq", signal.frequency)

		var/setname = interpreter.GetCleanVar("$source", signal.data["name"])

		if(signal.data["name"] != setname)
			signal.data["realname"] = setname
		signal.data["name"]		= setname
		signal.data["job"]		= interpreter.GetCleanVar("$job", signal.data["job"])
		signal.data["reject"]	= !(interpreter.GetCleanVar("$pass")) // set reject to the opposite of $pass

		// If the message is invalid, just don't broadcast it!
		if(signal.data["message"] == "" || !signal.data["message"])
			signal.data["reject"] = 1

/*  -- Actual language proc code --  */

var/const/SIGNAL_COOLDOWN = 20 // 2 seconds

datum/signal

	proc/mem(var/address, var/value)

		if(istext(address))
			var/obj/machinery/telecomms/server/S = data["server"]

			if(!value && value != 0)
				return S.memory[address]

			else
				S.memory[address] = value

	proc/signaler(var/freq = 1459, var/code = 30)

		if(isnum(freq) && isnum(code))

			var/obj/machinery/telecomms/server/S = data["server"]

			if(S.last_signal + SIGNAL_COOLDOWN > world.timeofday && S.last_signal < MIDNIGHT_ROLLOVER)
				return
			S.last_signal = world.timeofday

			var/datum/radio_frequency/connection = radio_controller.return_frequency(freq)

			if(findtext(num2text(freq), ".")) // if the frequency has been set as a decimal
				freq *= 10 // shift the decimal one place

			freq = sanitize_frequency(freq)

			code = round(code)
			code = Clamp(code, 0, 100)

			var/datum/signal/signal = new
			signal.source = S
			signal.encryption = code
			signal.data["message"] = "ACTIVATE"

			connection.post_signal(S, signal)

			var/time = time2text(world.realtime,"hh:mm:ss")
			lastsignalers.Add("[time] : [S.id] sent a signal command, which was triggered by NTSL.: [format_frequency(freq)]/[code]")

	proc/tcombroadcast(var/message, var/freq, var/source, var/job)

		var/datum/signal/newsign = new
		var/obj/machinery/telecomms/server/S = data["server"]
		var/obj/item/device/radio/hradio = S.server_radio

		if(!hradio)
			ERROR("[src] has no radio.")
			return

		if((!message || message == "") && message != 0)
			message = "*beep*"
		if(!source)
			source = "[html_encode(uppertext(S.id))]"
			hradio = new // sets the hradio as a radio intercom
		if(!freq || (!isnum(freq) && text2num(freq) == null))
			freq = 1459
		if(findtext(num2text(freq), ".")) // if the frequency has been set as a decimal
			freq *= 10 // shift the decimal one place

		if(!job)
			job = "Unknown"

		//SAY REWRITE RELATED CODE.
		//This code is a little hacky, but it *should* work. Even though it'll result in a virtual speaker referencing another virtual speaker. vOv
		var/atom/movable/virtualspeaker/virt = PoolOrNew(/atom/movable/virtualspeaker,null)
		virt.name = source
		virt.job = job
		virt.languages = HUMAN
		//END SAY REWRITE RELATED CODE.

		newsign.data["mob"] = virt
		newsign.data["mobtype"] = /mob/living/carbon/human
		newsign.data["name"] = source
		newsign.data["realname"] = newsign.data["name"]
		newsign.data["job"] = "[job]"
		newsign.data["compression"] = 0
		newsign.data["message"] = message
		newsign.data["type"] = 2 // artificial broadcast
		if(!isnum(freq))
			freq = text2num(freq)
		newsign.frequency = freq

		newsign.data["radio"] = hradio
		newsign.data["vmessage"] = message
		newsign.data["vname"] = source
		newsign.data["vmask"] = 0
		newsign.data["level"] = data["level"]

		newsign.sanitize_data()

		var/pass = S.relay_information(newsign, "/obj/machinery/telecomms/hub")
		if(!pass)
			S.relay_information(newsign, "/obj/machinery/telecomms/broadcaster") // send this simple message to broadcasters


code:
// Script -> BYOND code procs
#define SCRIPT_MAX_REPLACEMENTS_ALLOWED 200

// --- List operations (lists known as vectors in n_script) ---

// Creates a list out of all the arguments
/proc/n_list()
	var/list/returnlist = list()
	for(var/e in args)
		returnlist.Add(e)
	return returnlist

// Picks one random item from the list
/proc/n_pick()
	var/list/finalpick = list()
	for(var/e in args)
		if(isobject(e))
			if(istype(e, /list))
				var/list/sublist = e
				for(var/sube in sublist)
					finalpick.Add(sube)
				continue
		finalpick.Add(e)

	return pick(finalpick)

// Gets/Sets a value at a key in the list
/proc/n_listpos(var/list/L, var/pos, var/value)
	if(!istype(L, /list)) return
	if(isnum(pos))
		if(!value)
			if(L.len >= pos)
				return L[pos]
		else
			if(L.len >= pos)
				L[pos] = value
	else if(istext(pos))
		if(!value)
			return L[pos]
		else
			L[pos] = value

// Copies the list into a new one
/proc/n_listcopy(var/list/L, var/start, var/end)
	if(!istype(L, /list)) return
	return L.Copy(start, end)

// Adds arg 2,3,4,5... to the end of list at arg 1
/proc/n_listadd()
	var/list/chosenlist
	var/i = 1
	for(var/e in args)
		if(i == 1)
			if(isobject(e))
				if(istype(e, /list))
					chosenlist = e
			i = 2
		else
			if(chosenlist)
				chosenlist.Add(e)

// Removes arg 2,3,4,5... from list at arg 1
/proc/n_listremove()
	var/list/chosenlist
	var/i = 1
	for(var/e in args)
		if(i == 1)
			if(isobject(e))
				if(istype(e, /list))
					chosenlist = e
			i = 2
		else
			if(chosenlist)
				chosenlist.Remove(e)

// Cuts out a copy of a list
/proc/n_listcut(var/list/L, var/start, var/end)
	if(!istype(L, /list)) return
	return L.Cut(start, end)

// Swaps two values in the list
/proc/n_listswap(var/list/L, var/firstindex, var/secondindex)
	if(!istype(L, /list)) return
	if(L.len >= secondindex && L.len >= firstindex)
		return L.Swap(firstindex, secondindex)

// Inserts a value into the list
/proc/n_listinsert(var/list/L, var/index, var/element)
	if(!istype(L, /list)) return
	return L.Insert(index, element)

// --- String methods ---

//If list, finds a value in it, if text, finds a substring in it
/proc/n_smartfind(var/haystack, var/needle, var/start = 1, var/end = 0)
	if(haystack && needle)
		if(isobject(haystack))
			if(istype(haystack, /list))
				if(length(haystack) >= end && start > 0)
					var/list/listhaystack = haystack
					return listhaystack.Find(needle, start, end)

		else
			if(istext(haystack))
				if(length(haystack) >= end && start > 0)
					return findtext(haystack, needle, start, end)

//Returns a substring of the string
/proc/n_substr(var/string, var/start = 1, var/end = 0)
	if(istext(string) && isnum(start) && isnum(end))
		if(start > 0)
			return copytext(string, start, end)

//Returns the length of the string or list
/proc/n_smartlength(var/container)
	if(container)
		if(istype(container, /list) || istext(container))
			return length(container)
	return 0

//Lowercase all characters
/proc/n_lower(var/string)
	if(istext(string))
		return lowertext(string)

//Uppercase all characters
/proc/n_upper(var/string)
	if(istext(string))
		return uppertext(string)

//Converts a string to a list
/proc/n_explode(var/string, var/separator = "")
	if(istext(string) && (istext(separator) || isnull(separator)))
		return text2list(string, separator)

//Converts a list to a string
/proc/n_implode(var/list/li, var/separator)
	if(istype(li) && (istext(separator) || isnull(separator)))
		return list2text(li, separator)

//Repeats the string x times
/proc/n_repeat(var/string, var/amount)
	if(istext(string) && isnum(amount))
		var/i
		var/newstring = ""
		if(length(newstring)*amount >=1000)
			return
		for(i=0, i<=amount, i++)
			if(i>=1000)
				break
			newstring = newstring + string

		return newstring

//Reverses the order of the string. "Clown" becomes "nwolC"
/proc/n_reverse(var/string)
	if(istext(string))
		var/newstring = ""
		var/i
		for(i=lentext(string), i>0, i--)
			if(i>=1000)
				break
			newstring = newstring + copytext(string, i, i+1)

		return newstring

// String -> Number
/proc/n_str2num(var/string)
	if(istext(string))
		return text2num(string)

/proc/n_proper(var/string)
	if(!istext(string))
		return ""

	return text("[][]", uppertext(copytext(string, 1, 2)), lowertext(copytext(string, 2)))

// --- Number methods ---

//Returns the highest value of the arguments
//Need custom functions here cause byond's min and max runtimes if you give them a string or list.
/proc/n_max()
	if(args.len == 0)
		return 0

	var/max = args[1]
	for(var/e in args)
		if(isnum(e) && e > max)
			max = e

	return max

//Returns the lowest value of the arguments
/proc/n_min()
	if(args.len == 0)
		return 0

	var/min = args[1]
	for(var/e in args)
		if(isnum(e) && e < min)
			min = e

	return min

/proc/n_prob(var/chance)
	return prob(chance)

/proc/n_randseed(var/seed)
	rand_seed(seed)
	return 0

/proc/n_rand(var/low, var/high)
	if(isnull(low) && isnull(high))
		return rand()

	return rand(low, high)

// Number -> String
/proc/n_num2str(var/num)
	if(isnum(num))
		return num2text(num)

// Squareroot
/proc/n_sqrt(var/num)
	if(isnum(num))
		return sqrt(num)

// Magnitude of num
/proc/n_abs(var/num)
	if(isnum(num))
		return abs(num)

// Round down
/proc/n_floor(var/num)
	if(isnum(num))
		return round(num)

// Round up
/proc/n_ceil(var/num)
	if(isnum(num))
		return round(num)+1

// Round to nearest integer
/proc/n_round(var/num)
	if(isnum(num))
		if(num-round(num)<0.5)
			return round(num)
		return n_ceil(num)

// Clamps N between min and max
/proc/n_clamp(var/num, var/min=-1, var/max=1)
	if(isnum(num)&&isnum(min)&&isnum(max))
		if(num<=min)
			return min
		if(num>=max)
			return max
		return num

// Returns 1 if N is inbetween Min and Max
/proc/n_inrange(var/num, var/min=-1, var/max=1)
	if(isnum(num)&&isnum(min)&&isnum(max))
		return ((min <= num) && (num <= max))

// Returns the sine of num
/proc/n_sin(var/num)
	if(isnum(num))
		return sin(num)

// Returns the cosine of num
/proc/n_cos(var/num)
	if(isnum(num))
		return cos(num)

// Returns the arcsine of num
/proc/n_asin(var/num)
	if(isnum(num)&&-1<=num&&num<=1)
		return arcsin(num)

// Returns the arccosine of num
/proc/n_acos(var/num)
	if(isnum(num)&&-1<=num&&num<=1)
		return arccos(num)

// Returns the natural log of num
/proc/n_log(var/num)
	if(isnum(num)&&0<num)
		return log(num)

// Replace text
/proc/n_replace(text, find, replacement)
	if(istext(text) && istext(find) && istext(replacement))
		var/find_len = length(find)
		if(find_len < 1)	return text
		. = ""
		var/last_found = 1
		var/count = 0
		while(1)
			count += 1
			if(count >  SCRIPT_MAX_REPLACEMENTS_ALLOWED)
				break
			var/found = findtext(text, find, last_found, 0)
			. += copytext(text, last_found, found)
			if(found)
				. += replacement
				last_found = found + find_len
				continue
			return

#undef SCRIPT_MAX_REPLACEMENTS_ALLOWED

// --- Miscellaneous functions ---

/proc/n_time()
	return world.timeofday

// Clone of sleep()
/proc/n_delay(var/time)
	sleep(time)

license: mit


code:
/proc/isobject(x)
	return (istype(x, /datum) || istype(x, /list) || istype(x, /savefile) || istype(x, /client) || (x==world))

/n_Interpreter
	proc
		Eval(node/expression/exp)
			if(istype(exp, /node/expression/FunctionCall))
				return RunFunction(exp)
			else if(istype(exp, /node/expression/operator))
				return EvalOperator(exp)
			else if(istype(exp, /node/expression/value/literal))
				var/node/expression/value/literal/lit=exp
				return lit.value
			else if(istype(exp, /node/expression/value/reference))
				var/node/expression/value/reference/ref=exp
				return ref.value
			else if(istype(exp, /node/expression/value/variable))
				var/node/expression/value/variable/v=exp
				if(!v.object)
					return Eval(GetVariable(v.id.id_name))
				else
					var/datum/D
					if(istype(v.object, /node/identifier))
						D=GetVariable(v.object:id_name)
					else
						D=v.object
					D=Eval(D)
					if(!isobject(D))
						return null
					if(!D.vars.Find(v.id.id_name))
						RaiseError(new/runtimeError/UndefinedVariable("[v.object.ToString()].[v.id.id_name]"))
						return null
					return Eval(D.vars[v.id.id_name])
			else if(istype(exp, /node/expression))
				RaiseError(new/runtimeError/UnknownInstruction())
			else
				return exp

		EvalOperator(node/expression/operator/exp)
			if(istype(exp, /node/expression/operator/binary))
				var/node/expression/operator/binary/bin=exp
				switch(bin.type)
					if(/node/expression/operator/binary/Equal)
						return Equal(Eval(bin.exp), Eval(bin.exp2))
					if(/node/expression/operator/binary/NotEqual)
						return NotEqual(Eval(bin.exp), Eval(bin.exp2))
					if(/node/expression/operator/binary/Greater)
						return Greater(Eval(bin.exp), Eval(bin.exp2))
					if(/node/expression/operator/binary/Less)
						return Less(Eval(bin.exp), Eval(bin.exp2))
					if(/node/expression/operator/binary/GreaterOrEqual)
						return GreaterOrEqual(Eval(bin.exp), Eval(bin.exp2))
					if(/node/expression/operator/binary/LessOrEqual)
						return LessOrEqual(Eval(bin.exp), Eval(bin.exp2))
					if(/node/expression/operator/binary/LogicalAnd)
						return LogicalAnd(Eval(bin.exp), Eval(bin.exp2))
					if(/node/expression/operator/binary/LogicalOr)
						return LogicalOr(Eval(bin.exp), Eval(bin.exp2))
					if(/node/expression/operator/binary/LogicalXor)
						return LogicalXor(Eval(bin.exp), Eval(bin.exp2))
					if(/node/expression/operator/binary/BitwiseAnd)
						return BitwiseAnd(Eval(bin.exp), Eval(bin.exp2))
					if(/node/expression/operator/binary/BitwiseOr)
						return BitwiseOr(Eval(bin.exp), Eval(bin.exp2))
					if(/node/expression/operator/binary/BitwiseXor)
						return BitwiseXor(Eval(bin.exp), Eval(bin.exp2))
					if(/node/expression/operator/binary/Add)
						return Add(Eval(bin.exp), Eval(bin.exp2))
					if(/node/expression/operator/binary/Subtract)
						return Subtract(Eval(bin.exp), Eval(bin.exp2))
					if(/node/expression/operator/binary/Multiply)
						return Multiply(Eval(bin.exp), Eval(bin.exp2))
					if(/node/expression/operator/binary/Divide)
						return Divide(Eval(bin.exp), Eval(bin.exp2))
					if(/node/expression/operator/binary/Power)
						return Power(Eval(bin.exp), Eval(bin.exp2))
					if(/node/expression/operator/binary/Modulo)
						return Modulo(Eval(bin.exp), Eval(bin.exp2))
					else
						RaiseError(new/runtimeError/UnknownInstruction())
			else
				switch(exp.type)
					if(/node/expression/operator/unary/Minus)
						return Minus(Eval(exp.exp))
					if(/node/expression/operator/unary/LogicalNot)
						return LogicalNot(Eval(exp.exp))
					if(/node/expression/operator/unary/BitwiseNot)
						return BitwiseNot(Eval(exp.exp))
					if(/node/expression/operator/unary/group)
						return Eval(exp.exp)
					else
						RaiseError(new/runtimeError/UnknownInstruction())

	//Binary//
		//Comparison operators
		Equal(a, b) 				return a==b
		NotEqual(a, b)			return a!=b //LogicalNot(Equal(a, b))
		Greater(a, b)				return a>b
		Less(a, b)					return a<b
		GreaterOrEqual(a, b)return a>=b
		LessOrEqual(a, b)		return a<=b
		//Logical Operators
		LogicalAnd(a, b)		return a&&b
		LogicalOr(a, b)			return a||b
		LogicalXor(a, b)		return (a||b) && !(a&&b)
		//Bitwise Operators
		BitwiseAnd(a, b)		return a&b
		BitwiseOr(a, b)			return a|b
		BitwiseXor(a, b)		return a^b
		//Arithmetic Operators
		Add(a, b)
			if(istext(a)&&!istext(b)) 		 b="[b]"
			else if(istext(b)&&!istext(a)) a="[a]"
			if(isobject(a) && !isobject(b))
				RaiseError(new/runtimeError/TypeMismatch("+", a, b))
				return null
			else if(isobject(b) && !isobject(a))
				RaiseError(new/runtimeError/TypeMismatch("+", a, b))
				return null
			return a+b
		Subtract(a, b)
			if(isobject(a) && !isobject(b))
				RaiseError(new/runtimeError/TypeMismatch("-", a, b))
				return null
			else if(isobject(b) && !isobject(a))
				RaiseError(new/runtimeError/TypeMismatch("-", a, b))
				return null
			return a-b
		Divide(a, b)
			if(isobject(a) && !isobject(b))
				RaiseError(new/runtimeError/TypeMismatch("/", a, b))
				return null
			else if(isobject(b) && !isobject(a))
				RaiseError(new/runtimeError/TypeMismatch("/", a, b))
				return null
			if(b==0 || b==null)
				RaiseError(new/runtimeError/DivisionByZero())
				return null
			return a/b
		Multiply(a, b)
			if(isobject(a) && !isobject(b))
				RaiseError(new/runtimeError/TypeMismatch("*", a, b))
				return null
			else if(isobject(b) && !isobject(a))
				RaiseError(new/runtimeError/TypeMismatch("*", a, b))
				return null
			return a*b
		Modulo(a, b)
			if(isobject(a) && !isobject(b))
				RaiseError(new/runtimeError/TypeMismatch("%", a, b))
				return null
			else if(isobject(b) && !isobject(a))
				RaiseError(new/runtimeError/TypeMismatch("%", a, b))
				return null
			return a%b
		Power(a, b)
			if(isobject(a) && !isobject(b))
				RaiseError(new/runtimeError/TypeMismatch("**", a, b))
				return null
			else if(isobject(b) && !isobject(a))
				RaiseError(new/runtimeError/TypeMismatch("**", a, b))
				return null
			return a**b

	//Unary//
		Minus(a)						return -a
		LogicalNot(a)				return !a
		BitwiseNot(a)				return ~a


code:
/*
	File: Interpreter (Public)
	Contains methods for interacting with the interpreter.
*/
/*
	Class: n_Interpreter
	Procedures allowing for interaction with the script that is being run by the interpreter object.
*/

/n_Interpreter
	proc

/*
	Proc: Load
	Loads a 'compiled' script into memory.

	Parameters:
	program - A <GlobalBlock> object which represents the script's global scope.
*/
		Load(node/BlockDefinition/GlobalBlock/program)
			ASSERT(program)
			src.program 	= program
			CreateGlobalScope()
			alertadmins = 0 // reset admin alerts

/*
	Proc: Run
	Runs the script.
*/
		Run()
			cur_recursion = 0 // reset recursion
			cur_statements = 0 // reset CPU tracking

			ASSERT(src.program)
			RunBlock(src.program)

/*
	Proc: SetVar
	Defines a global variable for the duration of the next execution of a script.

	Notes:
	This differs from <Block.SetVar()> in that variables set using this procedure only last for the session,
	while those defined from the block object persist if it is ran multiple times.

	See Also:
	- <Block.SetVar()>
*/
		SetVar(name, value)
			if(!istext(name))
				//CRASH("Invalid variable name")
				return
			AssignVariable(name, value)

/*
	Proc: SetProc
	Defines a procedure to be available to the script.

	Parameters:
	name 		- The name of the procedure as exposed to the script.
	path 		- The typepath of a proc to be called when the function call is read by the interpreter, or, if object is specified, a string representing the procedure's name.
	object	- (Optional) An object which will the be target of a function call.
	params 	- Only required if object is not null, a list of the names of parameters the proc takes.
*/
		SetProc(name, path, object=null, list/params=null)
			if(!istext(name))
				//CRASH("Invalid function name")
				return
			if(!object)
				globalScope.functions[name] = path
			else
				var/node/statement/FunctionDefinition/S = new()
				S.func_name		= name
				S.parameters	= params
				S.block			= new()
				S.block.SetVar("src", object)
				var/node/expression/FunctionCall/C = new()
				C.func_name	= path
				C.object		= new("src")
				for(var/p in params)
					C.parameters += new/node/expression/value/variable(p)
				var/node/statement/ReturnStatement/R=new()
				R.value=C
				S.block.statements += R
				globalScope.functions[name] = S
/*
	Proc: VarExists
	Checks whether a global variable with the specified name exists.
*/
		VarExists(name)
			return globalScope.variables.Find(name) //convert to 1/0 first?

/*
	Proc: ProcExists
	Checks whether a global function with the specified name exists.
*/
		ProcExists(name)
			return globalScope.functions.Find(name)

/*
	Proc: GetVar
	Returns the value of a global variable in the script. Remember to ensure that the variable exists before calling this procedure.

	See Also:
	- <VarExists()>
*/
		GetVar(name)
			if(!VarExists(name))
				//CRASH("No variable named '[name]'.")
				return
			var/x = globalScope.variables[name]
			return Eval(x)

/*
	Proc: GetCleanVar
	Returns the value of a global variable in the script and cleans it (sanitizes).
*/

		GetCleanVar(name, compare)
			var/x = GetVar(name)
			if(istext(x) && compare && x != compare) // Was changed
				x = sanitize(x)
			return x

/*
	Proc: CallProc
	Calls a global function defined in the script and, amazingly enough, returns its return value. Remember to ensure that the function
	exists before calling this procedure.

	See Also:
	- <ProcExists()>
*/
		CallProc(name, params[]=null)
			if(!ProcExists(name))
				//CRASH("No function named '[name]'.")
				return
			var/node/statement/FunctionDefinition/func = globalScope.functions[name]
			if(istype(func))
				var/node/statement/FunctionCall/stmt = new
				stmt.func_name  = func.func_name
				stmt.parameters = params
				return RunFunction(stmt)
			else
				return call(func)(arglist(params))
			//CRASH("Unknown function type '[name]'.")

/*
	Event: HandleError
	Called when the interpreter throws a runtime error.

	See Also:
	- <runtimeError>
*/
		HandleError(runtimeError/e)


code:
/*
	File: Interpreter (Internal)
*/
/*
	Class: n_Interpreter
*/
/*
	Macros: Status Macros
	RETURNING  - Indicates that the current function is returning a value.
	BREAKING   - Indicates that the current loop is being terminated.
	CONTINUING - Indicates that the rest of the current iteration of a loop is being skipped.
*/
#define RETURNING  1
#define BREAKING   2
#define CONTINUING 4
/n_Interpreter
	var
		scope
			curScope
			globalScope
		node
			BlockDefinition/program
			statement/FunctionDefinition/curFunction
		stack
			scopes		= new()
			functions	= new()

		datum/container // associated container for interpeter
/*
	Var: status
	A variable indicating that the rest of the current block should be skipped. This may be set to any combination of <Status Macros>.
*/
		status=0
		returnVal

		max_statements=900 // maximum amount of statements that can be called in one execution. this is to prevent massive crashes and exploitation
		cur_statements=0    // current amount of statements called
		alertadmins=0		// set to 1 if the admins shouldn't be notified of anymore issues
		max_iterations=100 	// max number of uninterrupted loops possible
		max_recursion=10   	// max recursions without returning anything (or completing the code block)
		cur_recursion=0	   	// current amount of recursion
/*
	Var: persist
	If 0, global variables will be reset after Run() finishes.
*/
		persist=1
		paused=0

/*
	Constructor: New
	Calls <Load()> with the given parameters.
*/
	New(node/BlockDefinition/GlobalBlock/program=null)
		.=..()
		if(program)Load(program)

	proc

/*
	Set ourselves to Garbage Collect
*/
		GC()
			..()
			container = null

/*
	Proc: RaiseError
	Raises a runtime error.
*/
		RaiseError(runtimeError/e)
			e.stack=functions.Copy()
			e.stack.Push(curFunction)
			src.HandleError(e)

		CreateScope(node/BlockDefinition/B)
			var/scope/S = new(B, curScope)
			scopes.Push(curScope)
			curScope = S
			return S

		CreateGlobalScope()
			scopes.Clear()
			var/scope/S = new(program, null)
			globalScope = S
			return S

/*
	Proc: AlertAdmins
	Alerts the admins of a script that is bad.
*/
		AlertAdmins()
			if(container && !alertadmins)
				if(istype(container, /datum/TCS_Compiler))
					var/datum/TCS_Compiler/Compiler = container
					var/obj/machinery/telecomms/server/Holder = Compiler.Holder
					var/message = "Potential crash-inducing NTSL script detected at telecommunications server [Compiler.Holder] ([Holder.x], [Holder.y], [Holder.z])."

					alertadmins = 1
					message_admins(message, 1)
/*
	Proc: RunBlock
	Runs each statement in a block of code.
*/
		RunBlock(node/BlockDefinition/Block, scope/scope = null)
			var/is_global = istype(Block, /node/BlockDefinition/GlobalBlock)
			if(!is_global)
				if(scope)
					curScope = scope
				else
					CreateScope(Block)
			else
				if(!persist)
					CreateGlobalScope()
				curScope = globalScope

			if(cur_statements < max_statements)

				for(var/node/statement/S in Block.statements)
					while(paused) sleep(10)

					cur_statements++
					if(cur_statements >= max_statements)
						RaiseError(new/runtimeError/MaxCPU())
						AlertAdmins()
						break

					if(istype(S, /node/statement/VariableAssignment))
						var/node/statement/VariableAssignment/stmt = S
						var/name = stmt.var_name.id_name
						if(!stmt.object)
							// Below we assign the variable first to null if it doesn't already exist.
							// This is necessary for assignments like +=, and when the variable is used in a function
							// If the variable already exists in a different block, then AssignVariable will automatically use that one.
							if(!IsVariableAccessible(name))
								AssignVariable(name, null)
							AssignVariable(name, Eval(stmt.value))
						else
							var/datum/D = Eval(GetVariable(stmt.object.id_name))
							if(!D) return
							D.vars[stmt.var_name.id_name] = Eval(stmt.value)
					else if(istype(S, /node/statement/VariableDeclaration))
						//VariableDeclaration nodes are used to forcibly declare a local variable so that one in a higher scope isn't used by default.
						var/node/statement/VariableDeclaration/dec=S
						if(!dec.object)
							AssignVariable(dec.var_name.id_name, null, curScope)
						else
							var/datum/D = Eval(GetVariable(dec.object.id_name))
							if(!D) return
							D.vars[dec.var_name.id_name] = null
					else if(istype(S, /node/statement/FunctionCall))
						RunFunction(S)
					else if(istype(S, /node/statement/FunctionDefinition))
						//do nothing
					else if(istype(S, /node/statement/WhileLoop))
						RunWhile(S)
					else if(istype(S, /node/statement/IfStatement))
						RunIf(S)
					else if(istype(S, /node/statement/ReturnStatement))
						if(!curFunction)
							RaiseError(new/runtimeError/UnexpectedReturn())
							continue
						status |= RETURNING
						returnVal=Eval(S:value)
						break
					else if(istype(S, /node/statement/BreakStatement))
						status |= BREAKING
						break
					else if(istype(S, /node/statement/ContinueStatement))
						status |= CONTINUING
						break
					else
						RaiseError(new/runtimeError/UnknownInstruction())
					if(status)
						break

			curScope = scopes.Pop()

/*
	Proc: RunFunction
	Runs a function block or a proc with the arguments specified in the script.
*/
		RunFunction(node/statement/FunctionCall/stmt)
			//Note that anywhere /node/statement/FunctionCall/stmt is used so may /node/expression/FunctionCall

			// If recursion gets too high (max 50 nested functions) throw an error
			if(cur_recursion >= max_recursion)
				AlertAdmins()
				RaiseError(new/runtimeError/RecursionLimitReached())
				return 0

			var/node/statement/FunctionDefinition/def
			if(!stmt.object)							//A scope's function is being called, stmt.object is null
				def = GetFunction(stmt.func_name)
			else if(istype(stmt.object))				//A method of an object exposed as a variable is being called, stmt.object is a /node/identifier
				var/O = GetVariable(stmt.object.id_name)	//Gets a reference to the object which is the target of the function call.
				if(!O) return							//Error already thrown in GetVariable()
				def = Eval(O)

			if(!def) return

			cur_recursion++ // add recursion
			if(istype(def))
				if(curFunction) functions.Push(curFunction)
				var/scope/S = CreateScope(def.block)
				for(var/i=1 to def.parameters.len)
					var/val
					if(stmt.parameters.len>=i)
						val = stmt.parameters[i]
					//else
					//	unspecified param
					AssignVariable(def.parameters[i], new/node/expression/value/literal(Eval(val)), S)
				curFunction=stmt
				RunBlock(def.block, S)
				//Handle return value
				. = returnVal
				status &= ~RETURNING
				returnVal=null
				curFunction=functions.Pop()
				cur_recursion--
			else
				cur_recursion--
				var/list/params=new
				for(var/node/expression/P in stmt.parameters)
					params+=list(Eval(P))
				if(isobject(def))	//def is an object which is the target of a function call
					if( !hascall(def, stmt.func_name) )
						RaiseError(new/runtimeError/UndefinedFunction("[stmt.object.id_name].[stmt.func_name]"))
						return
					return call(def, stmt.func_name)(arglist(params))
				else										//def is a path to a global proc
					return call(def)(arglist(params))
			//else
			//	RaiseError(new/runtimeError/UnknownInstruction())

/*
	Proc: RunIf
	Checks a condition and runs either the if block or else block.
*/
		RunIf(node/statement/IfStatement/stmt)
			if(!stmt.skip)
				if(Eval(stmt.cond))
					RunBlock(stmt.block)
					// Loop through the if else chain and tell them to be skipped.
					var/node/statement/IfStatement/i = stmt.else_if
					var/fail_safe = 800
					while(i && fail_safe)
						fail_safe -= 1
						i.skip = 1
						i = i.else_if

				else if(stmt.else_block)
					RunBlock(stmt.else_block)
			// We don't need to skip you anymore.
			stmt.skip = 0

/*
	Proc: RunWhile
	Runs a while loop.
*/
		RunWhile(node/statement/WhileLoop/stmt)
			var/i=1
			while(Eval(stmt.cond) && Iterate(stmt.block, i++))
				continue
			status &= ~BREAKING

/*
	Proc:Iterate
	Runs a single iteration of a loop. Returns a value indicating whether or not to continue looping.
*/
		Iterate(node/BlockDefinition/block, count)
			RunBlock(block)
			if(max_iterations > 0 && count >= max_iterations)
				RaiseError(new/runtimeError/IterationLimitReached())
				return 0
			if(status & (BREAKING|RETURNING))
				return 0
			status &= ~CONTINUING
			return 1

/*
	Proc: GetFunction
	Finds a function in an accessible scope with the given name. Returns a <FunctionDefinition>.
*/
		GetFunction(name)
			var/scope/S = curScope
			while(S)
				if(S.functions.Find(name))
					return S.functions[name]
				S = S.parent
			RaiseError(new/runtimeError/UndefinedFunction(name))

/*
	Proc: GetVariable
	Finds a variable in an accessible scope and returns its value.
*/
		GetVariable(name)
			var/scope/S = curScope
			while(S)
				if(S.variables.Find(name))
					return S.variables[name]
				S = S.parent
			RaiseError(new/runtimeError/UndefinedVariable(name))

		GetVariableScope(name) //needed for when you reassign a variable in a higher scope
			var/scope/S = curScope
			while(S)
				if(S.variables.Find(name))
					return S
				S = S.parent

		IsVariableAccessible(name)
			var/scope/S = curScope
			while(S)
				if(S.variables.Find(name))
					return TRUE
				S = S.parent
			return FALSE

/*
	Proc: AssignVariable
	Assigns a value to a variable in a specific block.

	Parameters:
	name  - The name of the variable to assign.
	value - The value to assign to it.
	S     - The scope the variable resides in. If it is null, a scope with the variable already existing is found. If no scopes have a variable of the given name, the current scope is used.
*/
		AssignVariable(name, node/expression/value, scope/S=null)
			if(!S) S = GetVariableScope(name)
			if(!S) S = curScope
			if(!S) S = globalScope
			ASSERT(istype(S))
			if(istext(value) || isnum(value) || isnull(value))	value = new/node/expression/value/literal(value)
			else if(!istype(value) && isobject(value))			value = new/node/expression/value/reference(value)
			//TODO: check for invalid name
			S.variables["[name]"] = value


code:
/*
	Class: scope
	A runtime instance of a block. Used internally by the interpreter.
*/
scope
	var
		scope/parent = null
		node/BlockDefinition/block
		list
			functions
			variables

	New(node/BlockDefinition/B, scope/parent)
		src.block = B
		src.parent = parent
		src.variables = B.initial_variables.Copy()
		src.functions = B.functions.Copy()
		.=..()


code:
/*
File: Options
*/
var/const  //Ascii values of characters
	ascii_A  =65
	ascii_Z  =90
	ascii_a  =97
	ascii_z  =122
	ascii_DOLLAR = 36 // $
	ascii_ZERO=48
	ascii_NINE=57
	ascii_UNDERSCORE=95	// _

/*
	Class: n_scriptOptions
*/
n_scriptOptions
	proc
		CanStartID(char) //returns true if the character can start a variable, function, or keyword name (by default letters or an underscore)
			if(!isnum(char))char=text2ascii(char)
			return (char in ascii_A to ascii_Z) || (char in ascii_a to ascii_z) || char==ascii_UNDERSCORE || char==ascii_DOLLAR

		IsValidIDChar(char) //returns true if the character can be in the body of a variable, function, or keyword name (by default letters, numbers, and underscore)
			if(!isnum(char))char=text2ascii(char)
			return CanStartID(char) || IsDigit(char)

		IsDigit(char)
			if(!isnum(char))char=text2ascii(char)
			return char in ascii_ZERO to ascii_NINE

		IsValidID(id)    //returns true if all the characters in the string are okay to be in an identifier name
			if(!CanStartID(id)) //don't need to grab first char in id, since text2ascii does it automatically
				return 0
			if(lentext(id)==1) return 1
			for(var/i=2 to lentext(id))
				if(!IsValidIDChar(copytext(id, i, i+1)))
					return 0
			return 1

/*
	Class: nS_Options
	An implementation of <n_scriptOptions> for the n_Script language.
*/
	nS_Options
		var
			list
				symbols  		= list("(", ")", "\[", "]", ";", ",", "{", "}")     										//scanner - Characters that can be in symbols
/*
	Var: keywords
	An associative list used by the parser to parse keywords. Indices are strings which will trigger the keyword when parsed and the
	associated values are <nS_Keyword> types of which the <n_Keyword.Parse()> proc will be called.
*/
				keywords 	 	= list("if" = /n_Keyword/nS_Keyword/kwIf,  "else"  = /n_Keyword/nS_Keyword/kwElse, "elseif" = /n_Keyword/nS_Keyword/kwElseIf, \
													 "while"	  = /n_Keyword/nS_Keyword/kwWhile,		"break"	= /n_Keyword/nS_Keyword/kwBreak, \
													 "continue" = /n_Keyword/nS_Keyword/kwContinue, \
													 "return" = /n_Keyword/nS_Keyword/kwReturn, 		"def"   = /n_Keyword/nS_Keyword/kwDef)

			list
				assign_operators=list("="  = null, 																					 "&=" = "&",
												 			"|=" = "|",																					 	 "`=" = "`",
															"+=" = "+",																						 "-=" = "-",
															"*=" = "*",																						 "/=" = "/",
															"^=" = "^",
															"%=" = "%")

				unary_operators =list("!"  = /node/expression/operator/unary/LogicalNot, 		 "~"  = /node/expression/operator/unary/BitwiseNot,
															"-"  = /node/expression/operator/unary/Minus)

				binary_operators=list("==" = /node/expression/operator/binary/Equal, 			   "!="	= /node/expression/operator/binary/NotEqual,
															">"  = /node/expression/operator/binary/Greater, 			 "<" 	= /node/expression/operator/binary/Less,
															">=" = /node/expression/operator/binary/GreaterOrEqual,"<=" = /node/expression/operator/binary/LessOrEqual,
															"&&" = /node/expression/operator/binary/LogicalAnd,  	 "||"	= /node/expression/operator/binary/LogicalOr,
															"&"  = /node/expression/operator/binary/BitwiseAnd,  	 "|" 	= /node/expression/operator/binary/BitwiseOr,
															"`"  = /node/expression/operator/binary/BitwiseXor,  	 "+" 	= /node/expression/operator/binary/Add,
															"-"  = /node/expression/operator/binary/Subtract, 		 "*" 	= /node/expression/operator/binary/Multiply,
															"/"  = /node/expression/operator/binary/Divide, 			 "^" 	= /node/expression/operator/binary/Power,
															"%"  = /node/expression/operator/binary/Modulo)

		New()
			.=..()
			for(var/O in assign_operators+binary_operators+unary_operators)
				if(!symbols.Find(O)) symbols+=O


code:
/*
	File: Expressions
	Procedures for parsing expressions.
*/

/*
	Macros: Expression Macros
	OPERATOR   - A value indicating the parser currently expects a binary operator.
	VALUE			 - A value indicating the parser currently expects a value.
	SHIFT      - Tells the parser to push the current operator onto the stack.
	REDUCE		 - Tells the parser to reduce the stack.
*/
#define OPERATOR  1
#define VALUE     2
#define SHIFT     0
#define REDUCE    1

/*
	Class: nS_Parser
*/
/n_Parser/nS_Parser
	var
/*
	Var: expecting
	A variable which keeps track of whether an operator or value is expected. It should be either <OPERATOR> or <VALUE>. See <ParseExpression()>
	for more information.
*/
		expecting=VALUE

	proc
/*
	Proc: Precedence
	Compares two operators, decides which is higher in the order of operations, and returns <SHIFT> or <REDUCE>.
*/
		Precedence(node/expression/operator/top, node/expression/operator/input)
			if(istype(top))
				top=top.precedence
			if(istype(input))
				input=input:precedence
			if(top>=input)
				return REDUCE
			return SHIFT

/*
	Proc: GetExpression
	Takes a token expected to represent a value and returns an <expression> node.
*/
		GetExpression(token/T)
			if(!T) return
			if(istype(T, /node/expression))
				return T
			switch(T.type)
				if(/token/word)
					return new/node/expression/value/variable(T.value)
				if(/token/accessor)
					var
						token/accessor/A=T
						node/expression/value/variable/E//=new(A.member)
						stack/S=new()
					while(istype(A.object, /token/accessor))
						S.Push(A)
						A=A.object
					ASSERT(istext(A.object))

					while(A)
						var/node/expression/value/variable/V=new()
						V.id=new(A.member)
						if(E)
							V.object=E
						else
							V.object=new/node/identifier(A.object)
						E=V
						A=S.Pop()
					return E

				if(/token/number, /token/string)
					return new/node/expression/value/literal(T.value)

/*
	Proc: GetOperator
	Gets a path related to a token or string and returns an instance of the given type. This is used to get an instance of either a binary or unary
	operator from a token.

	Parameters:
	O		 - The input value. If this is a token, O is reset to the token's value.
			   When O is a string and is in L, its associated value is used as the path to instantiate.
	type - The desired type of the returned object.
	L		 - The list in which to search for O.

	See Also:
	- <GetBinaryOperator()>
	- <GetUnaryOperator()>
*/
		GetOperator(O, type=/node/expression/operator, L[])
			if(istype(O, type)) return O		//O is already the desired type
			if(istype(O, /token)) O=O:value //sets O to text
			if(istext(O))										//sets O to path
				if(L.Find(O)) O=L[O]
				else return null
			if(ispath(O))O=new O						//catches path from last check
			else return null								//Unknown type
			return O

/*
	Proc: GetBinaryOperator
	Uses <GetOperator()> to search for an instance of a binary operator type with which the given string is associated. For example, if
	O is set to "+", an <Add> node is returned.

	See Also:
	- <GetOperator()>
	- <GetUnaryOperator()>
*/
		GetBinaryOperator(O)
			return GetOperator(O, /node/expression/operator/binary, options.binary_operators)

/*
	Proc: GetUnaryOperator
	Uses <GetOperator()> to search for an instance of a unary operator type with which the given string is associated. For example, if
	O is set to "!", a <LogicalNot> node is returned.

	See Also:
	- <GetOperator()>
	- <GetBinaryOperator()>
*/
		GetUnaryOperator(O)
			return GetOperator(O, /node/expression/operator/unary,  options.unary_operators)

/*
	Proc: Reduce
	Takes the operator on top of the opr stack and assigns its operand(s). Then this proc pushes the value of that operation to the top
	of the val stack.
*/
		Reduce(stack/opr, stack/val)
			var/node/expression/operator/O=opr.Pop()
			if(!O) return
			if(!istype(O))
				errors+=new/scriptError("Error reducing expression - invalid operator.")
				return
			//Take O and assign its operands, popping one or two values from the val stack
			//depending on whether O is a binary or unary operator.
			if(istype(O, /node/expression/operator/binary))
				var/node/expression/operator/binary/B=O
				B.exp2=val.Pop()
				B.exp =val.Pop()
				val.Push(B)
			else
				O.exp=val.Pop()
				val.Push(O)

/*
	Proc: EndOfExpression
	Returns true if the current token represents the end of an expression.

	Parameters:
	end - A list of values to compare the current token to.
*/
		EndOfExpression(end[])
			if(!curToken)
				return 1
			if(istype(curToken, /token/symbol) && end.Find(curToken.value))
				return 1
			if(istype(curToken, /token/end) && end.Find(/token/end))
				return 1
			return 0

/*
	Proc: ParseExpression
	Uses the Shunting-yard algorithm to parse expressions.

	Notes:
	- When an opening parenthesis is found, then <ParseParenExpression()> is called to handle it.
	- The <expecting>  variable helps distinguish unary operators from binary operators (for cases like the - operator, which can be either).

	Articles:
	- <http://epaperpress.com/oper/>
	- <http://en.wikipedia.org/wiki/Shunting-yard_algorithm>

	See Also:
	- <ParseFunctionExpression()>
	- <ParseParenExpression()>
	- <ParseParamExpression()>
*/
		ParseExpression(list/end=list(/token/end), list/ErrChars=list("{", "}"), check_functions = 0)
			var/stack
				opr=new
				val=new
			src.expecting=VALUE
			var/loop = 0
			for()
				loop++
				if(loop > 800)
					errors+=new/scriptError("Too many nested tokens.")
					return

				if(EndOfExpression(end))
					break
				if(istype(curToken, /token/symbol) && ErrChars.Find(curToken.value))
					errors+=new/scriptError/BadToken(curToken)
					break

				if(index>tokens.len)																						//End of File
					errors+=new/scriptError/EndOfFile()
					break
				var/token/ntok
				if(index+1<=tokens.len)
					ntok=tokens[index+1]

				if(istype(curToken, /token/symbol) && curToken.value=="(")			//Parse parentheses expression
					if(expecting!=VALUE)
						errors+=new/scriptError/ExpectedToken("operator", curToken)
						NextToken()
						continue
					val.Push(ParseParenExpression())

				else if(istype(curToken, /token/symbol))												//Operator found.
					var/node/expression/operator/curOperator											//Figure out whether it is unary or binary and get a new instance.
					if(src.expecting==OPERATOR)
						curOperator=GetBinaryOperator(curToken)
						if(!curOperator)
							errors+=new/scriptError/ExpectedToken("operator", curToken)
							NextToken()
							continue
					else
						curOperator=GetUnaryOperator(curToken)
						if(!curOperator) 																						//given symbol isn't a unary operator
							errors+=new/scriptError/ExpectedToken("expression", curToken)
							NextToken()
							continue

					if(opr.Top() && Precedence(opr.Top(), curOperator)==REDUCE)		//Check order of operations and reduce if necessary
						Reduce(opr, val)
						continue
					opr.Push(curOperator)
					src.expecting=VALUE

				else if(ntok && ntok.value=="(" && istype(ntok, /token/symbol)\
											&& istype(curToken, /token/word))								//Parse function call

					if(!check_functions)

						var/token/preToken=curToken
						var/old_expect=src.expecting
						var/fex=ParseFunctionExpression()
						if(old_expect!=VALUE)
							errors+=new/scriptError/ExpectedToken("operator", preToken)
							NextToken()
							continue
						val.Push(fex)
					else
						errors+=new/scriptError/ParameterFunction(curToken)
						break

				else if(istype(curToken, /token/keyword)) 										//inline keywords
					var/n_Keyword/kw=options.keywords[curToken.value]
					kw=new kw(inline=1)
					if(kw)
						if(!kw.Parse(src))
							return
					else
						errors+=new/scriptError/BadToken(curToken)

				else if(istype(curToken, /token/end)) 													//semicolon found where it wasn't expected
					errors+=new/scriptError/BadToken(curToken)
					NextToken()
					continue
				else
					if(expecting!=VALUE)
						errors+=new/scriptError/ExpectedToken("operator", curToken)
						NextToken()
						continue
					val.Push(GetExpression(curToken))
					src.expecting=OPERATOR

				NextToken()

			while(opr.Top()) Reduce(opr, val) 																//Reduce the value stack completely
			.=val.Pop()                       																//Return what should be the last value on the stack
			if(val.Top())                     																//
				var/node/N=val.Pop()
				errors+=new/scriptError("Error parsing expression. Unexpected value left on stack: [N.ToString()].")
				return null

/*
	Proc: ParseFunctionExpression
	Parses a function call inside of an expression.

	See Also:
	- <ParseExpression()>
*/
		ParseFunctionExpression()
			var/node/expression/FunctionCall/exp=new
			exp.func_name=curToken.value
			NextToken() //skip function name
			NextToken() //skip open parenthesis, already found
			var/loops = 0

			for()
				loops++
				if(loops>=800)
					errors += new/scriptError("Too many nested expressions.")
					break
					//CRASH("Something TERRIBLE has gone wrong in ParseFunctionExpression ;__;")

				if(istype(curToken, /token/symbol) && curToken.value==")")
					return exp
				exp.parameters+=ParseParamExpression()
				if(errors.len)
					return exp
				if(curToken.value==","&&istype(curToken, /token/symbol))NextToken()	//skip comma
				if(istype(curToken, /token/end))																		//Prevents infinite loop...
					errors+=new/scriptError/ExpectedToken(")")
					return exp

/*
	Proc: ParseParenExpression
	Parses an expression that ends with a close parenthesis. This is used for parsing expressions inside of parentheses.

	See Also:
	- <ParseExpression()>
*/
		ParseParenExpression()
			if(!CheckToken("(", /token/symbol))
				return
			return new/node/expression/operator/unary/group(ParseExpression(list(")")))

/*
	Proc: ParseParamExpression
	Parses an expression that ends with either a comma or close parenthesis. This is used for parsing the parameters passed to a function call.

	See Also:
	- <ParseExpression()>
*/
		ParseParamExpression(var/check_functions = 0)
			var/cf = check_functions
			return ParseExpression(list(",", ")"), check_functions = cf)


code:
//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:33

/*
	File: Keywords
*/
var/const/KW_FAIL = 0 //Fatal error; stop parsing entire script.
var/const/KW_PASS = 1 //OK
var/const/KW_ERR  = 2 //Non-fatal error, keyword couldn't be handled properly. Ignore keyword but continue on.
var/const/KW_WARN = 3 //Warning

/*
var/const/Class: n_Keyword
var/const/Represents a special statement in the code triggered by a keyword.
*/
/n_Keyword
	New(inline=0)
		src.inline=inline
		return ..()

/*
	Var: inline
	1 if the keyword is in an expression (e.g. the new keyword in many languages), 0 otherwise (such as the if and else keywords).
*/
	var/inline

/*
	Proc: Parse
	Called when the parser finds a keyword in the code.

	Parameters:
	parser - The parser that created this object. You can use the parameter to manipulate the parser in order to add statements and blocks
	to its AST.
*/
	proc/Parse(n_Parser/parser)

/*
	Class: nS_Keyword
	A keyword in n_Script. By default these include return, if, else, while, and def. To enable or disable a keyword, change the
	<nS_Options.keywords> list.

	Behavior:
	When a parser is expecting a new statement, and a keyword listed in <nS_Options.keywords> is found, it will call the keyword's
	<n_Keyword.Parse()> proc.
*/
//
	nS_Keyword
		New(inline=0)
			if(inline)
				del src

		kwReturn
			Parse(n_Parser/nS_Parser/parser)
				.=KW_PASS
				if(istype(parser.curBlock, /node/BlockDefinition/GlobalBlock)) // Exit out of the program by setting the tokens list size to the same as index.
					parser.tokens.len = parser.index
					return
				var/node/statement/ReturnStatement/stmt=new
				parser.NextToken()   //skip 'return' token
				stmt.value=parser.ParseExpression()
				parser.curBlock.statements+=stmt

		kwIf
			Parse(n_Parser/nS_Parser/parser)
				.=KW_PASS
				var/node/statement/IfStatement/stmt=new
				parser.NextToken()  //skip 'if' token
				stmt.cond=parser.ParseParenExpression()
				if(!parser.CheckToken(")", /token/symbol))
					return KW_FAIL
				if(!parser.CheckToken("{", /token/symbol, skip=0)) //Token needs to be preserved for parse loop, so skip=0
					return KW_ERR
				parser.curBlock.statements+=stmt
				stmt.block=new
				parser.AddBlock(stmt.block)

		kwElseIf
			Parse(n_Parser/nS_Parser/parser)
				.=KW_PASS
				var/list/L=parser.curBlock.statements
				var/node/statement/IfStatement/ifstmt

				if(L && L.len)
					ifstmt = L[L.len] //Get the last statement in the current block
				if(!ifstmt || !istype(ifstmt) || ifstmt.else_if)
					parser.errors += new/scriptError/ExpectedToken("if statement", parser.curToken)
					return KW_FAIL

				var/node/statement/IfStatement/ElseIf/stmt = new
				parser.NextToken()  //skip 'if' token
				stmt.cond = parser.ParseParenExpression()
				if(!parser.CheckToken(")", /token/symbol))
					return KW_FAIL
				if(!parser.CheckToken("{", /token/symbol, skip=0)) //Token needs to be preserved for parse loop, so skip=0
					return KW_ERR
				parser.curBlock.statements+=stmt
				stmt.block=new
				ifstmt.else_if = stmt
				parser.AddBlock(stmt.block)

		kwElse
			Parse(n_Parser/nS_Parser/parser)
				.=KW_PASS
				var/list/L=parser.curBlock.statements
				var/node/statement/IfStatement/stmt
				if(L&&L.len) stmt=L[L.len] //Get the last statement in the current block
				if(!stmt || !istype(stmt) || stmt.else_block) //Ensure that it is an if statement
					parser.errors+=new/scriptError/ExpectedToken("if statement",parser.curToken)
					return KW_FAIL
				parser.NextToken()         //skip 'else' token
				if(!parser.CheckToken("{", /token/symbol, skip=0))
					return KW_ERR
				stmt.else_block=new()
				parser.AddBlock(stmt.else_block)

		kwWhile
			Parse(n_Parser/nS_Parser/parser)
				.=KW_PASS
				var/node/statement/WhileLoop/stmt=new
				parser.NextToken()  //skip 'while' token
				stmt.cond=parser.ParseParenExpression()
				if(!parser.CheckToken(")", /token/symbol))
					return KW_FAIL
				if(!parser.CheckToken("{", /token/symbol, skip=0))
					return KW_ERR
				parser.curBlock.statements+=stmt
				stmt.block=new
				parser.AddBlock(stmt.block)

		kwBreak
			Parse(n_Parser/nS_Parser/parser)
				.=KW_PASS
				if(istype(parser.curBlock, /node/BlockDefinition/GlobalBlock))
					parser.errors+=new/scriptError/BadToken(parser.curToken)
					. = KW_WARN
				var/node/statement/BreakStatement/stmt=new
				parser.NextToken()   //skip 'break' token
				parser.curBlock.statements+=stmt

		kwContinue
			Parse(n_Parser/nS_Parser/parser)
				.=KW_PASS
				if(istype(parser.curBlock, /node/BlockDefinition/GlobalBlock))
					parser.errors+=new/scriptError/BadToken(parser.curToken)
					. = KW_WARN
				var/node/statement/ContinueStatement/stmt=new
				parser.NextToken()   //skip 'break' token
				parser.curBlock.statements+=stmt

		kwDef
			Parse(n_Parser/nS_Parser/parser)
				.=KW_PASS
				var/node/statement/FunctionDefinition/def=new
				parser.NextToken() //skip 'def' token
				if(!parser.options.IsValidID(parser.curToken.value))
					parser.errors+=new/scriptError/InvalidID(parser.curToken)
					return KW_FAIL
				def.func_name=parser.curToken.value
				parser.NextToken()
				if(!parser.CheckToken("(", /token/symbol))
					return KW_FAIL
				for() //for now parameters can be separated by whitespace - they don't need a comma in between
					if(istype(parser.curToken, /token/symbol))
						switch(parser.curToken.value)
							if(",")
								parser.NextToken()
							if(")")
								break
							else
								parser.errors+=new/scriptError/BadToken(parser.curToken)
								return KW_ERR

					else if(istype(parser.curToken, /token/word))
						def.parameters+=parser.curToken.value
						parser.NextToken()
					else
						parser.errors+=new/scriptError/InvalidID(parser.curToken)
						return KW_ERR
				if(!parser.CheckToken(")", /token/symbol))
					return KW_FAIL

				if(istype(parser.curToken, /token/end)) //Function prototype
					parser.curBlock.statements+=def
				else if(parser.curToken.value=="{" && istype(parser.curToken, /token/symbol))
					def.block = new
					parser.curBlock.statements+=def
					parser.curBlock.functions[def.func_name]=def
					parser.AddBlock(def.block)
				else
					parser.errors+=new/scriptError/BadToken(parser.curToken)
					return KW_FAIL


code:
/*
	File: Parser
*/
/*
	Class: n_Parser
	An object that reads tokens and produces an AST (abstract syntax tree).
*/
/n_Parser
	var
/*
	Var: index
	The parser's current position in the token's list.
*/
		index = 1
		list
/*
	Var: tokens
	A list of tokens in the source code generated by a scanner.
*/
			tokens   = new
/*
	Var: errors
	A list of fatal errors found by the parser. If there are any items in this list, then it is not safe to run the returned AST.

	See Also:
	- <scriptError>
*/
			errors   = new
/*
	Var: warnings
	A list of non-fatal problems in the script.
*/
			warnings = new
		token
/*
	Var: curToken
	The token at <index> in <tokens>.
*/
			curToken
		stack
			blocks=new
		node/BlockDefinition
			GlobalBlock/global_block=new
			curBlock

	proc
/*
	Proc: Parse
	Reads the tokens and returns the AST's <GlobalBlock> node. Be sure to populate the tokens list before calling this procedure.
*/
		Parse()

/*
	Proc: NextToken
	Sets <curToken> to the next token in the <tokens> list, or null if there are no more tokens.
*/
		NextToken()
			if(index>=tokens.len)
				curToken=null
			else
				curToken=tokens[++index]
			return curToken

/*
	Class: nS_Parser
	An implmentation of a parser for n_Script.
*/
/n_Parser/nS_Parser
	var/n_scriptOptions/nS_Options/options
/*
	Constructor: New

	Parameters:
	tokens  - A list of tokens to parse.
	options - An object used for configuration.
*/
	New(tokens[], n_scriptOptions/options)
		src.tokens=tokens
		src.options=options
		curBlock=global_block
		return ..()

	Parse()
		ASSERT(tokens)
		for(,src.index<=src.tokens.len, src.index++)
			curToken=tokens[index]
			switch(curToken.type)
				if(/token/keyword)
					var/n_Keyword/kw=options.keywords[curToken.value]
					kw=new kw()
					if(kw)
						if(!kw.Parse(src))
							return
				if(/token/word)
					var/token/ntok
					if(index+1>tokens.len)
						errors+=new/scriptError/BadToken(curToken)
						continue
					ntok=tokens[index+1]
					if(!istype(ntok, /token/symbol))
						errors+=new/scriptError/BadToken(ntok)
						continue
					if(ntok.value=="(")
						ParseFunctionStatement()
					else if(options.assign_operators.Find(ntok.value))
						ParseAssignment()
					else
						errors+=new/scriptError/BadToken(ntok)
						continue
					if(!istype(curToken, /token/end))
						errors+=new/scriptError/ExpectedToken(";", curToken)
						continue
				if(/token/symbol)
					if(curToken.value=="}")
						if(!EndBlock())
							errors+=new/scriptError/BadToken(curToken)
							continue
					else
						errors+=new/scriptError/BadToken(curToken)
						continue
				if(/token/end)
					warnings+=new/scriptError/BadToken(curToken)
					continue
				else
					errors+=new/scriptError/BadToken(curToken)
					return
		return global_block

	proc
		CheckToken(val, type, err=1, skip=1)
			if(curToken.value!=val || !istype(curToken,type))
				if(err)
					errors+=new/scriptError/ExpectedToken(val, curToken)
				return 0
			if(skip)NextToken()
			return 1

		AddBlock(node/BlockDefinition/B)
			blocks.Push(curBlock)
			curBlock=B

		EndBlock()
			if(curBlock==global_block) return 0
			curBlock=blocks.Pop()
			return 1

		ParseAssignment()
			var/name=curToken.value
			if(!options.IsValidID(name))
				errors+=new/scriptError/InvalidID(curToken)
				return
			NextToken()
			var/t=options.binary_operators[options.assign_operators[curToken.value]]
			var/node/statement/VariableAssignment/stmt=new()
			stmt.var_name=new(name)
			NextToken()
			if(t)
				stmt.value=new t()
				stmt.value:exp=new/node/expression/value/variable(stmt.var_name)
				stmt.value:exp2=ParseExpression()
			else
				stmt.value=ParseExpression()
			curBlock.statements+=stmt

		ParseFunctionStatement()
			if(!istype(curToken, /token/word))
				errors+=new/scriptError("Bad identifier in function call.")
				return
			var/node/statement/FunctionCall/stmt=new
			stmt.func_name=curToken.value
			NextToken() //skip function name
			if(!CheckToken("(", /token/symbol)) //Check for and skip open parenthesis
				return
			var/loops = 0
			for()
				loops++
				if(loops>=800)
					errors +=new/scriptError("Cannot find ending params.")
					return

				if(!curToken)
					errors+=new/scriptError/EndOfFile()
					return
				if(istype(curToken, /token/symbol) && curToken.value==")")
					curBlock.statements+=stmt
					NextToken() //Skip close parenthesis
					return
				var/node/expression/P=ParseParamExpression(check_functions = 1)
				stmt.parameters+=P
				if(istype(curToken, /token/symbol) && curToken.value==",") NextToken()


code:
/*
	File: Scanner
*/
/*
	Class: n_Scanner
	An object responsible for breaking up source code into tokens for use by the parser.
*/
/n_Scanner
	var
		code
		list
/*
	Var: errors
	A list of fatal errors found by the scanner. If there are any items in this list, then it is not safe to parse the returned tokens.

	See Also:
	- <scriptError>
*/
			errors   = new
/*
	Var: warnings
	A list of non-fatal problems in the source code found by the scanner.
*/
			warnings = new

	proc
/*
	Proc: LoadCode
	Loads source code.
*/
		LoadCode(c)
			code=c

/*
	Proc: LoadCodeFromFile
	Gets the code from a file and calls <LoadCode()>.
*/
		LoadCodeFromFile(f)
			LoadCode(file2text(f))

/*
	Proc: Scan
	Runs the scanner and returns the resulting list of tokens. Ensure that <LoadCode()> has been called first.
*/
		Scan()

/*
	Class: nS_Scanner
	A scanner implementation for n_Script.
*/
/n_Scanner/nS_Scanner

	var
/*
	Variable: codepos
	The scanner's position in the source code.
*/
		codepos				 = 1
		line					 = 1
		linepos 			 = 0 										 //column=codepos-linepos
		n_scriptOptions/nS_Options/options

		commenting = 0
				// 1: single-line
				// 2: multi-line
		list
/*
	Variable: ignore
	A list of characters that are ignored by the scanner.

	Default Value:
	Whitespace
*/
			ignore 			 = list(" ", "\t", "\n") //Don't add tokens for whitespace
/*
	Variable: end_stmt
	A list of characters that end a statement. Each item may only be one character long.

	Default Value:
	Semicolon
*/
			end_stmt		 = list(";")
/*
	Variable: string_delim
	A list of characters that can start and end strings.

	Default Value:
	Double and single quotes.
*/
			string_delim = list("\"", "'")
/*
	Variable: delim
	A list of characters that denote the start of a new token. This list is automatically populated.
*/
			delim 			 = new

/*
	Macro: COL
	The current column number.
*/
	#define COL codepos-linepos

/*
	Constructor: New
	Parameters:
	code	 	- The source code to tokenize.
	options - An <nS_Options> object used to configure the scanner.
*/
	New(code, n_scriptOptions/nS_Options/options)
		.=..()
		ignore+= ascii2text(13) //Carriage return
		delim += ignore + options.symbols + end_stmt + string_delim
		src.options=options
		LoadCode(code)

	Scan() //Creates a list of tokens from source code
		var/list/tokens=new
		for(, src.codepos<=lentext(code), src.codepos++)

			var/char=copytext(code, codepos, codepos+1)
			var/nextchar=copytext(code, codepos+1, codepos+2)
			if(char=="\n")
				line++
				linepos=codepos

			if(ignore.Find(char))
				continue
			else if(char == "/" && (nextchar == "/" || nextchar == "*"))
				ReadComment()
			else if(end_stmt.Find(char))
				tokens+=new /token/end(char, line, COL)
			else if(string_delim.Find(char))
				codepos++ //skip string delimiter
				tokens+=ReadString(char)
			else if(options.CanStartID(char))
				tokens+=ReadWord()
			else if(options.IsDigit(char))
				tokens+=ReadNumber()
			else if(options.symbols.Find(char))
				tokens+=ReadSymbol()

		codepos=initial(codepos)
		line=initial(line)
		linepos=initial(linepos)
		return tokens

	proc
/*
	Proc: ReadString
	Reads a string in the source code into a token.

	Parameters:
	start - The character used to start the string.
*/
		ReadString(start)
			var
				buf
			for(, codepos <= lentext(code), codepos++)//codepos to lentext(code))
				var/char=copytext(code, codepos, codepos+1)
				switch(char)
					if("\\")					//Backslash (\) encountered in string
						codepos++       //Skip next character in string, since it was escaped by a backslash
						char=copytext(code, codepos, codepos+1)
						switch(char)
							if("\\")      //Double backslash
								buf+="\\"
							if("n")				//\n Newline
								buf+="\n"
							else
								if(char==start) //\" Doublequote
									buf+=start
								else				//Unknown escaped text
									buf+=char
					if("\n")
						. = new/token/string(buf, line, COL)
						errors+=new/scriptError("Unterminated string. Newline reached.", .)
						line++
						linepos=codepos
						break
					else
						if(char==start) //string delimiter found, end string
							break
						else
							buf+=char     //Just a normal character in a string
			if(!.) return new/token/string(buf, line, COL)

/*
	Proc: ReadWord
	Reads characters separated by an item in <delim> into a token.
*/
		ReadWord()
			var
				char=copytext(code, codepos, codepos+1)
				buf
			while(!delim.Find(char) && codepos<=lentext(code))
				buf+=char
				char=copytext(code, ++codepos, codepos+1)
			codepos-- //allow main Scan() proc to read the delimiter
			if(options.keywords.Find(buf))
				return new /token/keyword(buf, line, COL)
			else
				return new /token/word(buf, line, COL)

/*
	Proc: ReadSymbol
	Reads a symbol into a token.
*/
		ReadSymbol()
			var
				char=copytext(code, codepos, codepos+1)
				buf

			while(options.symbols.Find(buf+char))
				buf+=char
				if(++codepos>lentext(code)) break
				char=copytext(code, codepos, codepos+1)

			codepos-- //allow main Scan() proc to read the next character
			return new /token/symbol(buf, line, COL)

/*
	Proc: ReadNumber
	Reads a number into a token.
*/
		ReadNumber()
			var
				char=copytext(code, codepos, codepos+1)
				buf
				dec=0

			while(options.IsDigit(char) || (char=="." && !dec))
				if(char==".") dec=1
				buf+=char
				codepos++
				char=copytext(code, codepos, codepos+1)
			var/token/number/T=new(buf, line, COL)
			if(isnull(text2num(buf)))
				errors+=new/scriptError("Bad number: ", T)
				T.value=0
			codepos-- //allow main Scan() proc to read the next character
			return T

/*
	Proc: ReadComment
	Reads a comment and outputs the type of comment
*/

		ReadComment()
			var
				char=copytext(code, codepos, codepos+1)
				nextchar=copytext(code, codepos+1, codepos+2)
				charstring = char+nextchar
				comm = 1
					// 1: single-line comment
					// 2: multi-line comment
				expectedend = 0

			if(charstring == "//" || charstring == "/*")
				if(charstring == "/*")
					comm = 2 // starts a multi-line comment

				while(comm)
					if(++codepos>lentext(code)) break

					if(expectedend) // ending statement expected...
						char = copytext(code, codepos, codepos+1)
						if(char == "/") // ending statement found - beak the comment
							comm = 0
							break

					if(comm == 2)
						// multi-line comments are broken by ending statements
						char = copytext(code, codepos, codepos+1)
						if(char == "*")
							expectedend = 1
							continue
					else
						char = copytext(code, codepos, codepos+1)
						if(char == "\n")
							comm = 0
							break

					if(expectedend) expectedend = 0

				if(comm == 2)
					errors+=new/scriptError/UnterminatedComment()


code:
//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:33

/*
	Class: Token
	Represents an entity and position in the source code.
*/
/token
	var/value
	var/line
	var/column

	New(v, l=0, c=0)
		value=v
		line=l
		column=c

	string
	symbol
	word
	keyword
	number
		New()
			.=..()
			if(!isnum(value))
				value=text2num(value)
				ASSERT(!isnull(value))
	accessor
		var/object
		var/member

		New(object, member, l=0, c=0)
			src.object=object
			src.member=member
			src.value="[object].[member]" //for debugging only
			src.line=l
			src.column=c

	end


code:
/stack
	var/list
		contents=new
	proc
		Push(value)
			contents+=value

		Pop()
			if(!contents.len) return null
			. = contents[contents.len]
			contents.len--

		Top() //returns the item on the top of the stack without removing it
			if(!contents.len) return null
			return contents[contents.len]

		Copy()
			var/stack/S=new()
			S.contents=src.contents.Copy()
			return S

		Clear()
			contents.Cut()


code:
/obj/machinery/keycard_auth
	name = "Keycard Authentication Device"
	desc = "This device is used to trigger station functions, which require more than one ID card to authenticate."
	icon = 'icons/obj/monitors.dmi'
	icon_state = "auth_off"
	var/active = 0 //This gets set to 1 on all devices except the one where the initial request was made.
	var/event = ""
	var/screen = 1
	var/confirmed = 0 //This variable is set by the device that confirms the request.
	var/confirm_delay = 20 //(2 seconds)
	var/busy = 0 //Busy when waiting for authentication or an event request has been sent from this device.
	var/obj/machinery/keycard_auth/event_source
	var/mob/event_triggered_by
	var/mob/event_confirmed_by
	//1 = select event
	//2 = authenticate
	anchored = 1.0
	use_power = 1
	idle_power_usage = 2
	active_power_usage = 6
	power_channel = ENVIRON

/obj/machinery/keycard_auth/attack_ai(mob/user as mob)
	user << "The station AI is not to interact with these devices."
	return

/obj/machinery/keycard_auth/attack_paw(mob/user as mob)
	user << "You are too primitive to use this device."
	return

/obj/machinery/keycard_auth/attackby(obj/item/weapon/W as obj, mob/user as mob, params)
	if(stat & (NOPOWER|BROKEN))
		user << "This device is not powered."
		return
	if(istype(W,/obj/item/weapon/card/id))
		var/obj/item/weapon/card/id/ID = W
		if(access_keycard_auth in ID.access)
			if(active == 1)
				//This is not the device that made the initial request. It is the device confirming the request.
				if(event_source)
					event_source.confirmed = 1
					event_source.event_confirmed_by = usr
			else if(screen == 2)
				event_triggered_by = usr
				broadcast_request() //This is the device making the initial event request. It needs to broadcast to other devices

/obj/machinery/keycard_auth/power_change()
	if(powered(ENVIRON))
		stat &= ~NOPOWER
		icon_state = "auth_off"
	else
		stat |= NOPOWER

/obj/machinery/keycard_auth/attack_hand(mob/user as mob)
	if(user.stat || stat & (NOPOWER|BROKEN))
		user << "This device is not powered."
		return
	if(busy)
		user << "This device is busy."
		return

	user.set_machine(src)

	var/dat = "
Keycard Authentication Device
"

	dat += "This device is used to trigger some high security events. It requires the simultaneous swipe of two high-level ID cards."
	dat += "

"

	if(screen == 1)
		dat += "Select an event to trigger:
"
		dat += "
Red alert
"
		dat += "
Emergency Maintenance Access
"
		dat += "
"
		user << browse(dat, "window=keycard_auth;size=500x250")
	if(screen == 2)
		dat += "Please swipe your card to authorize the following event: [event]"
		dat += "
Back"
		user << browse(dat, "window=keycard_auth;size=500x250")
	return

/obj/machinery/keycard_auth/Topic(href, href_list)
	if(..())
		return
	if(busy)
		usr << "This device is busy."
		return
	if(href_list["triggerevent"])
		event = href_list["triggerevent"]
		screen = 2
	if(href_list["reset"])
		reset()

	updateUsrDialog()
	return

/obj/machinery/keycard_auth/proc/reset()
	active = 0
	event = ""
	screen = 1
	confirmed = 0
	event_source = null
	icon_state = "auth_off"
	event_triggered_by = null
	event_confirmed_by = null

/obj/machinery/keycard_auth/proc/broadcast_request()
	icon_state = "auth_on"
	for(var/obj/machinery/keycard_auth/KA in world)
		if(KA == src) continue
		KA.reset()
		spawn()
			KA.receive_request(src)

	sleep(confirm_delay)
	if(confirmed)
		confirmed = 0
		trigger_event(event)
		log_game("[key_name(event_triggered_by)] triggered and [key_name(event_confirmed_by)] confirmed event [event]")
		message_admins("[key_name(event_triggered_by)] triggered and [key_name(event_confirmed_by)] confirmed event [event]")
	reset()

/obj/machinery/keycard_auth/proc/receive_request(var/obj/machinery/keycard_auth/source)
	if(stat & (BROKEN|NOPOWER))
		return
	event_source = source
	busy = 1
	active = 1
	icon_state = "auth_on"

	sleep(confirm_delay)

	event_source = null
	icon_state = "auth_off"
	active = 0
	busy = 0

/obj/machinery/keycard_auth/proc/trigger_event()
	switch(event)
		if("Red alert")
			set_security_level(SEC_LEVEL_RED)
			feedback_inc("alert_keycard_auth_red",1)
		if("Emergency Maintenance Access")
			make_maint_all_access()
			feedback_inc("alert_keycard_auth_maint",1)

/var/emergency_access = 0
/proc/make_maint_all_access()
	for(var/area/maintenance/A in world)
		for(var/obj/machinery/door/airlock/D in A)
			D.emergency = 1
			D.update_icon(0)
	minor_announce("Access restrictions on maintenance and external airlocks have been lifted.", "Attention! Station-wide emergency declared!",1)
	emergency_access = 1

/proc/revoke_maint_all_access()
	for(var/area/maintenance/A in world)
		for(var/obj/machinery/door/airlock/D in A)
			D.emergency = 0
			D.update_icon(0)
	minor_announce("Access restrictions in maintenance areas have been restored.", "Attention! Station-wide emergency rescinded:")
	emergency_access = 0


code:
/var/security_level = 0
//0 = code green
//1 = code blue
//2 = code red
//3 = code delta

//config.alert_desc_blue_downto

/proc/set_security_level(var/level)
	switch(level)
		if("green")
			level = SEC_LEVEL_GREEN
		if("blue")
			level = SEC_LEVEL_BLUE
		if("red")
			level = SEC_LEVEL_RED
		if("delta")
			level = SEC_LEVEL_DELTA

	//Will not be announced if you try to set to the same level as it already is
	if(level >= SEC_LEVEL_GREEN && level <= SEC_LEVEL_DELTA && level != security_level)
		switch(level)
			if(SEC_LEVEL_GREEN)
				minor_announce(config.alert_desc_green, "Attention! Security level lowered to green:")
				security_level = SEC_LEVEL_GREEN
				for(var/obj/machinery/firealarm/FA in world)
					if(FA.z == ZLEVEL_STATION)
						FA.update_icon()
			if(SEC_LEVEL_BLUE)
				if(security_level < SEC_LEVEL_BLUE)
					minor_announce(config.alert_desc_blue_upto, "Attention! Security level elevated to blue:",1)
				else
					minor_announce(config.alert_desc_blue_downto, "Attention! Security level lowered to blue:")
				security_level = SEC_LEVEL_BLUE
				for(var/obj/machinery/firealarm/FA in world)
					if(FA.z == ZLEVEL_STATION)
						FA.update_icon()
			if(SEC_LEVEL_RED)
				if(security_level < SEC_LEVEL_RED)
					minor_announce(config.alert_desc_red_upto, "Attention! Code red!",1)
				else
					minor_announce(config.alert_desc_red_downto, "Attention! Code red!")
				security_level = SEC_LEVEL_RED

				/*	- At the time of commit, setting status displays didn't work properly
				var/obj/machinery/computer/communications/CC = locate(/obj/machinery/computer/communications,world)
				if(CC)
					CC.post_status("alert", "redalert")*/

				for(var/obj/machinery/firealarm/FA in world)
					if(FA.z == ZLEVEL_STATION)
						FA.update_icon()
			if(SEC_LEVEL_DELTA)
				minor_announce(config.alert_desc_delta, "Attention! Delta security level reached!",1)
				security_level = SEC_LEVEL_DELTA
				for(var/obj/machinery/firealarm/FA in world)
					if(FA.z == ZLEVEL_STATION)
						FA.update_icon()
	else
		return

/proc/get_security_level()
	switch(security_level)
		if(SEC_LEVEL_GREEN)
			return "green"
		if(SEC_LEVEL_BLUE)
			return "blue"
		if(SEC_LEVEL_RED)
			return "red"
		if(SEC_LEVEL_DELTA)
			return "delta"

/proc/num2seclevel(var/num)
	switch(num)
		if(SEC_LEVEL_GREEN)
			return "green"
		if(SEC_LEVEL_BLUE)
			return "blue"
		if(SEC_LEVEL_RED)
			return "red"
		if(SEC_LEVEL_DELTA)
			return "delta"

/proc/seclevel2num(var/seclevel)
	switch( lowertext(seclevel) )
		if("green")
			return SEC_LEVEL_GREEN
		if("blue")
			return SEC_LEVEL_BLUE
		if("red")
			return SEC_LEVEL_RED
		if("delta")
			return SEC_LEVEL_DELTA

/*DEBUG
/mob/verb/set_thing0()
	set_security_level(0)
/mob/verb/set_thing1()
	set_security_level(1)
/mob/verb/set_thing2()
	set_security_level(2)
/mob/verb/set_thing3()
	set_security_level(3)
*/


code:
//use this define to highlight docking port bounding boxes (ONLY FOR DEBUG USE)
//#define DOCKING_PORT_HIGHLIGHT

#define ALLTURFS 0
#define NONSPACE 1
#define SHUTTLEONLY 2
#define AREAONLY 3

//NORTH default dir
/obj/docking_port
	invisibility = 101
	icon = 'icons/obj/device.dmi'
	//icon = 'icons/dirsquare.dmi'
	icon_state = "pinonfar"

	unacidable = 1
	anchored = 1

	var/id
	dir = NORTH		//this should point -away- from the dockingport door, ie towards the ship
	var/width = 0	//size of covered area, perpendicular to dir
	var/height = 0	//size of covered area, paralell to dir
	var/dwidth = 0	//position relative to covered area, perpendicular to dir
	var/dheight = 0	//position relative to covered area, parallel to dir
	var/turfcheck = NONSPACE

	//these objects are indestructable
/obj/docking_port/Destroy()
	return QDEL_HINT_LETMELIVE

//returns a list(x0,y0, x1,y1) where points 0 and 1 are bounding corners of the projected rectangle
/obj/docking_port/proc/return_coords(_x, _y, _dir)
	if(!_dir)
		_dir = dir
	if(!_x)
		_x = x
	if(!_y)
		_y = y

	//byond's sin and cos functions are inaccurate. This is faster and perfectly accurate
	var/cos = 1
	var/sin = 0
	switch(_dir)
		if(WEST)
			cos = 0
			sin = 1
		if(SOUTH)
			cos = -1
			sin = 0
		if(EAST)
			cos = 0
			sin = -1

	return list(
		_x + (-dwidth*cos) - (-dheight*sin),
		_y + (-dwidth*sin) + (-dheight*cos),
		_x + (-dwidth+width-1)*cos - (-dheight+height-1)*sin,
		_y + (-dwidth+width-1)*sin + (-dheight+height-1)*cos
		)

//returns turfs within our projected rectangle in a specific order.
//this ensures that turfs are copied over in the same order, regardless of any rotation
/obj/docking_port/proc/return_ordered_turfs(_x, _y, _z, _dir, area/A)
	if(!_dir)
		_dir = dir
	if(!_x)
		_x = x
	if(!_y)
		_y = y
	if(!_z)
		_z = z
	var/cos = 1
	var/sin = 0
	switch(_dir)
		if(WEST)
			cos = 0
			sin = 1
		if(SOUTH)
			cos = -1
			sin = 0
		if(EAST)
			cos = 0
			sin = -1

	. = list()

	var/xi
	var/yi
	for(var/dx=0, dx<width, ++dx)
		for(var/dy=0, dy<height, ++dy)
			xi = _x + (dx-dwidth)*cos - (dy-dheight)*sin
			yi = _y + (dy-dheight)*cos + (dx-dwidth)*sin
			var/turf/T = locate(xi, yi, _z)
			if(A)
				if(get_area(T) == A)
					. += T
				else
					. += null
			else
				. += T

#ifdef DOCKING_PORT_HIGHLIGHT
//Debug proc used to highlight bounding area
/obj/docking_port/proc/highlight(_color)
	var/list/L = return_coords()
	var/turf/T0 = locate(L[1],L[2],z)
	var/turf/T1 = locate(L[3],L[4],z)
	for(var/turf/T in block(T0,T1))
		T.color = _color
		T.maptext = null
	if(_color)
		var/turf/T = locate(L[1], L[2], z)
		T.color = "#0f0"
		T = locate(L[3], L[4], z)
		T.color = "#00f"
#endif

//return first-found touching dockingport
/obj/docking_port/proc/get_docked()
	return locate(/obj/docking_port/stationary) in loc

/obj/docking_port/proc/getDockedId()
	var/obj/docking_port/P = get_docked()
	if(P) return P.id

/obj/docking_port/stationary
	name = "dock"

	var/turf_type = /turf/space
	var/area_type = /area/space

/obj/docking_port/stationary/New()
	..()
	SSshuttle.stationary += src
	if(!id)
		id = "[SSshuttle.stationary.len]"
	if(name == "dock")
		name = "dock[SSshuttle.stationary.len]"

	#ifdef DOCKING_PORT_HIGHLIGHT
	highlight("#f00")
	#endif

//returns first-found touching shuttleport
/obj/docking_port/stationary/get_docked()
	return locate(/obj/docking_port/mobile) in loc
	/*
	for(var/turf/T in return_ordered_turfs())
		. = locate(/obj/docking_port/mobile) in loc
		if(.)
			return .
	*/

/obj/docking_port/stationary/transit
	name = "In Transit"
	turf_type = /turf/space/transit

/obj/docking_port/stationary/transit/New()
	..()
	SSshuttle.transit += src

/obj/docking_port/mobile
	icon_state = "mobile"
	name = "shuttle"
	icon_state = "pinonclose"

	var/area/shuttle/areaInstance

	var/timer						//used as a timer (if you want time left to complete move, use timeLeft proc)
	var/mode = SHUTTLE_IDLE			//current shuttle mode (see global defines)
	var/callTime = 50				//time spent in transit (deciseconds)

	var/travelDir = 0			//direction the shuttle would travel in

	var/obj/docking_port/stationary/destination
	var/obj/docking_port/stationary/previous

/obj/docking_port/mobile/New()
	..()
	SSshuttle.mobile += src

	var/area/A = get_area(src)
	if(istype(A, /area/shuttle))
		areaInstance = A

	if(!id)
		id = "[SSshuttle.mobile.len]"
	if(name == "shuttle")
		name = "shuttle[SSshuttle.mobile.len]"

	if(!areaInstance)
		areaInstance = new()
		areaInstance.name = name
		areaInstance.contents += return_ordered_turfs()

	#ifdef DOCKING_PORT_HIGHLIGHT
	highlight("#0f0")
	#endif

//this is a hook for custom behaviour. Maybe at some point we could add checks to see if engines are intact
/obj/docking_port/mobile/proc/canMove()
	return 0	//0 means we can move

//this is to check if this shuttle can physically dock at dock S
/obj/docking_port/mobile/proc/canDock(obj/docking_port/stationary/S)
	if(!istype(S))
		return 1
	if(istype(S, /obj/docking_port/stationary/transit))
		return 0
	//check dock is big enough to contain us
	if(dwidth > S.dwidth)
		return 2
	if(width-dwidth > S.width-S.dwidth)
		return 3
	if(dheight > S.dheight)
		return 4
	if(height-dheight > S.height-S.dheight)
		return 5
	//check the dock isn't occupied
	if(S.get_docked())
		return 6
	return 0	//0 means we can dock

//call the shuttle to destination S
/obj/docking_port/mobile/proc/request(obj/docking_port/stationary/S)
	if(canDock(S))
		ERROR("[type](\"[name]\") cannot dock at [S]\")")
		return 1	//we can't dock at S

	switch(mode)
		if(SHUTTLE_CALL)
			if(S == destination)
				if(world.time <= timer)
					timer = world.time
			else
				destination = S
				timer = world.time
		if(SHUTTLE_RECALL)
			if(S == destination)
				timer = world.time - timeLeft(1)
			else
				destination = S
				timer = world.time
			mode = SHUTTLE_CALL
		else
			destination = S
			mode = SHUTTLE_CALL
			timer = world.time
			enterTransit()		//hyperspace

//recall the shuttle to where it was previously
/obj/docking_port/mobile/proc/cancel()
	if(mode != SHUTTLE_CALL)
		return

	timer = world.time - timeLeft(1)
	mode = SHUTTLE_RECALL

/obj/docking_port/mobile/proc/enterTransit()
	previous = null
//		if(!destination)
//			return
	var/obj/docking_port/stationary/S0 = get_docked()
	var/obj/docking_port/stationary/S1 = findTransitDock()
	if(S1)
		if(dock(S1))
			WARNING("shuttle \"[id]\" could not enter transit space. Docked at [S0 ? S0.id : "null"]. Transit dock [S1 ? S1.id : "null"].")
		else
			previous = S0
	else
		WARNING("shuttle \"[id]\" could not enter transit space. S0=[S0 ? S0.id : "null"] S1=[S1 ? S1.id : "null"]")

//this is the main proc. It instantly moves our mobile port to stationary port S1
//it handles all the generic behaviour, such as sanity checks, closing doors on the shuttle, stunning mobs, etc
/obj/docking_port/mobile/proc/dock(obj/docking_port/stationary/S1)
	. = canDock(S1)
	if(.)
		ERROR("[type](\"[name]\") cannot dock at [S1]")
		return .

	if(canMove())
		return -1

	closePortDoors()

//		//rotate transit docking ports, so we don't need zillions of variants
//		if(istype(S1, /obj/docking_port/stationary/transit))
//			S1.dir = turn(NORTH, -travelDir)

	var/obj/docking_port/stationary/S0 = get_docked()
	var/turf_type = /turf/space
	var/area_type = /area/space
	if(S0)
		if(S0.turf_type)
			turf_type = S0.turf_type
		if(S0.area_type)
			area_type = S0.area_type
	var/list/L0
	if(turfcheck == AREAONLY)
		L0 = return_ordered_turfs(x, y, z, dir, areaInstance)
	else
		L0 = return_ordered_turfs()
	var/list/L1 = return_ordered_turfs(S1.x, S1.y, S1.z, S1.dir)

	//remove area surrounding docking port
	if(areaInstance.contents.len)
		var/area/A0 = locate("[area_type]")
		if(!A0)
			A0 = new area_type(null)
		for(var/turf/T0 in L0)
			A0.contents += T0

	//move or squish anything in the way ship at destination
	roadkill(L1, S1.dir)

	for(var/i=1, i<=L0.len, ++i)
		var/turf/T0 = L0[i]
		if(!T0)
			continue
		if(src.turfcheck)
			switch(turfcheck)
				if (NONSPACE) //only move non-space turfs!
					if(istype(T0, /turf/space))
						continue
				if (SHUTTLEONLY)
					if(!T0.shuttle)
						continue
				else
					if(!T0)
						continue

		var/turf/T1 = L1[i]
		if(!T1)
			continue

		T0.copyTurf(T1)
		areaInstance.contents += T1

		//copy over air
		if(istype(T1, /turf/simulated))
			var/turf/simulated/Ts1 = T1
			Ts1.copy_air_with_tile(T0)

		//move mobile to new location
		loc = S1.loc
		dir = S1.dir

		//move all objects
		for(var/obj/O in T0)
		//	areaInstance.contents += O
			if(O.invisibility >= 101)
				continue
		//	if(O == T0.lighting_object)
		//		continue
			O.loc = T1
			O.update_all_lights()

			//close open doors
			if(istype(O, /obj/machinery/door))
				var/obj/machinery/door/Door = O
				spawn(-1)
					if(Door)
						Door.close()

		for(var/mob/M in T0)
			if(!M.move_on_shuttle)
				continue
			M.loc = T1

			//docking turbulence
			if(M.client)
				spawn(0)
					if(M.buckled)
						shake_camera(M, 2, 1) // turn it down a bit come on
					else
						shake_camera(M, 7, 1)
			if(istype(M, /mob/living/carbon))
				if(!M.buckled)
					M.Weaken(3)
		if(!istype(T0, /turf/space) && (turfcheck == NONSPACE || turfcheck == ALLTURFS))
			T0.ChangeTurf(turf_type)

	//air system updates
	for(var/turf/T1 in L1)
//		T1.shift_to_subarea()
		SSair.remove_from_active(T1)
		T1.CalculateAdjacentTurfs()
		SSair.add_to_active(T1,1)
		T1.update_overlay()

	for(var/turf/T0 in L0)
//		T0.shift_to_subarea()
		SSair.remove_from_active(T0)
		T0.CalculateAdjacentTurfs()
		SSair.add_to_active(T0,1)
		T0.update_overlay()

/*
	if(istype(S1, /obj/docking_port/stationary/transit))
		var/d = turn(dir, 180 + travelDir)
		for(var/turf/space/transit/T in S1.return_ordered_turfs())
			T.pushdirection = d
			T.update_icon()
*/

/obj/docking_port/mobile/proc/findTransitDock()
	var/obj/docking_port/stationary/transit/T = SSshuttle.getDock("[id]_transit")
	if(T && !canDock(T))
		return T
/*	commented out due to issues with rotation
	for(var/obj/docking_port/stationary/transit/S in SSshuttle.transit)
		if(S.id)
			continue
		if(!canDock(S))
			return S
*/

//shuttle-door closing is handled in the dock() proc whilst looping through turfs
//this one closes the door where we are docked at, if there is one there.
/obj/docking_port/mobile/proc/closePortDoors()
	var/turf/T = get_step(loc, turn(dir,180))
	if(T)
		var/obj/machinery/door/Door = locate() in T
		if(Door)
			spawn(-1)
				Door.close()

/obj/docking_port/mobile/proc/roadkill(list/L, dir, x, y)
	for(var/turf/T in L)
		for(var/atom/movable/AM in T)
			if(istype(AM, /atom/movable/lighting_overlay)) //This'd be a whole nother level of dumb
				continue
			if(ismob(AM))
				if(istype(AM, /mob/living))
					var/mob/living/M = AM
					M.Paralyse(10)
					M.take_organ_damage(80)
					M.anchored = 0
				else
					continue

			if(!AM.anchored)
				step(AM, dir)
			else
				if((turfcheck == NONSPACE || turfcheck == ALLTURFS))
					qdel(AM)
		T.reconsider_lights()
		if(!istype(T,/turf/space) && (turfcheck == NONSPACE || turfcheck == ALLTURFS))
			T.ChangeTurf(T.baseturf)
/*
//used to check if atom/A is within the shuttle's bounding box
/obj/docking_port/mobile/proc/onShuttleCheck(atom/A)
	var/turf/T = get_turf(A)
	if(!T)
		return 0

	var/list/L = return_coords()
	if(L[1] > L[3])
		L.Swap(1,3)
	if(L[2] > L[4])
		L.Swap(2,4)

	if(L[1] <= T.x && T.x <= L[3])
		if(L[2] <= T.y && T.y <= L[4])
			return 1
	return 0
*/
//used by shuttle subsystem to check timers
/obj/docking_port/mobile/proc/check()
	var/timeLeft = timeLeft(1)
	if(timeLeft <= 0)
		switch(mode)
			if(SHUTTLE_CALL)
				if(dock(destination))
					setTimer(20)	//can't dock for some reason, try again in 2 seconds
					return
			if(SHUTTLE_RECALL)
				if(dock(previous))
					setTimer(20)	//can't dock for some reason, try again in 2 seconds
					return
		mode = SHUTTLE_IDLE
		timer = 0
		destination = null

/obj/docking_port/mobile/proc/setTimer(wait)
	if(timer <= 0)
		timer = world.time
	timer += wait - timeLeft(1)

//returns timeLeft
/obj/docking_port/mobile/proc/timeLeft(divisor)
	if(divisor <= 0)
		divisor = 10
	if(!timer)
		return round(callTime/divisor, 1)
	return max( round((timer+callTime-world.time)/divisor,1), 0 )

/obj/docking_port/mobile/proc/getStatusText()
	var/obj/docking_port/stationary/dockedAt = get_docked()
	. = (dockedAt && dockedAt.name) ? dockedAt.name : "unknown"
	if(istype(dockedAt, /obj/docking_port/stationary/transit))
		var/obj/docking_port/stationary/dst
		if(mode == SHUTTLE_RECALL)
			dst = previous
		else
			dst = destination
		. += " towards [dst ? dst.name : "unknown location"] ([timeLeft(600)]mins)"

/obj/machinery/computer/shuttle
	name = "Shuttle Console"
	icon = 'icons/obj/computer.dmi'
	icon_state = "shuttle"
	req_access = list( )
	circuit = /obj/item/weapon/circuitboard/shuttle
	var/shuttleId
	var/possible_destinations = ""
	var/admin_controlled

/obj/machinery/computer/shuttle/New(location, obj/item/weapon/circuitboard/shuttle/C)
	..()
	if(istype(C))
		possible_destinations = C.possible_destinations
		shuttleId = C.shuttleId

/obj/machinery/computer/shuttle/attack_hand(user as mob)
	if(..(user))
		return
	src.add_fingerprint(usr)

	var/list/options = params2list(possible_destinations)
	var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId)
	var/dat = "Status: [M ? M.getStatusText() : "*Missing*"]

"
	if(M)
		var/destination_found
		for(var/obj/docking_port/stationary/S in SSshuttle.stationary)
			if(!options.Find(S.id))
				continue
			if(M.canDock(S))
				continue
			destination_found = 1
			dat += "Send to [S.name]
"
		if(!destination_found)
			dat += "Shuttle Locked
"
			if(admin_controlled)
				dat += "Authorized personnel only
"
				dat += "Request Authorization
"
	dat += "Close"

	var/datum/browser/popup = new(user, "computer", M ? M.name : "shuttle", 300, 200)
	popup.set_content("[dat]")
	popup.set_title_image(usr.browse_rsc_icon(src.icon, src.icon_state))
	popup.open()

/obj/machinery/computer/shuttle/Topic(href, href_list)
	if(..())
		return
	usr.set_machine(src)
	src.add_fingerprint(usr)
	if(!allowed(usr))
		usr << "Access denied."
		return

	if(href_list["move"])
		switch(SSshuttle.moveShuttle(shuttleId, href_list["move"], 1))
			if(0)	usr << "Shuttle received message and will be sent shortly."
			if(1)	usr << "Invalid shuttle requested."
			else	usr << "Unable to comply."

/obj/machinery/computer/shuttle/emag_act(mob/user as mob)
	if(!emagged)
		src.req_access = list()
		emagged = 1
		user << " You fried the consoles ID checking system."

/obj/machinery/computer/shuttle/ferry
	name = "transport ferry console"
	circuit = /obj/item/weapon/circuitboard/ferry
	shuttleId = "ferry"
	possible_destinations = "ferry_home;ferry_away"

/obj/machinery/computer/shuttle/ferry/request
	name = "ferry console"
	circuit = /obj/item/weapon/circuitboard/ferry/request
	var/cooldown //prevents spamming admins
	possible_destinations = "ferry_home"
	admin_controlled = 1

/obj/machinery/computer/shuttle/ferry/request/Topic(href, href_list)
	..()
	if(href_list["request"])
		if(cooldown)
			return
		cooldown = 1
		usr << "Your request has been recieved by Centcom."
		admins << "FERRY: [key_name(usr)] (?) (JMP) (Move Ferry) is requesting to move the transport ferry to Centcom."
		spawn(600) //One minute cooldown
			cooldown = 0

#undef DOCKING_PORT_HIGHLIGHT

/turf/proc/copyTurf(turf/T)
	if(T.type != type)
		var/obj/O
		if(underlays.len)	//we have underlays, which implies some sort of transparency, so we want to a snapshot of the previous turf as an underlay
			O = new()
			O.underlays.Add(T)
		T = new type(T)
		if(underlays.len)
			T.underlays = O.underlays
	if(T.icon_state != icon_state)
		T.icon_state = icon_state
	if(T.lighting_overlay != lighting_overlay)
		T.lighting_overlay = lighting_overlay
		T.contents += lighting_overlay
		T.update_overlay()
	if(T.icon != icon)
		T.icon = icon
	if(T.color != color)
		T.color = color
	if(T.dir != dir)
		T.dir = dir
	return T


code:
/datum/surgery/appendectomy
	name = "appendectomy"
	steps = list(/datum/surgery_step/incise, /datum/surgery_step/clamp_bleeders, /datum/surgery_step/retract_skin, /datum/surgery_step/incise, /datum/surgery_step/extract_appendix, /datum/surgery_step/close)
	species = list(/mob/living/carbon/human, /mob/living/carbon/monkey)
	location = "groin"
	requires_organic_chest = 1

//extract appendix
/datum/surgery_step/extract_appendix
	accept_hand = 1
	time = 64
	var/obj/item/organ/appendix/A = null

/datum/surgery_step/extract_appendix/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
	A = locate() in target.internal_organs
	if(A)
		user.visible_message("[user] begins to extract [target]'s appendix.")
	else
		user.visible_message("[user] looks for an appendix in [target].")

/datum/surgery_step/extract_appendix/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
	if(A)
		user.visible_message("[user] successfully removes [target]'s appendix!")
		A.loc = get_turf(target)
		target.internal_organs -= A
		for(var/datum/disease/appendicitis in target.viruses)
			appendicitis.cure()
	else
		user.visible_message("[user] can't find an appendix in [target]!")
	return 1


code:
/datum/surgery/brain_removal
	name = "brain removal"
	steps = list(/datum/surgery_step/incise, /datum/surgery_step/clamp_bleeders, /datum/surgery_step/retract_skin, /datum/surgery_step/saw, /datum/surgery_step/extract_brain)
	species = list(/mob/living/carbon/human, /mob/living/carbon/monkey)
	location = "head"

//extract brain
/datum/surgery_step/extract_brain
	implements = list(/obj/item/weapon/hemostat = 100, /obj/item/weapon/crowbar = 55)
	time = 64
	var/obj/item/organ/brain/B = null

/datum/surgery_step/extract_brain/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
	B = target.getorgan(/obj/item/organ/brain)
	if(B)
		user.visible_message("[user] begins to extract [target]'s brain.")
	else
		user.visible_message("[user] looks for a brain in [target].")

/datum/surgery_step/extract_brain/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
	if(B)
		user.visible_message("[user] successfully removes [target]'s brain!")
		B.loc = get_turf(target)
		B.transfer_identity(target)
		target.internal_organs -= B
		if(ishuman(target))
			var/mob/living/carbon/human/H = target
			H.update_hair(0)
			H.apply_damage(25,"brute","head")
		add_logs(user, target, "debrained", addition="INTENT: [uppertext(user.a_intent)]")
	else
		user.visible_message("[user] can't find a brain in [target]!")
	return 1

//brain removal for aliens
/datum/surgery/brain_removal/alien
	name = "alien brain removal"
	steps = list(/datum/surgery_step/saw, /datum/surgery_step/incise, /datum/surgery_step/retract_skin, /datum/surgery_step/saw, /datum/surgery_step/extract_brain)
	species = list(/mob/living/carbon/alien/humanoid)


code:
/datum/surgery/cavity_implant
	name = "cavity implant"
	steps = list(/datum/surgery_step/incise, /datum/surgery_step/clamp_bleeders, /datum/surgery_step/retract_skin, /datum/surgery_step/incise, /datum/surgery_step/handle_cavity, /datum/surgery_step/close)
	species = list(/mob/living/carbon/human, /mob/living/carbon/monkey)
	location = "chest"
	requires_organic_chest = 1

//handle cavity
/datum/surgery_step/handle_cavity
	accept_hand = 1
	accept_any_item = 1
	time = 32
	var/obj/item/IC = null

/datum/surgery_step/handle_cavity/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
	for(var/obj/item/I in target.internal_organs)
		if(!istype(I, /obj/item/organ))
			IC = I
			break
	if(tool)
		user.visible_message("[user] begins to insert [tool] into [target]'s [target_zone].")
	else
		user.visible_message("[user] checks for items in [target]'s [target_zone].")

/datum/surgery_step/handle_cavity/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
	if(tool)
		if(IC || tool.w_class > 3 || (tool.flags & NODROP) || tool.GetTypeInAllContents(/obj/item/weapon/disk/nuclear) || istype(tool, /obj/item/weapon/disk/nuclear) || istype(tool, /obj/item/organ))
			user.visible_message("[user] can't seem to fit [tool] in [target]'s [target_zone].")
			return 0
		else
			user.visible_message("[user] stuffs [tool] into [target]'s [target_zone]!")
			user.drop_item()
			target.internal_organs += tool
			tool.loc = target
			return 1
	else
		if(IC)
			user.visible_message("[user] pulls [IC] out of [target]'s [target_zone]!")
			user.put_in_hands(IC)
			target.internal_organs -= IC
			return 1
		else
			user.visible_message("[user] doesn't find anything in [target]'s [target_zone].")
			return 0