# -*- coding: utf-8 -*-
#
# SletScript interpreter
LAST_MODIFIED="Nov. 17th 2025 19:10 UTC+8";VERSION="6.0.3.8 alpha"
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#                                                                           #
#  Copyright 2025 islptng                                                   #
#                                                                           #
#  Licensed under the Apache License, Version 2.0 (the "License");          #
#  you may not use this file except in compliance with the License.         #
#  You may obtain a copy of the License at                                  #
#                                                                           #
#      http://www.apache.org/licenses/LICENSE-2.0                           #
#                                                                           #
#  Unless required by applicable law or agreed to in writing, software      #
#  distributed under the License is distributed on an "AS IS" BASIS,        #
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
#  See the License for the specific language governing permissions and      #
#  limitations under the License.                                           #
#                                                                           #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

from sys import argv
from math import gcd
from random import randint, sample
import os
from time import sleep, time
from copy import deepcopy

# Tool for file path.
def normalizePath(p, WorkingAddress=""):
	p = p.replace("\\","/")
	if not (p.startswith("/") or (len(p) > 1 and p[1] == ":" and p[2] == "/")):
		p = WorkingAddress + p
	parts = p.split("/")
	stack = []
	for part in parts:
		if part == "..":
			if stack and stack[-1] != "..": stack.pop()
			else: stack.append("..")
		elif part and part != ".":
			stack.append(part)
	return "/".join(stack)

class SletScriptError(Exception):
	def __init__(self, type, msg, addr=None):
		self.type = type
		self.msg = msg
		self.addr = addr

class Null:
	def __init__(self): pass
	__repr__ = __str__ = lambda self: ";"
	__eq__ = lambda self,other: isinstance(other, Null)
	__ne__ = lambda self,other: not isinstance(other, Null)
	__hash__ = lambda self: hash(None)
NULL = Null()

class Number:
	def simplify(self):
		if self.denominator == 0:
			if self.numerator != 0: self.numerator = 1
		elif self.denominator != 1:
			gcdn = gcd(self.numerator, self.denominator)
			self.numerator //= gcdn
			self.denominator //= gcdn
	def __init__(self, val, denominator = 1):
		if isinstance(val, str):
			if val == "nan": val = "0/0"
			if val == "infinity": val = "1/0"
			val = val.split("/")
			self.numerator = int(val[0])
			try: self.denominator = int(val[1])
			except: self.denominator = 1
		else:
			self.numerator = val
			self.denominator = denominator
		self.simplify()
	def __str__(self):
		if self.denominator == 1:
			return str(self.numerator)
		return str(self.numerator) + "\\" + str(self.denominator)
	def __add__(self, other):
		if not isinstance(other, Number):
			other = Number(other)
		denominator = self.denominator * other.denominator
		numerator = self.numerator * other.denominator + other.numerator * self.denominator
		res = Number(numerator, denominator)
		res.simplify()
		return res
	def __sub__(self, other):
		if not isinstance(other, Number):
			other = Number(other)
		denominator = self.denominator * other.denominator
		numerator = self.numerator * other.denominator - other.numerator * self.denominator
		res = Number(numerator, denominator)
		res.simplify()
		return res
	def __neg__(self):
		return Number(0) - self
	def __mul__(self, other):
		if not isinstance(other, Number):
			other = Number(other)
		numerator = self.numerator * other.numerator
		denominator = self.denominator * other.denominator
		res = Number(numerator, denominator)
		res.simplify()
		return res
	def __truediv__(self, other):
		if not isinstance(other, Number):
			other = Number(other)
		numerator = self.numerator * other.denominator
		denominator = self.denominator * other.numerator
		res = Number(numerator, denominator)
		res.simplify()
		return res
	def __mod__(self, other):
		if not isinstance(other, Number):
			other = Number(other)
		intn = int(self / other)
		modn = self - other * intn
		return modn
	def __floordiv__(self, other):
		if not isinstance(other, Number):
			other = Number(other)
		return Number(int(self / other))
	def __eq__(self, other):
		if not isinstance(other, Number):
			try: other = Number(other)
			except: return False
		return self.numerator == other.numerator and self.denominator == other.denominator
	def __ne__(self, other):
		if not isinstance(other, Number):
			try: other = Number(other)
			except: return True
		return self.numerator != other.numerator or self.denominator != other.denominator
	def __lt__(self, other):
		if not isinstance(other, Number):
			other = Number(other)
		return self.numerator * other.denominator < other.numerator * self.denominator
	def __gt__(self, other):
		if not isinstance(other, Number):
			other = Number(other)
		return self.numerator * other.denominator > other.numerator * self.denominator
	def __le__(self, other):
		if not isinstance(other, Number):
			other = Number(other)
		return self.numerator * other.denominator <= other.numerator * self.denominator
	def __ge__(self, other):
		if not isinstance(other, Number):
			other = Number(other)
		return self.numerator * other.denominator >= other.numerator * self.denominator
	def __int__(self):
		if self.denominator == 0: raise SletScriptError("ValueError","Required a finite number, not infinity/NaN.")
		return self.numerator // self.denominator
	def ceiling(self): return self.numerator // self.denominator + (self.numerator % self.denominator != 0)
	def __bool__(self):
		if self == Number(1,0): return True  # infinity
		if self == Number(0,0): return False # NaN
		return self > 0
	def __float__(self): return float(self.numerator) / float(self.denominator)
	def __list__(self):
		res = []
		i = Number(0)
		while i < self:
			i += Number(1,self.denominator)
			res.append(i)
		return res
	def __hash__(self): return hash((self.numerator,self.denominator))
	__repr__ = __str__

def decExpand(n, digits):
	try:
		# print integer part
		res = str(int(n))
		# print decimal part
		digits = int(digits)
		if digits > 0: res += "."
		for i in range(digits):
			n *= 10
			res += str(int(n % 10))
		return List(res)
	except: raise SletScriptError("ValueError","Cannot expand a number.")

class Char:
	def __init__(self, val):
		if isinstance(val, str):
			if len(val) > 1:
				raise Exception("Char can only be initialized with a single character.")
			self.val = val
		else: self.val = chr(int(val))
	__eq__ = lambda self,other: isinstance(other, Char) and self.val == other.val
	__ne__ = lambda self,other: not isinstance(other, Char) or self.val != other.val
	__repr__ = lambda self: "@" + self.val if self.val not in "\n\t()" else {"\n":"\\@n","\t":"\\@t","(":"\\@l",")":"\\@r"}[self.val]
	__str__ = lambda self: self.val

class List:
	# Sadly, due to Python's restrictions, we can't directly use Python's list. Python's list are for storing tokens.
	def __init__(self,s=[]):
		if isinstance(s,str):
			r = []
			for i in list(s):
				r.append(Char(i))
			self.val = list(r)
		else: self.val = list(s)
	def __len__(self): return len(self.val)
	def __getitem__(self, index):
		try:
			if isinstance(index,slice): return List(self.val[index])
			if isinstance(index,Pair):
				if isinstance(index.former,Null) and isinstance(index.latter,Null): return self
				if isinstance(index.former,Null) and isinstance(index.latter,Number):
					return List(self.val[:int(index.latter)])
				if isinstance(index.latter,Null) and isinstance(index.former,Number):
					return List(self.val[int(index.former):])
				if isinstance(index.former,Number) and isinstance(index.latter,Number):
					return List(self.val[int(index.former):int(index.latter)])
				raise SletScriptError("ValueError","Invalid slice.")
			return self.val[int(index)]
		except IndexError: raise SletScriptError("ValueError","Index out of range.")
	def __setitem__(self, index, value): self.val[int(index)] = value
	def __eq__(self, other):
		if not isinstance(other, List):
			return False
		return self.val == other.val
	__int__ = __len__
	__Number__ = lambda self: Number(len(self))
	def __str__(self):
		for i in self.val:
			if not isinstance(i, Char): return "[%s]" % " ".join([repr(_) for _ in self.val])
		return "".join([_.val for _ in self.val])
	def __repr__(self):
		leftparenmiss = 0
		rightparenmiss = 0
		for i in self.val:
			if not isinstance(i, Char): return "[%s]" % " ".join([repr(_) for _ in self.val])
			if i.val == "(": rightparenmiss += 1
			if i.val == ")":
				if rightparenmiss > 0: rightparenmiss -= 1
				else: leftparenmiss += 1
		if leftparenmiss == 0 and rightparenmiss == 0: return "("+"".join([_.val for _ in self.val])+")"
		if leftparenmiss == 0:
			return "+("+"".join([_.val for _ in self.val])+")"*rightparenmiss+")^;-"+str(rightparenmiss)
		if rightparenmiss == 0:
			return "+("+"("*leftparenmiss+"".join([_.val for _ in self.val])+(")^%d;" % leftparenmiss)
		return "+("+"("*leftparenmiss+"".join([_.val for _ in self.val])+")"*rightparenmiss+(")^%d-%d"%(leftparenmiss,rightparenmiss))
	def __add__(self, other):
		if not isinstance(other, List):
			other = List(other)
		return List(self.val + other.val)
	def __mul__(self, other):
		if not isinstance(other, Number):
			other = Number(other)
			if other < 0: raise SletScriptError("ValueError","Cannot repeat a list with a negative number.")
			if other.denominator != 1: raise SletScriptError("ValueError","Cannot repeat a list with a non-integer number.")
		return List(self.val * int(other))
	def reverse(self): return List(self.val[::-1])
	def index(self, item):
		res = []
		for index,i in enumerate(self.val):
			if i == item:
				res.append(Number(index))
		return List(res)
	def findSubstr(self, substr):
		if not isinstance(substr, List):
			substr = List(substr)
		res = []
		for i in range(len(self) - len(substr) + 1):
			if self.val[i:i+len(substr)] == substr.val:
				res.append(Number(i))
		return List(res)
	def findSubstrNoRepeat(self, substr):
		if not isinstance(substr, List):
			substr = List(substr)
		res = []
		i = 0
		while i < len(self) - len(substr) + 1:
			if self.val[i:i+len(substr)] == substr.val:
				res.append(i)
				i += len(substr)
			else: i += 1
		return res
	def replace(self, index, length, new):
		if not isinstance(index, Number):
			index = Number(index)
		if not isinstance(length, Number):
			length = Number(length)
		if not isinstance(new, List):
			new = List(new)
		return List(self.val[:int(index)] + new.val + self.val[int(index)+int(length):])
	def findreplace(self, substr, new, maxReplace):
		if not isinstance(substr, List):
			substr = List(substr)
		if not isinstance(new, List):
			new = List(new)
		res = self
		index = res.findSubstrNoRepeat(substr)
		i = 0
		while i < len(index) and (isinstance(maxReplace,Null) or i < int(maxReplace)):
			res = res.replace(index[i], Number(len(substr)), new)
			i += 1
		return res
	def split(self, sep, maxSplit=None):
		if not isinstance(sep, List):
			sep = List(sep)
		res = []
		index = self.findSubstrNoRepeat(sep) + [len(self)]
		indexFrom = 0
		for i in index:
			res.append(List(self.val[indexFrom:i]))
			indexFrom = i + len(sep)
		return List(res)
	def randomChoice(self, num):
		if not isinstance(num, Number):
			num = Number(num)
		if num.numerator > len(self):
			raise SletScriptError("ValueError","Number of choices exceeds the length of the list.")
		return List(sample(self.val, num.numerator))
	def join(sep, content): # Respect to Python!
		objs = content.val
		res = []
		for i,j in enumerate(objs):
			res += j.val
			if i != len(objs) - 1: res += sep.val
		return List(res)
	__hash__ = lambda self: hash(tuple(self.val))
def sletZip(arg):
	try:
		if isinstance(arg, List):
			if len(arg) == 0: return List()
			res = [[]] * len(arg[0])
			for i in arg.val:
				for j in range(len(i)):
					res[j]=res[j]+[i.val[j]]
			return List([List(x) for x in res])
		elif isinstance(arg, Pair):
			if len(arg.former) != len(arg.latter): raise SletScriptError("ValueError","Different lengths when zipping.")
			res = []
			for i in range(len(arg.former)):
				res.append(Pair(arg.former.val[i],arg.latter.val[i]))
			return List(res)
	except IndexError: raise SletScriptError("ValueError","Different lengths when zipping.")

class Pair:
	def __init__(self, former, latter):
		self.former = former
		self.latter = latter
	def __str__(self):
		return "^%s %s" % (repr(self.former), repr(self.latter))
	__eq__ = lambda self,other: isinstance(other, Pair) and self.former == other.former and self.latter == other.latter
	__ne__ = lambda self,other: not isinstance(other, Pair) or self.former != other.former or self.latter != other.latter
	__repr__ = __str__
	__hash__ = lambda self: hash((self.former,self.latter))

class Function:
	def __init__(self, name, body):
		self.name = name
		self.body = body
	def __str__(self):
		return "\\" + self.name
	__repr__ = __str__

class UserDefinedTypeObject:
	def __init__(self, typeName, value):
		self.type = typeName
		self.value = value
	def __str__(self):
		return '"%s %s' % (self.type, self.value)
	__repr__ = __str__
	__hash__ = lambda self: hash((self.type,self.value))
	__eq__ = lambda self,other: isinstance(other, UserDefinedTypeObject) and self.type == other.type and self.value == other.value
	__ne__ = lambda self,other: not isinstance(other, UserDefinedTypeObject) or self.type != other.type or self.value != other.value

def _analyzeTypeString(s):
	s = s[2:]
	stk = []
	currentNum = ""
	inQuote = False
	quoteCont = ""
	for i in s[::-1]:
		if i == "\\":
			if inQuote: stk.append(("\\",quoteCont))
			quoteCont = ""
			inQuote = not inQuote
		elif inQuote: quoteCont = i + quoteCont # since we reads backwards
		elif i in "0123456789": currentNum = i + currentNum
		else:
			if currentNum:
				if i != "f": raise SletScriptError("TypeStringError","Excepted f before a number, %s found." % i)
				stk.append(("f",int(currentNum)))
				currentNum = ""
			elif i == "p":
				try:
					former = stk.pop()
					latter = stk.pop()
				except IndexError: raise SletScriptError("TypeStringError","excepted 2 subTypeStrings after p.")
				stk.append(("p",former,latter))
			elif i == "L":
				try: t = stk.pop()
				except IndexError: raise SletScriptError("TypeStringError","excepted 1 subTypeString after L.")
				stk.append(("L",t))
			else: stk.append(i)
	if len(stk) != 1: raise SletScriptError("TypeStringError","excepted 1 type in a TypeString, got %d." % len(stk))
	return stk[0]
def _TypeCheck(val, analyzed):
	if isinstance(analyzed, str):
		if analyzed == "x": return True # Wildcard
		if analyzed == "n" and isinstance(val,Number): return True
		if analyzed == "l" and isinstance(val,List): return True
		if analyzed == "c" and isinstance(val,Char): return True
		if analyzed == "t" and isinstance(val,Type): return True
		if analyzed == "q" and isinstance(val,Null): return True
		return False
	else: # Tuple ("f",n) / ("p",t1,t2) / ("L",t) / ("\\", s)
		if analyzed[0] == "f" and isinstance(val,Function) and len(val.body[0][0]) == analyzed[1]: return True
		if analyzed[0] == "p" and isinstance(val,Pair) and _TypeCheck(val.former, analyzed[1]) and _TypeCheck(val.latter, analyzed[2]): return True
		if analyzed[0] == "L" and isinstance(val,List):
			for i in val.val:
				if not _TypeCheck(i, analyzed[1]): return False
			return True
		if analyzed[0] == "\\" and isinstance(val,UserDefinedTypeObject) and val.type == analyzed[1]: return True
		return False
class Type:
	def __init__(self, value):
		self.val = value
	__str__ = __repr__ = lambda self: self.val
	__eq__ = lambda self,other: isinstance(other,Type) and self.val == other.val
	__ne__ = lambda self,other: not isinstance(other,Type) or self.val != other.val
	def __call__(self, obj): # A type check.
		if self.val[:2] != "\\\\": return isinstance(obj, UserDefinedTypeObject) and obj.type == self.val
		analyzed = _analyzeTypeString(self.val)
		return _TypeCheck(obj, analyzed)
	__hash__ = lambda self: hash(self.val)

def Sletify(t):
	if t is None: return NULL
	if isinstance(t,int): return Number(t)
	if isinstance(t,list):
		res = []
		for i in t:
			res.append(Sletify(i))
		return List(res)
	if isinstance(t,str): return List(t)
	if isinstance(t,bool): return [Number(-1),Number(1)][t]
	if isinstance(t,tuple):
		if len(t) != 2: raise SyntaxError(f"SletScript don't have {len(t)}-tuples, only 2-tuples(pairs) are supported.")
		return Pair(Sletify(t[0]), Sletify(t[1]))
	return t
def Slet2Bool(t):
	if isinstance(t,Null): return False
	if isinstance(t,Number): return t > 0
	if isinstance(t,List): return t != List([])
	return True

def SletTypeof(t, withPrefix=True):
	if isinstance(t,Null): return r"\\q" if withPrefix else "q"
	if isinstance(t,Number): return r"\\n" if withPrefix else "n"
	if isinstance(t,Char): return r"\\c" if withPrefix else "c"
	if isinstance(t,Type): return r"\\t" if withPrefix else "t"
	if isinstance(t,Pair):
		if withPrefix: return r"\\p"+SletTypeof(t.former, False)+SletTypeof(t.latter, False)
		else: return "p"+SletTypeof(t.former, False)+SletTypeof(t.latter, False)
	if isinstance(t,List): return r"\\l" if withPrefix else "l"
	if isinstance(t,Function):
		if withPrefix: return r"\\f"+str(len(t.body[0][0]))
		else: return "f"+str(len(t.body[0][0]))
	if isinstance(t,UserDefinedTypeObject):
		if withPrefix: return t.type
		else: return f"\\{t.type}\\"
	raise SletScriptError("TypeError",f"Can't get type of {t}.")
def BasicTypeConvert(t, destTypeT):
	try: destType = _analyzeTypeString(destTypeT.val)
	except AttributeError: destType = destTypeT
	if destType == "x": return t
	if destType == "q": return NULL
	if destType == "t": return Type(SletTypeof(t))
	if isinstance(t,Number):
		if destType == "n": return t
		if destType == "c": return Char(chr(int(t)))
		if isinstance(destType,tuple) and destType[0] == "p": return Pair(BasicTypeConvert(Number(t.numerator),destType[1]),BasicTypeConvert(Number(t.denominator),destType[2]))
		if destType == "l" or isinstance(destType,tuple) and destType[0] == "L":
			destType1 = destType[1] if isinstance(destType,tuple) else "x"
			step = Number(1, t.denominator)
			res = []
			cur = Number(0)
			while cur <= t:
				res.append(BasicTypeConvert(cur, destType1))
				cur += step
			return List(res)
	elif isinstance(t,List):
		if destType == "n": return Number(len(t))
		if destType == "l": return t
		if isinstance(destType,tuple) and destType[0] == "L":
			destType1 = destType[1]
			return List([BasicTypeConvert(i, destType1) for i in t])
		if isinstance(destType,tuple) and destType[0] == "p": return Pair(BasicTypeConvert(t[0],destType[1]),BasicTypeConvert(t[-1],destType[2]))
	elif isinstance(t,Char):
		if destType == "n": return Number(ord(t.val))
		if destType == "c": return t
		if isinstance(destType,tuple) and destType[0] == "p": return Pair(BasicTypeConvert(t,destType[1]),BasicTypeConvert(t,destType[2]))
	elif isinstance(t,Pair):
		if destType == "n" and isinstance(t.former,Number) and isinstance(t.latter,Number): return Number(t.former/t.latter)
		if destType == "l": return List([t.former,t.latter])
		if isinstance(destType,tuple) and destType[0] == "L":
			destType1 = destType[1]
			return List([BasicTypeConvert(t.former, destType1), BasicTypeConvert(t.latter, destType1)])
		if isinstance(destType,tuple) and destType[0] == "p": return Pair(BasicTypeConvert(t.former,destType[1]),BasicTypeConvert(t.latter,destType[2]))
	elif isinstance(t,Null):
		if destType == "n": return Number("0/0")
		if destType == "c": return Char("\0")
		if isinstance(destType,tuple) and destType[0] == "p": return Pair(BasicTypeConvert(NULL,destType[1]),BasicTypeConvert(NULL,destType[2]))
		if destType == "l" or isinstance(destType,tuple) and destType[0] == "L": return List([])
	raise SletScriptError("TypeError",f"Can't convert {t} to {repr(destTypeT)}.")

def SletRange(start, end, step):
	i = start
	res = []
	if step > 0:
		while i < end:
			res.append(i)
			i += step
	elif step.denominator == 0 or step.numerator == 0: raise SletScriptError("ValueError","Step cannot be zero or Infinity/NaN.")
	else:
		while i > end:
			res.append(i)
			i += step
	return List(res)

def tokenize(s, fileName = "", indexBegin = 0): # Lexer
	# Remove comment.
	t = []
	DepthString = 0
	DepthComment = 0
	i = 0
	while i < len(s):
		if not DepthString and i + 1 < len(s):
			if s[i:i+2] == "[}":
				i += 1
				DepthComment += 1
			elif s[i:i+2] == "{]":
				i += 2
				DepthComment -= 1
				continue # avoid adding extra ]
		if not DepthComment:
			if s[i] == "@" and not DepthString:
				t.append((i+indexBegin,s[i]))
				try: t.append((i+1+indexBegin,s[i+1]))
				except IndexError: raise SletScriptError("SyntaxError","Unexpected @ in the end of code.",(fileName, i+indexBegin))
				i += 2
				continue
			elif s[i] == "(":
				DepthString += 1
			elif s[i] == ")":
				DepthString -= 1
			t.append((i+indexBegin,s[i])) # to keep track of the position of tokens
		i += 1
	if DepthString > 0: raise SletScriptError("SyntaxError","Unclosed string literal.",(fileName, i+indexBegin))
	elif DepthString < 0: raise SletScriptError("SyntaxError","Unmatched ).",(fileName, i+indexBegin))
	# Tokenize.
	tok = []
	currentTok = ""
	currentTokPos = None
	resTypeString = None
	i = 0
	while i < len(t):
		isChar = False
		if t[i][1] == "(":
			if DepthString == 0 and currentTok:
				tok.append((currentTokPos,currentTok))
				currentTok = ""
				currentTokPos = i
			DepthString += 1
		if DepthString:
			if not currentTok: currentTokPos = t[i][0]
			currentTok += t[i][1]
		elif resTypeString:
			if t[i][1] == "\\":
				currentTok += t[i][1]
				resTypeString *= -1
				if resTypeString > 0: resTypeString -= 1
			else:
				currentTok += t[i][1]
				if resTypeString > 0:
					resTypeString -= 1
					if t[i][1] == "p": resTypeString += 2
					elif t[i][1] == "f":
						flag = False
						while i < len(t) - 1 and t[i+1][1].isdigit():
							flag = True
							currentTok += t[i+1][1]
							i += 1
						if not flag:
							raise SletScriptError("SyntaxError","Expected number after 'f' in TypeString.",(fileName, t[i][0]))
					elif t[i][1] == "L": resTypeString += 1
					elif t[i][1] not in "nlctxq":
						raise SletScriptError("SyntaxError",f"Unknown character '{t[i][1]}' in TypeString.",(fileName, t[i][0]))
			if resTypeString == 0:
				tok.append((currentTokPos,currentTok))
				currentTok = ""
				currentTokPos = None
				resTypeString = None
		elif t[i][1] in " \n\t\r":
			if currentTok:
				tok.append((currentTokPos,currentTok))
				currentTok = ""
				currentTokPos = None
		elif t[i][1].isalnum() or t[i][1] == '_' or ord(t[i][1]) >= 128:
			if currentTok.isdigit() and not t[i][1].isdigit():
				# Implicit Space: 12a isn't a valid variable name. We treat it as "12 a".
				tok.append((currentTokPos,currentTok))
				currentTok = ""
				currentTokPos = None
			if not currentTok: currentTokPos = t[i][0]
			currentTok += t[i][1]
		elif t[i][1] == '@': # Character
			if currentTok:
				tok.append((currentTokPos,currentTok))
				currentTok = ""
				currentTokPos = None
			tok.append((t[i][0],'@'+t[i+1][1]))
			i += 1
			isChar = True
		elif i + 1 < len(t) and t[i][1] == "\\" and t[i+1][1] == "\\": # TypeString
			if currentTok:
				tok.append((currentTokPos,currentTok))
			currentTokPos = t[i][0]
			currentTok = "\\\\"
			i += 1
			resTypeString = 1
		else:
			if currentTok:
				tok.append((currentTokPos,currentTok))
				currentTok = ""
				currentTokPos = None
			tok.append((t[i][0],t[i][1]))
		if t[i][1] == ")" and not isChar:
			if DepthString == 0:
				raise SletScriptError("SyntaxError","Unmatched ).",(fileName, t[i][0]))
			DepthString -= 1
			if DepthString == 0:
				tok.append((currentTokPos,currentTok))
				currentTok = ""
				currentTokPos = None
		i += 1
	if currentTok:
		tok.append((currentTokPos,currentTok))
	if resTypeString: raise SletScriptError("SyntaxError","Unfinished TypeString.",(fileName, currentTokPos))
	# Merge \x and "x into a single token.
	t = tok
	tok = []
	i = 0
	while i < len(t):
		if t[i][1] == '"':
			try: tok.append((t[i][0],t[i][1] + t[i+1][1]))
			except IndexError: raise SletScriptError("SyntaxError","Unexpected \" at the end of code.",(fileName, t[i][0]))
			i += 1
		else:
			tok.append(t[i])
		i += 1
	t = tok
	tok = []
	i = 0
	while i < len(t):
		if t[i][1] == "\\":
			try:
				if t[i+1][1].isdigit():
					try:
						if isinstance(tok[-1][1],Number):
							tok[-1] = (tok[-1][0],tok[-1][1]/Number(t[i+1][1]))
					except IndexError: raise SletScriptError("SyntaxError","Fraction without numerator.",(fileName, t[i][0]))
				elif t[i+1][1] == "\\": raise SletScriptError("SyntaxError","Wrong syntax for TypeString.",(fileName, t[i][0]))
				else: tok.append((t[i][0],t[i][1] + t[i+1][1]))
			except IndexError: raise SletScriptError("SyntaxError","Unexpected \\ at the end of code.",(fileName, t[i][0]))
			i += 1
		elif t[i][1].isdigit():
			tok.append((t[i][0],Number(t[i][1])))
		else: tok.append(t[i])
		i += 1
	return tok

def preprocess(t, importer, fileName="", definedStrings=[], returnWithNewDS=False, indexBegin=0):
	# Execute system command (in the form of "\\\x\").
	parentdirectory = normalizePath(fileName+"/../")+"/"
	tok = tokenize(t, fileName, indexBegin)
	t = [((fileName,i[0]),i[1]) for i in tok][::-1]
	tok = []
	ifstack = [1] # 0 - condition not met, 1 - executing, 2 - condition already met
	while t:
		i = t.pop()
		if isinstance(i[1],str) and i[1].startswith("\\\\\\"):
			content = i[1][3:-1]
			if content.startswith("+"): #include <x>
				if ifstack[-1] == 1:
					filepath = normalizePath(content[1:], parentdirectory)
					filecontent = importer(filepath)
					if not isinstance(filecontent,str): raise SletScriptError("FileNotFoundError",f"File {filepath} not found.",i[0])
					pres, definedStrings = preprocess(filecontent, importer, filepath, definedStrings, returnWithNewDS=True)
					t = t + pres[::-1]
			elif content.startswith("#"): #define string
				if ifstack[-1] == 1:
					if content[1:] not in definedStrings: definedStrings.append(content[1:])
			elif content.startswith("$"): #undef string
				if ifstack[-1] == 1:
					if content[1:] in definedStrings: definedStrings.remove(content[1:])
			elif content.startswith("!"): #ifdef string
				if ifstack[-1] == 1:
					if content[1:] in definedStrings: ifstack.append(1)
					else: ifstack.append(0)
				else: ifstack.append(2)
			elif content.startswith("?"): #ifndef string
				if ifstack[-1] == 1:
					if content[1:] not in definedStrings: ifstack.append(1)
					else: ifstack.append(0)
				else: ifstack.append(2)
			elif content.startswith(".!"): #elifdef string
				if ifstack[-1] == 0:
					if content[1:] in definedStrings: ifstack[-1] = 1
				else: ifstack[-1] = 2
			elif content.startswith(".?"): #elifndef string
				if ifstack[-1] == 0:
					if content[1:] not in definedStrings: ifstack[-1] = 1
				else: ifstack[-1] = 2
			elif content.startswith("."): #else
				if ifstack[-1] != 2:
					ifstack[-1] += 1
			elif content.startswith("@"): #endif
				ifstack.pop()
				if not ifstack: raise SletScriptError("SyntaxError","Extra \\\\\\@\\.",i[0])
			else: raise SletScriptError("SyntaxError","Unknown preprocessor instruction.",i[0])
		else: 
			if ifstack[-1] == 1: tok.append(i)
	if returnWithNewDS: return tok, definedStrings
	return tok

def flat1(l): # Flatten a list by 1 level. For example, flat1([[2,[3]],[4]]) -> [2,[3],4]; flat1([{"a":1,"b":2},{"c":3}]) -> {"a":1,"b":2,"c":3}
	if not l: return l
	first = l[0]
	if isinstance(first, list):
		return [item for sublist in l for item in sublist]
	elif isinstance(first, dict):
		result = {}
		for d in l: result |= d
		return result
def ainflat1(l,a): # Check if a in in flat1(l) without actually flatting the list.
	if not l: return False
	for i in l:
		if a in i: return True
	return False

def analyze(s, importer, fileName="", History_numargs=None, History_definedVariables=None, History_definedTypes=None, indexBegin=0):
	tok = preprocess(s, importer, fileName, indexBegin=indexBegin) + [((fileName,None),"}")]
	numargs = History_numargs or [{'!':None,'\\#':2,'$':2,'#':2,'+':2,'-':1,'*':2,'/':1,'%':2,'|':2,'=':2,'?':3,'~':3,'.':1,'`':2,',':0,':':0,'<':1,'>':2,'&':2,';':0,'^':2,"'":2,
'">':1,'"<':1,'"*':2,'"%':3,'":':4,'"+':3,'"~':3,'"!':5,'",':1,'".':2,'"$':1,'"/':1,'"|':2}]
	definedVariables = History_definedVariables or [[]]
	definedTypes = History_definedTypes or [[]]
	# -1: code block, -2: list,
	# -4: function definition (at name), -5: function definition (at tokens)
	# -6: function definition (at TypeStrings), -7: function definition (at expression)
	depthStack = [-1]
	stack = [((fileName,None),"{")]
	funcnumstkargs = []
	funcnumargs = {}
	backup = numargs, definedVariables, definedTypes
	for index, i in enumerate(tok):
		if len(depthStack) == 0: raise SletScriptError(f"SyntaxError","Extra bracket.",i[0])
		definedTokens = definedVariables + definedTypes
		flatdeftok = flat1(definedTokens)
		flatargs = flat1(numargs)
		isFunctionName = False
		if depthStack[-1] <= -3:
			dx = depthStack[-1]
			if dx == -3:
				depthStack[-1] = -4
				funcnumstkargs.append([i[1],0])
				isFunctionName = True
			elif dx == -5:
				funcnumstkargs[-1][1] += 1
				depthStack[-1] = -6
			elif dx in [-4,-6]:
				depthStack[-1] = -7
				flag = True
				if isinstance(i[1],str):
					if i[1] not in flatdeftok:
						if i[1] not in flatargs:
							if i[1] != funcnumstkargs[-1][0]:
								if i[1] not in ["[","{"]:
									if i[1][0] not in ["@","(","\\"]:
										depthStack[-1] = -5
										flag = False
										flag2 = True
										try: tok[index+1][1]
										except IndexError: raise SletScriptError("SyntaxError","Wrong syntax for function definition.",i[0])
										if isinstance(tok[index+1][1],str):
											if tok[index+1][1].startswith("\\\\"):
												try: pts = _analyzeTypeString(tok[index+1][1])
												except SletScriptError as e: raise SletScriptError(e.type,e.msg,tok[index+1][0])
												if isinstance(pts,tuple):
													if pts[0] == "f": # Function argument
														funcnumargs[i[1]] = pts[1]
														flatargs = flat1(numargs)
														flag2 = False
										if flag2: # Normal argument
											definedVariables[-1].append(i[1])
											definedTokens = definedVariables + definedTypes
											flatdeftok = flat1(definedTokens)
				if flag:
					o = funcnumstkargs.pop()
					try:
						if o[1] != flatargs[o[0]]: raise SletScriptError("SyntaxError","Mismatched number of arguments when overloading.",i[0])
					except KeyError: pass # Not overloading
					numargs[-1][o[0]] = o[1]
					numargs.append(funcnumargs)
					flatargs = flat1(numargs)
		if i[1] == "!": # Function definition
			if tok[index+1][1] in flatdeftok:
				raise SletScriptError("SyntaxError","Function cannot share a name with an existing variable or type.",i[0])
			if depthStack[-1] >= 0 or depthStack[-1] == -7: depthStack[-1] -= 1
			depthStack += [-3]
			definedVariables.append([])
			definedTypes.append([])
			funcnumargs = {}
			stack.append(i)
		elif i[1] in ["\\#","$"]:
			if depthStack[-1] >= 0 or depthStack[-1] == -7: depthStack[-1] -= 1
			depthStack.append(2)
			stack.append(i)
			if not isinstance(tok[index+1][1],str):
				if tok[index+1][1] in flatargs:
					raise SletScriptError("SyntaxError","Expected name after '\\#' or '$'.",tok[index+1][0])
			if i[1] == "\\#": definedVariables[-1].append(tok[index+1][1])
			else:
				definedTypes[-1].append(tok[index+1][1])
				numargs[-1]['"'+tok[index+1][1]] = 1
			definedTokens = definedVariables + definedTypes
			flatdeftok = flat1(definedTokens)
		elif i[1] == "{":
			if depthStack[-1] >= 0 or depthStack[-1] == -7: depthStack[-1] -= 1
			depthStack.append(-1)
			stack.append(i)
			numargs.append({})
			definedVariables.append([])
			definedTypes.append([])
		elif i[1] == "[":
			if depthStack[-1] >= 0 or depthStack[-1] == -7: depthStack[-1] -= 1
			depthStack.append(-2)
			stack.append(i)
		elif i[1] in flatargs and (depthStack[-1] != -6 or i[1] not in flat1(definedTypes)) and not isFunctionName:
			if depthStack[-1] >= 0 or depthStack[-1] == -7: depthStack[-1] -= 1
			depthStack.append(flatargs[i[1]])
			stack.append(i)
		elif i[1] == "}":
			if depthStack[-1] != -1: raise SletScriptError("SyntaxError","Missing argument before '}'.",i[0])
			depthStack.pop()
			pstk = []
			while stack[-1][1] != "{" or len(stack[-1]) == 3:
				pstk.append(stack.pop())
			stack[-1] = (stack[-1][0],stack[-1][1],pstk[::-1])
			numargs.pop()
			definedVariables.pop()
			definedTypes.pop()
			definedTokens = definedVariables + definedTypes
		elif i[1] == "]":
			if depthStack[-1] != -2: raise SletScriptError("SyntaxError","Missing argument before ']'.",i[0])
			depthStack.pop()
			pstk = []
			while stack[-1][1] != "[" or len(stack[-1]) == 3:
				pstk.append(stack.pop())
			stack[-1] = (stack[-1][0],stack[-1][1],pstk[::-1])
		else:
			stack.append(i)
			if depthStack[-1] >= 0 or depthStack[-1] == -7: depthStack[-1] -= 1
		while len(depthStack) and (depthStack[-1] in [0,-8]):
			flag = depthStack[-1] == -8
			depthStack.pop()
			if flag:
				numargs.pop()
				flatargs = flat1(numargs)
				definedVariables.pop()
				definedTypes.pop()
				definedTokens = definedVariables + definedTypes
				flatdeftok = flat1(definedTokens)
			pstk = []
			while len(stack[-1]) == 3 or (stack[-1][1] not in flatargs or (len(stack) > 2 and len(stack[-2]) != 3 and stack[-2][1] == "!")):
				backup = deepcopy(numargs), deepcopy(definedVariables), deepcopy(definedTypes)
				pstk.append(stack.pop())
			stack[-1] = (stack[-1][0],stack[-1][1],pstk[::-1])
	if len(stack) != 1: raise SletScriptError("SyntaxError","Unclosed block.",(fileName, None))
	if History_numargs is not None: return (stack[0], *backup)
	return stack[0]

def printWithIndent(tree,indent=0,indentWith="│ "):
	if isinstance(tree,tuple):
		if len(tree) == 2: print(indentWith*indent + repr(tree)); return
		print(indentWith*indent + f"({repr(tree[0])}, {repr(tree[1])},")
		for i in tree[2:]:
			printWithIndent(i,indent+1)
		print(indentWith*indent + ")")
	elif isinstance(tree,list):
		print(indentWith*indent + "[")
		for i in tree:
			printWithIndent(i,indent+1)
		print(indentWith*indent + "]")
	else: print(indentWith*indent + repr(tree))

def getFile(filename):
	# If filename is a URL then it should be a GET request
	if filename.startswith("http://") or filename.startswith("https://"):
		import requests
		try: return requests.get(filename).text
		except requests.exceptions.RequestException: return None
	# otherwise, read file
	try:
		with open(filename,"r") as f: return f.read()
	except FileNotFoundError: return None
def putFile(filename,content):
	# If filename is a URL then it should be a POST request
	if filename.startswith("http://") or filename.startswith("https://"):
		import requests
		try:
			requests.post(filename,data=content)
			return True
		except requests.exceptions.RequestException: return False
	# otherwise, write file
	try:
		with open(filename,"w") as f: f.write(content)
		return True
	except IOError: return False
def delFile(filename):
	try:
		os.remove(filename)
		return True
	except FileNotFoundError: return False
def systemp(cmd):
	try:
		os.system(cmd)
		return True
	except: return False
class SletScriptEnvironment:
	def __init__(self, stdin=lambda: input()+"\n", stdout=lambda x: print(str(x), end=""), stderr=print, filein=getFile, fileout=putFile,
	                   filedel=delFile, syscmd=systemp, history=[False,False,False], histtext=""):
		def raiseError(x,y): raise SletScriptError(x,y)
		def evalStrCode(code):
			try: res = self.exec(str(code),mode=2)
			except SletScriptError as e: raise SletScriptError(e.type,e.msg)
			return res
		self.functions = [{
	'+':[
		([Type(r"\\n"),Type(r"\\n")], lambda a,b: a+b), # addition
		([Type(r"\\l"),Type(r"\\n")], lambda a,b: a[b]), # get item
		([Type(r"\\l"),Type(r"\\l")], lambda a,b: a+b), # concatenation
		([Type(r"\\l"),Type(r"\\pnn")], lambda a,b: a[b]), # slice
		([Type(r"\\l"),Type(r"\\pqn")], lambda a,b: a[b]), # slice
		([Type(r"\\l"),Type(r"\\pnq")], lambda a,b: a[b]), # slice
		([Type(r"\\l"),Type(r"\\pqq")], lambda a,b: a[b])  # slice
	],
	'-':[
		([Type(r"\\n")], lambda a: -a), # negation
		([Type(r"\\l")], lambda a: a.reverse()), # reverse
		([Type(r"\\pxx")], lambda a: Pair(a.latter,a.former)) # swap
	],
	'*':[
		([Type(r"\\n"),Type(r"\\n")], lambda a,b: a*b), # multiplication
		([Type(r"\\l"),Type(r"\\n")], lambda a,b: a*b), # repetition
		([Type(r"\\x"),Type(r"\\t")], lambda a,b: BasicTypeConvert(a,b)), # type conversion
		([Type(r"\\Ll"),Type(r"\\l")], lambda a,b: b.join(a)) # join
	],
	'/':[
		([Type(r"\\n")], lambda a: Number(1)/a), # reciprocal
		([Type(r"\\pll")], lambda a: sletZip(a)), # zip
		([Type(r"\\Ll")], lambda a: sletZip(a)) # zip
	],
	'%':[
		([Type(r"\\n"),Type(r"\\n")], lambda a,b: a%b), # modulo
		([Type(r"\\l"),Type(r"\\x")], lambda a,b: a.index(b)), # indexes of occurrences
		([Type(r"\\f1"),Type(r"\\x")], ["<argument1>","x"], (None,"<argument1>",[(None,"x")])) # apply
	],
	'=':[
		([Type(r"\\x"),Type(r"\\x")], lambda a,b: Number(1 if a==b else -1)), # equality
	],
	'.':[
		([Type(r"\\x")], lambda a: (self.stdout(a),a)[1]) # print
	],
	'`':[
		([Type(r"\\n"),Type(r"\\n")], lambda a,b: decExpand(a,b)), # decimal expansion
		([Type(r"\\n"),Type(r"\\q")], lambda a,b: Number(int(a))), # floor
		([Type(r"\\x"),Type(r"\\t")], lambda a,b: Number(1 if b(a) else -1)) # type check
	],
	'>':[
		([Type(r"\\n"),Type(r"\\n")], lambda a,b: Number(1 if a>b else -1 if a<b else 0)) # compare (1 if a>b, 0 if a=b, -1 if a<b)
	],
	'&':[
		([Type(r"\\n"),Type(r"\\n")], lambda a,b: Number(randint(a.ceiling(),int(b))) if a.ceiling() < int(b) else Number(a.ceiling())), # random int between 2 numbers (l inclusive, r exclusive)
		([Type(r"\\l"),Type(r"\\n")], lambda a,b: a.randomChoice(b)), # random choice
		([Type(r"\\l"),Type(r"\\q")], lambda a,b: a.randomChoice(len(a.val))) # shuffle
	],
	'">':[ # convert to string
		([Type(r"\\x")], lambda a: List(repr(a)))
	],
	'"<':[
		([Type(r"\\Lc")], lambda a: evalStrCode(a)) # eval
	],
	'"*':[
		([Type(r"\\l"),Type(r"\\l")], lambda a,b: a.findSubstr(b)), # find substring
		([Type(r"\\n"),Type(r"\\n")], lambda a,b: a//b) # floor division
	],
	'"%':[
		([Type(r"\\l"),Type(r"\\l"),Type(r"\\n")], lambda a,b,c: a.split(b,c)), # split
		([Type(r"\\l"),Type(r"\\l"),Type(r"\\q")], lambda a,b,c: a.split(b,c)) # split (all)
	],
	'":':[
		([Type(r"\\l"),Type(r"\\n"),Type(r"\\n"),Type(r"\\l")], lambda a,b,c,d: a.replace(b,c,d)), # c++'s replace
		([Type(r"\\l"),Type(r"\\l"),Type(r"\\l"),Type(r"\\n")], lambda a,b,c,d: a.findreplace(b,c,d)), # python's replace
		([Type(r"\\l"),Type(r"\\l"),Type(r"\\l"),Type(r"\\q")], lambda a,b,c,d: a.findreplace(b,c,d)) # python's replace (all)
	],
	'"+':[
		([Type(r"\\n"),Type(r"\\n"),Type(r"\\n")], lambda a,b,c: SletRange(a,b,c)) # range
	],
	'",':[
		([Type(r"\\Lc")], lambda a: self.filein(a)) # GET
	],
	'".':[
		([Type(r"\\Lc"),Type(r"\\Lc")], lambda a,b: self.fileout(a,b)) # POST
	],
	'"$':[
		([Type(r"\\Lc")], lambda a: self.filedel(a)) # Delete file
	],
	'"/':[
		([Type(r"\\Lc")], lambda a: self.syscmd(a)), # Execute system command
		([Type(r"\\n")], lambda a: (a,sleep(float(a)))[0]), # Sleep
		([Type(r"\\q")], lambda a: Number(int(time() * 1000000),1000000)) # Get time
	],
	'"|':[
		([Type(r"\\Lc"),Type(r"\\Lc")], lambda a,b: raiseError(a,b)) # raise error
	]
}]
		self.InputBuffer = ""
		self.variables = [{}]
		self.types = [{}]
		def getchar0():
			if self.InputBuffer == "":
				try:
					self.InputBuffer = stdin()
					if not isinstance(self.InputBuffer, str):
						self.InputBuffer = ""
						raise SletScriptError("InputError","Met EOF in input.")
				except:
					self.InputBuffer = ""
					raise SletScriptError("InputError","Met EOF in input.")
			res = self.InputBuffer[0]
			self.InputBuffer = self.InputBuffer[1:]
			return res
		def getchar1(): return Char(getchar0())
		self.stdin = getchar1
		def getnumber1():
			res = ""
			neg = None
			metNum = False
			while True:
				char = getchar0()
				if char == "-" and not metNum:
					if neg is None: neg = True
					else: return NULL
					metNum = True
				elif char.isdigit():
					res += char
					metNum = True
				elif metNum:
					self.InputBuffer = char + self.InputBuffer
					if neg: res = "-" + res
					return Number(int(res))
				else: pass
		self.numberin = getnumber1
		self.stdout = stdout
		def getFile1(name):
			res = filein(str(name))
			if isinstance(res, str): return List(res)
			elif res is None: return NULL
			raise SyntaxError("Wrong return value for custom filein.")
		self.filein = getFile1
		def putFile1(name, val):
			res = fileout(str(name),str(val))
			if isinstance(res, bool): return Number(1 if res else -1) 
			else: raise SyntaxError("Wrong type for custom fileout.")
		self.fileout = putFile1
		def delFile1(name):
			res = filedel(str(name))
			if isinstance(res, bool): return Number(1 if res else -1) 
			else: raise SyntaxError("Wrong type for custom filedel.")
		self.filedel = delFile1
		self.stderr = stderr
		def systemp1(name):
			res = syscmd(name)
			if isinstance(res, bool): return Number(1 if res else -1)
			elif isinstance(res, str): return List(res)
			else: raise SyntaxError("Wrong type for custom syscmd.")
		self.syscmd = systemp1
		self.history = history
		self.histtext = histtext
		self.importer = filein
	def defVar(self, name, val):
		if not isinstance(name,str): raise SletScriptError("SyntaxError","Variable name must be a token, not an expression.")
		if name in self.variables[-1]: raise SletScriptError("NameError","Variable '"+name+"' already defined.")
		self.variables[-1][name] = val
	def getVar(self, name):
		for i in self.variables[::-1]:
			if name in i: return i[name]
		raise SletScriptError("NameError","Variable '"+name+"' not defined.")
	def setVar(self, name, val):
		if not isinstance(name,str): raise SletScriptError("SyntaxError","Variable name must be a token, not an expression.")
		for i in self.variables[::-1]:
			if name in i:
				i[name] = val
				return
		raise SletScriptError("NameError","Variable '"+name+"' not defined.")
	def defType(self, name, type):
		if not isinstance(name,str): raise SletScriptError("SyntaxError","Type name must be a token, not an expression.")
		if name in self.types[-1]: raise SletScriptError("NameError","Type '"+name+"' already defined.")
		self.types[-1][name] = type
		self.defFunc('"'+name,[type.val],["x"],(None,"x"))
	def getType(self, name):
		for i in self.types[::-1]:
			if name in i: return i[name]
		raise SletScriptError("NameError","Type '"+name+"' not defined.")
	def defFunc(self, name, types, args=None, funcBody=None):
		if not isinstance(name,str): raise SletScriptError("SyntaxError","Function name must be a token, not an expression.")
		if name[0] == "\\": raise SletScriptError("SyntaxError","Function name cannot start with '\\'.")
		if isinstance(types,Function):
			self.functions[-1][name] = types.body
			return None
		if name not in self.functions[-1]: self.functions[-1][name] = []
		if name[0] == "\"":
			toType = name[1:]
			try:
				self.getType(toType)
				self.functions[-1][name].insert(0,(types,args,funcBody,toType))
			except: self.functions[-1][name].insert(0,(types,args,funcBody))
		else: self.functions[-1][name].insert(0,(types,args,funcBody))
	def getFunc(self, name):
		for i in self.functions[::-1]:
			if name in i: return i[name]
		raise SletScriptError("NameError","Function '"+name+"' not defined.")
	def callFunc(self, name, args):
		for f in self.functions[::-1]:
			if name in f:
				for i in f[name]:
					# check if the arguments are of the correct type
					# first matched type will be the result function
					types = i[0]
					flag = True
					for j,k in zip(types,args):
						if not j(k):
							flag = False
							break
					if flag: # Correct function!
						if len(i) == 2: return i[1](*args) # Built with Python
						else: # Built with SletScript
							self.push()
							for type, name, val in zip(i[0],i[1],args):
								try:
									typenest = _analyzeTypeString(type.val)
									if isinstance(typenest,tuple) and typenest[0] == "f" and isinstance(val,Function):
										self.defFunc(name,val)
									else: self.defVar(name,val)
								except: self.defVar(name,val)
							res = self.eval(i[2])
							self.pop()
							if len(i) == 4: return UserDefinedTypeObject(i[3],res)
							return res
		raise SletScriptError("TypeError","Function '"+name+"' called with arguments of the wrong type.")
	def push(self):
		self.variables.append({})
		self.functions.append({})
		self.types.append({})
	def pop(self):
		self.variables.pop()
		self.functions.pop()
		self.types.pop()
	def eval(self,tree):
		if isinstance(tree,tuple):
			if tree[1] == "{": # code block
				self.push()
				res = NULL
				for i in tree[2]:
					res = self.eval(i)
				self.pop()
				return res
			elif tree[1] == "[": # list
				res = []
				for i in tree[2]:
					res.append(self.eval(i))
				return List(res)
			elif tree[1] == "!": # function definition
				name, *argstype, expr = tree[2]
				name = name[1]
				args = [i[1] for i in argstype[::2]]
				type = [Type(i[1]) for i in argstype[1::2]]
				try: self.defFunc(name, type, args, expr)
				except SletScriptError as e: raise SletScriptError(e.type,e.msg,e.addr or tree[2][0][0])
				return Function(name,self.getFunc(name))
			elif isinstance(tree[1],str) and tree[1].startswith("("): # string
				return List(tree[1][1:-1])
			elif isinstance(tree[1],str) and tree[1].startswith('@'): # character
				return Char(tree[1][1])
			elif isinstance(tree[1],str) and tree[1].startswith("\\\\"): # TypeString
				return Type(tree[1])
			elif tree[1] == "\\#": # define variable
				res = self.eval(tree[2][1])
				if len(tree[2][0]) != 2: raise SletScriptError("SyntaxError","Variable name must be a token, not an expression.",tree[2][0][0])
				try: self.defVar(tree[2][0][1],res)
				except SletScriptError as e: raise SletScriptError(e.type,e.msg,e.addr or tree[2][0][0])
				return res
			elif tree[1] == "$": # typedef
				if len(tree[2][0]) != 2: raise SletScriptError("SyntaxError","Type name must be a token, not an expression.",tree[2][0][0])
				res = self.eval(tree[2][1])
				try: self.defType(tree[2][0][1],Type(res))
				except SletScriptError as e: raise SletScriptError(e.type,e.msg,e.addr or tree[2][0][0])
				return res
			elif tree[1] == "#": # assignment
				if len(tree[2][0]) != 2: raise SletScriptError("SyntaxError","Variable name must be a token, not an expression.",tree[2][0][0])
				res = self.eval(tree[2][1])
				try: self.setVar(tree[2][0][1],res)
				except SletScriptError as e: raise SletScriptError(e.type,e.msg,e.addr or tree[2][0][0])
				return res
			elif tree[1] == "|": # try-except
				try: res = self.eval(tree[2][0])
				except SletScriptError as e:
					res = self.eval(tree[2][1])
				return res
			elif tree[1] == "?": # if-else
				res = self.eval(tree[2][0])
				if Slet2Bool(res): return self.eval(tree[2][1])
				else: return self.eval(tree[2][2])
			elif tree[1] == "~": # for-in
				self.push()
				iterator = self.eval(tree[2][0])
				if not isinstance(iterator,List):
					raise SletScriptError("TypeError","For('~') loop iterator must be a list.",tree[0])
				try: self.defVar(tree[2][1][1],NULL)
				except SletScriptError as e: raise SletScriptError(e.type,e.msg,e.addr or tree[2][1][0])
				res = []
				for i in iterator.val:
					self.setVar(tree[2][1][1],i)
					res.append(self.eval(tree[2][2]))
				self.pop()
				return List(res)
			elif tree[1] == ",":
				try: return self.stdin() # Getchar
				except SletScriptError as e: raise SletScriptError(e.type,e.msg,e.addr or tree[0])
			elif tree[1] == ":":
				try: return self.numberin() # Getnumber
				except SletScriptError as e: raise SletScriptError(e.type,e.msg,e.addr or tree[0])
			elif tree[1] == "<":
				arg = self.eval(tree[2][0])
				if isinstance(arg,Number): return Number(arg.numerator)
				elif isinstance(arg,Char): return Number(ord(arg.val))
				elif isinstance(arg,List): return Number(len(arg.val))
				elif isinstance(arg,Pair): return arg.former
				elif isinstance(arg,UserDefinedTypeObject): return arg.value
				elif isinstance(arg,Type) and arg.val in flat1(self.types): return self.getType(arg.val)
				else: return NULL
			elif tree[1] == ";": return NULL # constant null
			elif tree[1] == "^": # make pair
				return Pair(self.eval(tree[2][0]),self.eval(tree[2][1]))
			elif tree[1] == "'": # while loop
				cond = self.eval(tree[2][0])
				res = []
				while Slet2Bool(cond):
					res.append(self.eval(tree[2][1]))
					cond = self.eval(tree[2][0])
				return List(res)
			elif tree[1] == "\"~": # filter
				self.push()
				iterator = self.eval(tree[2][0])
				if not isinstance(iterator,List):
					raise SletScriptError("TypeError","Filter('\"~') iterator must be a list.",tree[0])
				try: self.defVar(tree[2][1][1],NULL)
				except SletScriptError as e: raise SletScriptError(e.type,e.msg,e.addr or tree[2][1][0])
				res = []
				for i in iterator.val:
					self.setVar(tree[2][1][1],i)
					if Slet2Bool(self.eval(tree[2][2])): res.append(i)
				self.pop()
				return List(res)
			elif tree[1] == "\"!": # iteration
				self.push()
				try: self.defVar(tree[2][0][1],self.eval(tree[2][1]))
				except SletScriptError as e: raise SletScriptError(e.type,e.msg,e.addr or tree[2][0][0])
				iterator = self.eval(tree[2][2])
				if not isinstance(iterator,List):
					raise SletScriptError("TypeError","Iteration('\"!') iterator must be a list.",tree[0])
				self.defVar(tree[2][3][1],NULL)
				for i in iterator.val:
					self.setVar(tree[2][3][1],i)
					self.setVar(tree[2][0][1],self.eval(tree[2][4]))
				res = self.getVar(tree[2][0][1])
				self.pop()
				return res
			elif ainflat1(self.functions, a=tree[1]):
				try:
					res = self.callFunc(tree[1], [self.eval(i) for i in tree[2]])
				except SletScriptError as e: raise SletScriptError(e.type,e.msg,e.addr or tree[0])
				except IndexError: raise SletScriptError("ArgumentError","Function called with no argument.",tree[0])
				return res
			elif ainflat1(self.variables, a=tree[1]):
				try: res = self.getVar(tree[1])
				except SletScriptError as e: raise SletScriptError(e.type,e.msg,e.addr or tree[0])
				return res
			elif ainflat1(self.types, a=tree[1]):
				return Type(tree[1])
			elif isinstance(tree[1],str) and tree[1].startswith("\\"): # Escaped token
				unescaped = tree[1][1:]
				if unescaped == "@n": return Char("\n")
				elif unescaped == "@t": return Char("\t")
				elif unescaped == "@l": return Char("(")
				elif unescaped == "@r": return Char(")")
				if unescaped in flat1(self.functions): return Function(unescaped,self.getFunc(unescaped))
				else: raise SletScriptError("NameError","Unknown escape sequence '\\"+unescaped+"'.",tree[0])
			elif isinstance(tree[1],str): raise SletScriptError("NameError","Unknown keyword '"+tree[1]+"'.",tree[0])
			else: return tree[1]
		else: return tree
	def exec(self,code,mode=0,fileName="<string>.slet"): # 0: normal, 1: REPL, 2: No error catch
		backup = deepcopy(self.variables),deepcopy(self.types),deepcopy(self.history),deepcopy(self.functions),self.histtext
		try:
			newHistory = [False,False,False]
			if self.histtext: self.histtext += "\n"
			htlen = len(self.histtext)
			self.histtext += code
			tree, newHistory[0], newHistory[1], newHistory[2] = analyze(code, self.importer, fileName, *deepcopy(self.history), indexBegin=htlen)
			res = None
			for i in tree[2]:
				res = self.eval(i)
			if newHistory != [[],[],[]]: self.history = newHistory
			return res
		except RecursionError as e:
			if mode == 2: raise e
			self.variables,self.types,self.history,self.functions,self.histtext = backup
			self.stderr("#======\nMax recursion depth reached.")
		except SletScriptError as e:
			if mode == 2: raise e
			ErrorMessage = "#======\n"
			name, msg, addr = e.type, e.msg, e.addr
			ErrFileName, addr = addr
			if fileName != ErrFileName:
				if "<" not in ErrFileName:
					ErrorMessage += f"In file-> {ErrFileName}):\n"
					code = self.importer(ErrFileName)
			else:
				if name == "SyntaxError" and msg in ["Missing argument before '}'.","Unclosed string literal.","Unclosed block."] and mode == 1 and addr is None: raise Exception("Next")
				code = self.histtext
			if addr is None: addr = len(code)
			# get line and column number
			ln = code.count("\n",0,addr)
			col = addr - code.rfind("\n",0,addr)
			lspltdc = code.split("\n")
			ErrorMessage += f"ERROR (at line {ln+1}, column {col}):\n"
			try:
				if ln > 0:ErrorMessage += (""," ")[len(str(ln+1))!=len(str(ln))]+str(ln)+" | "+lspltdc[ln-1]+"\n"
			except IndexError: pass
			ErrorMessage += str(ln+1)+" | "+lspltdc[ln]+"\n"
			ErrorMessage += " "*len(str(ln+1))+" |"+"-"*col+"^\n"
			try: ErrorMessage += str(ln+2)+" | "+lspltdc[ln+1]+"\n"
			except IndexError: pass
			ErrorMessage += f"{name}: {msg}\n"
			self.variables,self.types,self.history,self.functions,self.histtext = backup
			self.stderr(ErrorMessage)

if __name__ == "__main__":
	if len(argv) > 1:
		filepath = normalizePath(argv[1])
		if not filepath.endswith(".slet"):
			print("Usage: sletscript_interpreter.py filename.slet\n or without argument to enter REPL mode.")
			exit(1)
		try: code = open(filepath).read()
		except FileNotFoundError:
			print(f"File not found: {filepath}")
			exit(1)
		env = SletScriptEnvironment()
		res = env.exec(code, mode=0, fileName=filepath)
		if res is not None and res != NULL: print(f"\n#======\n= {repr(res)}")
	else: # enter REPL
		print("""SletScript %s by islptng | %s
Type "\\\\\\?\\" for help, "\\\\\\*\\" to reset the environment.
Press Ctrl+Z then Return to quit.""" % (VERSION, LAST_MODIFIED))
		env = SletScriptEnvironment()
		while True:
			try:
				code = input("        --> ")
				if code == "": continue
				if code == "\\\\\\*\\":
					env = SletScriptEnvironment()
					print("Environment reset.")
				else:
					try: res = env.exec(code,mode=1,fileName=normalizePath(__file__+"/..")+"/<REPL>.slet")
					except Exception as e:
						if str(e) == "Next":
							while True:
								code += "\n" + input("          | ")
								if code == "": break
								try: res = env.exec(code,mode=1,fileName=normalizePath(__file__+"/..")+"/<REPL>.slet")
								except Exception as e:
									if str(e) == "Next":
										continue
									else: raise e
								break
						else: raise e
					if res != None: print(f"\n    <= {repr(res)}\n")
			except KeyboardInterrupt:
				print("\nInterrupted.\n")
			except EOFError:
				print("\nGoodbye.")
				break