//-----------------------------------------------------------------------------
// AScript re module
//-----------------------------------------------------------------------------
#include <ascript.h>
#include <oniguruma.h>

AScript_BeginModule(re)

AScript_DeclarePrivSymbol(re);
AScript_DeclarePrivSymbol(string);
AScript_DeclarePrivSymbol(multiline);

static regex_t *CreateRegEx(Signal sig, const char *pattern, const SymbolSet &attrs);
static Value DoMatch(Environment &env, Signal sig, regex_t *pRegEx,
							const char *str, int pos, int posEnd);
static Value DoSubWithString(Environment &env, Signal sig, regex_t *pRegEx,
							const char *replace, const char *str, int cnt);
static Value DoSubWithFunc(Environment &env, Signal sig, regex_t *pRegEx,
							const Function *pFunc, const char *str, int cnt);
static void SetError_OnigurumaError(Signal sig, int rtn);
static void SetError_FailInOniguruma(Signal sig);

//-----------------------------------------------------------------------------
// Object_Match class declaration
//-----------------------------------------------------------------------------
AScript_DeclarePrivClass(Match);

class Object_Match : public Object {
public:
	class Group {
	private:
		int _posBegin, _posEnd;
	public:
		inline Group(int posBegin, int posEnd) :
						_posBegin(posBegin), _posEnd(posEnd) {}
		inline Group(const Group &group) :
						_posBegin(group._posBegin), _posEnd(group._posEnd) {}
		inline void operator=(const Group &group) {
			_posBegin = group._posBegin, _posEnd = group._posEnd;
		}
		inline int GetPosBegin() const { return _posBegin; }
		inline int GetPosEnd() const { return _posEnd; }
		inline int GetLength() const { return _posEnd - _posBegin; }
	};
	typedef std::vector<Group> GroupList;
	typedef std::map<String, size_t> GroupNameDict;
public:
	AScript_DeclareObjectAccessor(Match)
private:
	String _str;
	GroupList _groupList;
	GroupNameDict _groupNameDict;
public:
	inline Object_Match(Environment &env) : Object(AScript_PrivClass(Match)) {}
	inline Object_Match(const Object_Match &obj) : Object(obj),
							_str(obj._str), _groupList(obj._groupList) {}
	virtual ~Object_Match();
	virtual Object *Clone() const;
	virtual String ToString(Signal sig, bool exprFlag);
	bool SetMatchInfo(const char *str, regex_t *pRegEx,
								const OnigRegion *pRegion, int posOffset);
	inline String GetGroupString(const Group &group) const {
		return Middle(_str.c_str(), group.GetPosBegin(), group.GetLength());
	}
	const Group *GetGroup(Signal sig, const Value &index) const;
	const GroupList &GetGroupList() const { return _groupList; }
private:
	int ForeachNameCallback(const String &name, int nGroups,
											int *idxGroupTbl, regex_t *pRegEx);
	static int ForeachNameCallbackStub(
				const UChar *nameRaw, const UChar *nameRawEnd,
				int nGroups, int *idxGroupTbl, regex_t *pRegEx, void *pArg);
};

//-----------------------------------------------------------------------------
// Object_Pattern class declaration
//-----------------------------------------------------------------------------
AScript_DeclarePrivClass(Pattern);

class Object_Pattern : public Object {
private:
	String _pattern;
	regex_t *_pRegEx;
public:
	AScript_DeclareObjectAccessor(Pattern)
public:
	inline Object_Pattern(Environment &env) :
						Object(AScript_PrivClass(Pattern)), _pRegEx(NULL) {}
	virtual ~Object_Pattern();
	virtual Object *Clone() const;
	virtual String ToString(Signal sig, bool exprFlag);
	inline bool SetPattern(Signal sig, const char *pattern, const SymbolSet &attrs) {
		_pattern = pattern;
		_pRegEx = CreateRegEx(sig, pattern, attrs);
		return _pRegEx != NULL;
	}
	inline regex_t *GetRegEx() { return _pRegEx; }
};

//-----------------------------------------------------------------------------
// IteratorSplit class declaration
//-----------------------------------------------------------------------------
class IteratorSplit : public Iterator {
private:
	Object_Pattern *_pObjPattern;
	Object_String *_pObjStr;
	int _cnt;
	int _idx;
	int _len;
	bool _doneFlag;
	OnigRegion *_pRegion;
public:
	IteratorSplit(Object_Pattern *pObjPattern, Object_String *pObjStr, int cntMax);
	virtual ~IteratorSplit();
	virtual bool DoNext(Environment &env, Signal sig, Value &value);
	virtual String ToString(Signal sig) const;
};

//-----------------------------------------------------------------------------
// IteratorScan class declaration
//-----------------------------------------------------------------------------
class IteratorScan : public Iterator {
private:
	Object_Pattern *_pObjPattern;
	Object_String *_pObjStr;
	int _idx, _idxEnd;
	int _len;
	OnigRegion *_pRegion;
public:
	IteratorScan(Object_Pattern *pObjPattern, Object_String *pObjStr, int pos, int posEnd);
	virtual ~IteratorScan();
	virtual bool DoNext(Environment &env, Signal sig, Value &value);
	virtual String ToString(Signal sig) const;
};

//-----------------------------------------------------------------------------
// IteratorSplit
//-----------------------------------------------------------------------------
IteratorSplit::IteratorSplit(Object_Pattern *pObjPattern, Object_String *pObjStr, int cntMax) :
		Iterator(false), _pObjPattern(pObjPattern), _pObjStr(pObjStr),
		_cnt(cntMax), _idx(0),
		_len(static_cast<int>(pObjStr->GetStringSTL().size())), _doneFlag(false),
		_pRegion(::onig_region_new())
{
}

IteratorSplit::~IteratorSplit()
{
	Object::Delete(_pObjPattern);
	Object::Delete(_pObjStr);
	::onig_region_free(_pRegion, 1); // 1:free self, 0:free contents only
}

bool IteratorSplit::DoNext(Environment &env, Signal sig, Value &value)
{
	//Environment &env = *_pObjStr;
	const char *str = _pObjStr->GetString();
	if (_doneFlag) return false;
	if (_cnt == 0) {
		value = Value(env, str + _idx);
		_idx = _len;
		return true;
	} else if (_idx >= _len) {
		value = Value(env, "");
		_doneFlag = true;
		return true;
	}
	int rtn = ::onig_search(_pObjPattern->GetRegEx(),
					reinterpret_cast<const OnigUChar *>(str),
					reinterpret_cast<const OnigUChar *>(str + _len),
					reinterpret_cast<const OnigUChar *>(str + _idx),
					reinterpret_cast<const OnigUChar *>(str + _len),
					_pRegion, ONIG_OPTION_NONE);
	if (rtn >= 0) {
		if (rtn < _idx || _pRegion->num_regs == 0 || _pRegion->end[0] < _idx) {
			SetError_FailInOniguruma(sig);
			return false;
		}
		if (_pRegion->end[0] == _idx) {
			value = Value(env, str + _idx);
			_doneFlag = true;
			return true;
		}
		value = Value(env, String(str + _idx, rtn - _idx).c_str());
		_idx = _pRegion->end[0];
	} else if (rtn == ONIG_MISMATCH) {
		value = Value(env, str + _idx);
		_idx = _len;
		_doneFlag = true;
	} else { // error
		SetError_OnigurumaError(sig, rtn);
		return false;
	}
	if (_cnt > 0) _cnt--;
	return true;
}

String IteratorSplit::ToString(Signal sig) const
{
	return String("<iterator:re.split>");
}

//-----------------------------------------------------------------------------
// IteratorScan
//-----------------------------------------------------------------------------
IteratorScan::IteratorScan(Object_Pattern *pObjPattern, Object_String *pObjStr, int pos, int posEnd) :
		Iterator(false), _pObjPattern(pObjPattern), _pObjStr(pObjStr),
		_len(static_cast<int>(pObjStr->GetStringSTL().size())), _pRegion(::onig_region_new())
{
	_idx = static_cast<int>(CalcCharOffset(pObjStr->GetString(), pos));
	_idxEnd = (posEnd < 0)? _len : static_cast<int>(CalcCharOffset(pObjStr->GetString(), posEnd));
}

IteratorScan::~IteratorScan()
{
	Object::Delete(_pObjPattern);
	Object::Delete(_pObjStr);
	::onig_region_free(_pRegion, 1); // 1:free self, 0:free contents only
}

bool IteratorScan::DoNext(Environment &env, Signal sig, Value &value)
{
	if (_idx >= _idxEnd) return false;
	//Environment &env = *_pObjStr;
	const char *str = _pObjStr->GetString();
	int rtn = ::onig_search(_pObjPattern->GetRegEx(),
					reinterpret_cast<const OnigUChar *>(str),
					reinterpret_cast<const OnigUChar *>(str + _len),
					reinterpret_cast<const OnigUChar *>(str + _idx),
					reinterpret_cast<const OnigUChar *>(str + _idxEnd),
					_pRegion, ONIG_OPTION_NONE);
	if (rtn >= 0) {
		if (rtn < _idx || _pRegion->num_regs == 0 || _pRegion->end[0] < _idx) {
			SetError_FailInOniguruma(sig);
			return false;
		}
		if (_pRegion->end[0] == _idx) {
			return false;
		}
		Object_Match *pObj = new Object_Match(env);
		if (!pObj->SetMatchInfo(str, _pObjPattern->GetRegEx(), _pRegion, 0)) {
			SetError_FailInOniguruma(sig);
			delete pObj;
			return false;
		}
		value = Value(pObj);
		_idx = _pRegion->end[0];
	} else if (rtn == ONIG_MISMATCH) {
		value = Value(env, str + _idx);
		_idx = _idxEnd;
		return false;
	} else { // error
		SetError_OnigurumaError(sig, rtn);
		return false;
	}
	return true;
}

String IteratorScan::ToString(Signal sig) const
{
	return String("<iterator:re.scan>");
}

//-----------------------------------------------------------------------------
// Object_Pattern
//-----------------------------------------------------------------------------
Object_Pattern::~Object_Pattern()
{
	if (_pRegEx != NULL) {
		::onig_free(_pRegEx);
	}
}

Object *Object_Pattern::Clone() const
{
	return NULL;
}

String Object_Pattern::ToString(Signal sig, bool exprFlag)
{
	String rtn;
	rtn += "<pattern:'";
	rtn += _pattern;
	rtn += "'>";
	return rtn;
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_Pattern
//-----------------------------------------------------------------------------
// m = re.regex#match(str:string, pos:number => 0):map
AScript_DeclareMethod(Pattern, match)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "str", VTYPE_String);
	DeclareArg(env, "pos", VTYPE_Number, OCCUR_Once, false, false, new Expr_Value(0));
	DeclareArg(env, "endpos", VTYPE_Number, OCCUR_ZeroOrOnce);
	SetHelp(
	"Applies a pattern matching to a string and returns a match object.");
}

AScript_ImplementMethod(Pattern, match)
{
	Object_Pattern *pObj = Object_Pattern::GetSelfObj(args);
	return DoMatch(env, sig, pObj->GetRegEx(), args.GetString(0),
			args.GetInt(1), args.IsNumber(2)? args.GetInt(2) : -1);
}

// str = re.regex#sub(replace, str:string, count?:number):map
AScript_DeclareMethod(Pattern, sub)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "replace", VTYPE_Any);
	DeclareArg(env, "str", VTYPE_String);
	DeclareArg(env, "count", VTYPE_Number, OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(Pattern, sub)
{
	Object_Pattern *pObj = Object_Pattern::GetSelfObj(args);
	int cnt = args.IsNumber(2)? static_cast<int>(args.GetNumber(2)) : -1;
	if (args.IsString(0)) {
		return DoSubWithString(env, sig, pObj->GetRegEx(),
						args.GetString(0), args.GetString(1), cnt);
	} else if (args.IsFunction(0)) {
		return DoSubWithFunc(env, sig, pObj->GetRegEx(),
						args.GetFunction(0), args.GetString(1), cnt);
	}
	SetError_ArgumentTypeByIndex(sig, 0, args.GetValue(0));
	return Value::Null;
}

// re.regex#split(str:string, count?:number):map {block?}
AScript_DeclareMethod(Pattern, split)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "str", VTYPE_String);
	DeclareArg(env, "count", VTYPE_Number, OCCUR_ZeroOrOnce);
	DeclareBlock(OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(Pattern, split)
{
	Object_Pattern *pSelf = Object_Pattern::GetSelfObj(args);
	Object_Pattern *pObjPattern = Object_Pattern::Reference(pSelf);
	Object_String *pObjStr = Object_String::Reference(args.GetStringObj(0));
	int cntMax = args.IsNumber(1)? static_cast<int>(args.GetNumber(1)) : -1;
	return ReturnIterator(env, sig, args,
							new IteratorSplit(pObjPattern, pObjStr, cntMax));
}

// re.regex#scan(str:string, pos:number => 0, endpos?:number):map {block?}
AScript_DeclareMethod(Pattern, scan)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "str", VTYPE_String);
	DeclareArg(env, "pos", VTYPE_Number, OCCUR_Once, false, false, new Expr_Value(0));
	DeclareArg(env, "endpos", VTYPE_Number, OCCUR_ZeroOrOnce);
	DeclareBlock(OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(Pattern, scan)
{
	Object_Pattern *pSelf = Object_Pattern::GetSelfObj(args);
	Object_Pattern *pObjPattern = Object_Pattern::Reference(pSelf);
	Object_String *pObjStr = Object_String::Reference(args.GetStringObj(0));
	int posEnd = args.IsNumber(2)? args.GetInt(2) : -1;
	return ReturnIterator(env, sig, args,
				new IteratorScan(pObjPattern, pObjStr, args.GetInt(1), posEnd));
}

// implementation of class Pattern
AScript_ImplementPrivClassWithCast(Pattern)
{
	AScript_AssignMethod(Pattern, match);
	AScript_AssignMethod(Pattern, sub);
	AScript_AssignMethod(Pattern, split);
	AScript_AssignMethod(Pattern, scan);
}

AScript_ImplementCastFrom(Pattern)
{
	if (value.IsString()) {
		Object_Pattern *pObjPattern = new Object_Pattern(env);
		if (!pObjPattern->SetPattern(sig, value.GetString(), SymbolSet::Null)) {
			delete pObjPattern;
			return false;
		}
		value = Value(pObjPattern);
		return true;
	}
	return false;
}

AScript_ImplementCastTo(Pattern)
{
	return false;
}

//-----------------------------------------------------------------------------
// Object_Match
//-----------------------------------------------------------------------------
Object_Match::~Object_Match()
{
}

Object *Object_Match::Clone() const
{
	return new Object_Match(*this);
}

String Object_Match::ToString(Signal sig, bool exprFlag)
{
	String rtn;
	rtn += "<match:";
	foreach_const (GroupList, pGroup, _groupList) {
		if (pGroup != _groupList.begin()) rtn += ",";
		char str[80];
		::sprintf(str, "%d-%d", pGroup->GetPosBegin(), pGroup->GetPosEnd());
		rtn += str;
	}
	rtn += ">";
	return rtn;
}

bool Object_Match::SetMatchInfo(const char *str,
				regex_t *pRegEx, const OnigRegion *pRegion, int posOffset)
{
	if (pRegion->num_regs == 0) return false;
	::onig_foreach_name(pRegEx, &ForeachNameCallbackStub, this);
	_str = str;
	AssignValue(AScript_Symbol(string), Value(*this, str), false);
	for (int iGroup = 0; iGroup < pRegion->num_regs; iGroup++) {
		int idxBegin = pRegion->beg[iGroup];
		int idxEnd = pRegion->end[iGroup];
		if (idxBegin > idxEnd) return false;
		int posBegin = static_cast<int>(CalcCharPos(str, idxBegin)) + posOffset;
		int posEnd = static_cast<int>(CalcCharPos(str, idxEnd)) + posOffset;
		_groupList.push_back(Group(posBegin, posEnd));
	}
	return true;
}

const Object_Match::Group *Object_Match::GetGroup(Signal sig, const Value &index) const
{
	if (index.IsNumber()) {
		size_t indexNum = static_cast<size_t>(index.GetNumber());
		if (indexNum >= _groupList.size()) {
			sig.SetError(ERR_IndexError, "index is out of range");
			return NULL;
		}
		return &_groupList[indexNum];
	} else if (index.IsString()) {
		const char *name = index.GetString();
		GroupNameDict::const_iterator iter = _groupNameDict.find(name);
		if (iter == _groupNameDict.end()) {
			sig.SetError(ERR_IndexError,
				"regular expression doesn't have a group named '%s%", name);
			return NULL;
		}
		return &_groupList[iter->second];
	} else {
		sig.SetError(ERR_TypeError, "invalid argument type");
		return NULL;
	}
}

int Object_Match::ForeachNameCallback(const String &name, int nGroups,
											int *idxGroupTbl, regex_t *pRegEx)
{
	if (nGroups > 0) _groupNameDict[name] = idxGroupTbl[0];
	return 0;
}

int Object_Match::ForeachNameCallbackStub(
			const UChar *nameRaw, const UChar *nameRawEnd,
			int nGroups, int *idxGroupTbl, regex_t *pRegEx, void *pArg)
{
	String name(reinterpret_cast<const char *>(nameRaw), nameRawEnd - nameRaw);
	return reinterpret_cast<Object_Match *>(pArg)->
					ForeachNameCallback(name, nGroups, idxGroupTbl, pRegEx);
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_Match
//-----------------------------------------------------------------------------
// str = re.match#group(index):map
AScript_DeclareMethod(Match, group)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "index", VTYPE_Any);
}

AScript_ImplementMethod(Match, group)
{
	Object_Match *pObj = Object_Match::GetSelfObj(args);
	const Object_Match::Group *pGroup = pObj->GetGroup(sig, args.GetValue(0));
	if (pGroup == NULL) return Value::Null;
	return Value(env, pObj->GetGroupString(*pGroup).c_str());
}

// list = re.match#groups()
AScript_DeclareMethod(Match, groups)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
}

AScript_ImplementMethod(Match, groups)
{
	Object_Match *pObj = Object_Match::GetSelfObj(args);
	Value result;
	ValueList &valList = result.InitAsList(env);
	const Object_Match::GroupList &groupList = pObj->GetGroupList();
	Object_Match::GroupList::const_iterator pGroup = groupList.begin();
	if (pGroup != groupList.end()) pGroup++;
	for ( ; pGroup != groupList.end(); pGroup++) {
		valList.push_back(Value(env, pObj->GetGroupString(*pGroup).c_str()));
	}
	return result;
}

// num = re.match#start(index):map
AScript_DeclareMethod(Match, start)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "index", VTYPE_Any);
}

AScript_ImplementMethod(Match, start)
{
	Object_Match *pObj = Object_Match::GetSelfObj(args);
	const Object_Match::Group *pGroup = pObj->GetGroup(sig, args.GetValue(0));
	if (pGroup == NULL) return Value::Null;
	return Value(pGroup->GetPosBegin());
}

// num = re.match#end(index):map
AScript_DeclareMethod(Match, end)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "index", VTYPE_Any);
}

AScript_ImplementMethod(Match, end)
{
	Object_Match *pObj = Object_Match::GetSelfObj(args);
	const Object_Match::Group *pGroup = pObj->GetGroup(sig, args.GetValue(0));
	if (pGroup == NULL) return Value::Null;
	return Value(pGroup->GetPosEnd());
}

// implementation of class Match
AScript_ImplementPrivClass(Match)
{
	AScript_AssignMethod(Match, group);
	AScript_AssignMethod(Match, groups);
	AScript_AssignMethod(Match, start);
	AScript_AssignMethod(Match, end);
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_String
//-----------------------------------------------------------------------------
// m = string#match(pattern:regex, pos:number => 0, endpos?:number):map
AScript_DeclareMethod(String, match)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "pattern", AScript_PrivVTYPE(Pattern));
	DeclareArg(env, "pos", VTYPE_Number, OCCUR_Once, false, false, new Expr_Value(0));
	DeclareArg(env, "endpos", VTYPE_Number, OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(String, match)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	regex_t *pRegEx = dynamic_cast<Object_Pattern *>(args.GetObject(0))->GetRegEx();
	Value result = DoMatch(env, sig, pRegEx, pSelf->GetString(),
			args.GetInt(1), args.IsNumber(2)? args.GetInt(2) : -1);
	return result;
}

// str = string#sub(pattern:regex, replace, count?:number):map
AScript_DeclareMethod(String, sub)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "pattern", AScript_PrivVTYPE(Pattern));
	DeclareArg(env, "replace", VTYPE_Any);
	DeclareArg(env, "count", VTYPE_Number, OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(String, sub)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	regex_t *pRegEx = dynamic_cast<Object_Pattern *>(args.GetObject(0))->GetRegEx();
	int cnt = args.IsNumber(2)? static_cast<int>(args.GetNumber(2)) : -1;
	Value result;
	if (args.IsString(1)) {
		result = DoSubWithString(env, sig, pRegEx,
						args.GetString(1), pSelf->GetString(), cnt);
	} else if (args.IsFunction(1)) {
		result = DoSubWithFunc(env, sig, pRegEx,
						args.GetFunction(1), pSelf->GetString(), cnt);
	} else {
		SetError_ArgumentTypeByIndex(sig, 1, args.GetValue(1));
	}
	return result;
}

// iter = string#splitreg(pattern:regex, count?:number):map {block?}
AScript_DeclareMethod(String, splitreg)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "pattern", AScript_PrivVTYPE(Pattern));
	DeclareArg(env, "count", VTYPE_Number, OCCUR_ZeroOrOnce);
	DeclareBlock(OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(String, splitreg)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	Object_Pattern *pObjPattern =
			dynamic_cast<Object_Pattern *>(Object::Reference(args.GetObject(0)));
	Object_String *pObjStr = Object_String::Reference(pSelf);
	int cntMax = args.IsNumber(1)? static_cast<int>(args.GetNumber(1)) : -1;
	return ReturnIterator(env, sig, args,
				new IteratorSplit(pObjPattern, pObjStr, cntMax));
}

// iter = string#scan(pattern:regex, pos:number => 0, endpos?:number):map {block?}
AScript_DeclareMethod(String, scan)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "pattern", AScript_PrivVTYPE(Pattern));
	DeclareArg(env, "pos", VTYPE_Number, OCCUR_Once, false, false, new Expr_Value(0));
	DeclareArg(env, "endpos", VTYPE_Number, OCCUR_ZeroOrOnce);
	DeclareBlock(OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(String, scan)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	Object_Pattern *pObjPattern =
			dynamic_cast<Object_Pattern *>(Object::Reference(args.GetObject(0)));
	Object_String *pObjStr = Object_String::Reference(pSelf);
	int posEnd = args.IsNumber(2)? args.GetInt(2) : -1;
	return ReturnIterator(env, sig, args,
			new IteratorScan(pObjPattern, pObjStr, args.GetInt(1), posEnd));
}

//-----------------------------------------------------------------------------
// AScript module functions: re
//-----------------------------------------------------------------------------
// regex = re.pattern(pattern:string):map:[icase,multiline]
AScript_DeclareFunction(pattern)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "pattern", VTYPE_String);
	DeclareAttr(AScript_Symbol(icase));
	DeclareAttr(AScript_PrivSymbol(multiline));
}

AScript_ImplementFunction(pattern)
{
	Object_Pattern *pObjPattern = new Object_Pattern(env);
	if (!pObjPattern->SetPattern(sig, args.GetString(0), args.GetAttrs())) {
		delete pObjPattern;
		return Value::Null;
	}
	return Value(pObjPattern);
}

// m = re.match(pattern:regex, str:string, pos:number => 0):map
AScript_DeclareFunction(match)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "pattern", AScript_PrivVTYPE(Pattern));
	DeclareArg(env, "str", VTYPE_String);
	DeclareArg(env, "pos", VTYPE_Number, OCCUR_Once, false, false, new Expr_Value(0));
	DeclareArg(env, "endpos", VTYPE_Number, OCCUR_ZeroOrOnce);
}

AScript_ImplementFunction(match)
{
	regex_t *pRegEx = dynamic_cast<Object_Pattern *>(args.GetObject(0))->GetRegEx();
	Value result = DoMatch(env, sig, pRegEx, args.GetString(1),
			args.GetInt(2), args.IsNumber(3)? args.GetInt(3) : -1);
	return result;
}

// str = re.sub(pattern:regex, replace, str:string, count?:number):map
AScript_DeclareFunction(sub)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "pattern", AScript_PrivVTYPE(Pattern));
	DeclareArg(env, "replace", VTYPE_Any);
	DeclareArg(env, "str", VTYPE_String);
	DeclareArg(env, "count", VTYPE_Number, OCCUR_ZeroOrOnce);
}

AScript_ImplementFunction(sub)
{
	regex_t *pRegEx = dynamic_cast<Object_Pattern *>(args.GetObject(0))->GetRegEx();
	int cnt = args.IsNumber(3)? static_cast<int>(args.GetNumber(3)) : -1;
	Value result;
	if (args.IsString(1)) {
		result = DoSubWithString(env, sig, pRegEx,
						args.GetString(1), args.GetString(2), cnt);
	} else if (args.IsFunction(1)) {
		result = DoSubWithFunc(env, sig, pRegEx,
						args.GetFunction(1), args.GetString(2), cnt);
	} else {
		SetError_ArgumentTypeByIndex(sig, 1, args.GetValue(1));
	}
	return result;
}

// iter = re.split(pattern:regex, str:string, count?:number):map {block?}
AScript_DeclareFunction(split)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "pattern", AScript_PrivVTYPE(Pattern));
	DeclareArg(env, "str", VTYPE_String);
	DeclareArg(env, "count", VTYPE_Number, OCCUR_ZeroOrOnce);
	DeclareBlock(OCCUR_ZeroOrOnce);
}

AScript_ImplementFunction(split)
{
	Object_Pattern *pObjPattern =
			dynamic_cast<Object_Pattern *>(Object::Reference(args.GetObject(0)));
	Object_String *pObjStr = Object_String::Reference(args.GetStringObj(1));
	int cntMax = args.IsNumber(2)? static_cast<int>(args.GetNumber(2)) : -1;
	return ReturnIterator(env, sig, args,
							new IteratorSplit(pObjPattern, pObjStr, cntMax));
}

// iter = re.scan(pattern:regex, str:string, pos:number => 0, endpos?:number):map {block?}
AScript_DeclareFunction(scan)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "pattern", AScript_PrivVTYPE(Pattern));
	DeclareArg(env, "str", VTYPE_String);
	DeclareArg(env, "pos", VTYPE_Number, OCCUR_Once, false, false, new Expr_Value(0));
	DeclareArg(env, "endpos", VTYPE_Number, OCCUR_ZeroOrOnce);
	DeclareBlock(OCCUR_ZeroOrOnce);
}

AScript_ImplementFunction(scan)
{
	Object_Pattern *pObjPattern =
			dynamic_cast<Object_Pattern *>(Object::Reference(args.GetObject(0)));
	Object_String *pObjStr = Object_String::Reference(args.GetStringObj(1));
	int posEnd = args.IsNumber(3)? args.GetInt(3) : -1;
	return ReturnIterator(env, sig, args,
				new IteratorScan(pObjPattern, pObjStr, args.GetInt(2), posEnd));
}

// Module entry
AScript_ModuleEntry()
{
	// symbol realization
	AScript_RealizePrivSymbol(re);
	AScript_RealizePrivSymbol(string);
	AScript_RealizePrivSymbol(multiline);
	// class realization
	AScript_RealizePrivClass(Match, "match", env.LookupClass(VTYPE_Object));
	AScript_RealizePrivClass(Pattern, "pattern", env.LookupClass(VTYPE_Object));
	// function assignment
	AScript_AssignFunction(pattern);
	AScript_AssignFunction(match);
	AScript_AssignFunction(sub);
	AScript_AssignFunction(split);
	AScript_AssignFunction(scan);
	// method assignment
	AScript_AssignMethodTo(VTYPE_String, String, match);
	AScript_AssignMethodTo(VTYPE_String, String, sub);
	AScript_AssignMethodTo(VTYPE_String, String, splitreg);
	AScript_AssignMethodTo(VTYPE_String, String, scan);
}

AScript_ModuleTerminate()
{
}

//-----------------------------------------------------------------------------
// Utilities
//-----------------------------------------------------------------------------
static regex_t *CreateRegEx(Signal sig, const char *pattern, const SymbolSet &attrs)
{
	// ::onig_end() call may be necessary when module is destroyed
	regex_t *pRegEx = NULL;
	OnigOptionType option = ONIG_OPTION_CAPTURE_GROUP; //ONIG_OPTION_NONE
	OnigEncoding enc = ONIG_ENCODING_UTF8; //ONIG_ENCODING_SJIS;
	OnigErrorInfo errInfo;
	size_t len = ::strlen(pattern);
	if (attrs.IsSet(AScript_Symbol(icase))) {
		option |= ONIG_OPTION_IGNORECASE;
	}
	if (attrs.IsSet(AScript_PrivSymbol(multiline))) {
		option |= ONIG_OPTION_MULTILINE;
	}
	int rtn = ::onig_new(&pRegEx,
				reinterpret_cast<const OnigUChar *>(pattern),
				reinterpret_cast<const OnigUChar *>(pattern + len),
				option, enc, ONIG_SYNTAX_DEFAULT, &errInfo);
	if (rtn != ONIG_NORMAL) {
		SetError_OnigurumaError(sig, rtn);
		return NULL;
	}
	return pRegEx;
}

static Value DoMatch(Environment &env, Signal sig, regex_t *pRegEx,
										const char *str, int pos, int posEnd)
{
	Value result;
	size_t len = ::strlen(str);
	const char *strStart = Forward(str, pos);
	const char *strEnd = (posEnd < 0)? str + len : Forward(str, posEnd);
	OnigRegion *pRegion = ::onig_region_new();
	int rtn = ::onig_search(pRegEx,
				reinterpret_cast<const OnigUChar *>(strStart),
				reinterpret_cast<const OnigUChar *>(str + len),
				reinterpret_cast<const OnigUChar *>(strStart),
				reinterpret_cast<const OnigUChar *>(strEnd),
				pRegion, ONIG_OPTION_NONE);
	if (rtn >= 0) {
		Object_Match *pObj = new Object_Match(env);
		if (pObj->SetMatchInfo(str, pRegEx, pRegion, pos)) {
			result.InitAsObject(pObj);
		} else {
			SetError_FailInOniguruma(sig);
			delete pObj;
		}
	} else if (rtn == ONIG_MISMATCH) {
		// nothing to do
	} else { // error
		SetError_OnigurumaError(sig, rtn);
	}
	::onig_region_free(pRegion, 1); // 1:free self, 0:free contents only
	return result;
}

static Value DoSubWithString(Environment &env, Signal sig, regex_t *pRegEx,
							const char *replace, const char *str, int cnt)
{
	enum Stat { STAT_Start, STAT_Escape };
	size_t len = ::strlen(str);
	String result;
	OnigRegion *pRegion = ::onig_region_new();
	int idx = 0;
	for ( ; cnt != 0; cnt--) {
		int rtn = ::onig_search(pRegEx,
						reinterpret_cast<const OnigUChar *>(str),
						reinterpret_cast<const OnigUChar *>(str + len),
						reinterpret_cast<const OnigUChar *>(str + idx),
						reinterpret_cast<const OnigUChar *>(str + len),
						pRegion, ONIG_OPTION_NONE);
		if (rtn >= 0) {
			if (rtn < idx || pRegion->num_regs == 0 || pRegion->end[0] <= idx) {
				SetError_FailInOniguruma(sig);
				goto error_done;
			}
			result += String(str + idx, rtn - idx);
			Stat stat = STAT_Start;
			for (const char *p = replace; *p != '\0'; p++) {
				char ch = *p;
				if (stat == STAT_Start) {
					if (ch == '\\') {
						stat = STAT_Escape;
					} else {
						result.push_back(*p);
					}
				} else if (stat == STAT_Escape) {
					if (IsDigit(ch)) {
						int iGroup = ch - '0';
						if (iGroup < pRegion->num_regs) {
							int idxBegin = pRegion->beg[iGroup];
							int idxEnd = pRegion->end[iGroup];
							result += String(str + idxBegin, idxEnd - idxBegin);
						}
						stat = STAT_Start;
					} else {
						result.push_back(GetEscaped(ch));
						stat = STAT_Start;
					}
				}
			}
			idx = pRegion->end[0];
		} else if (rtn == ONIG_MISMATCH) {
			break;
		} else { // error
			SetError_OnigurumaError(sig, rtn);
			goto error_done;
		}
	}
	::onig_region_free(pRegion, 1); // 1:free self, 0:free contents only
	result += String(str + idx);
	return Value(env, result.c_str());
error_done:
	::onig_region_free(pRegion, 1); // 1:free self, 0:free contents only
	return Value::Null;
}

static Value DoSubWithFunc(Environment &env, Signal sig, regex_t *pRegEx,
						const Function *pFunc, const char *str, int cnt)
{
	enum Stat { STAT_Start, STAT_Escape };
	size_t len = ::strlen(str);
	String result;
	OnigRegion *pRegion = ::onig_region_new();
	int idx = 0;
	for ( ; cnt != 0; cnt--) {
		int rtn = ::onig_search(pRegEx,
					reinterpret_cast<const OnigUChar *>(str),
					reinterpret_cast<const OnigUChar *>(str + len),
					reinterpret_cast<const OnigUChar *>(str + idx),
					reinterpret_cast<const OnigUChar *>(str + len),
					pRegion, ONIG_OPTION_NONE);
		if (rtn >= 0) {
			Object_Match *pObj = new Object_Match(env);
			if (!pObj->SetMatchInfo(str, pRegEx, pRegion, 0)) {
				SetError_FailInOniguruma(sig);
				delete pObj;
				goto error_done;
			}
			Value value(pObj);
			ValueList valListArg(value);
			Args args(valListArg);
			Value resultFunc = pFunc->Eval(env, sig, args);
			if (sig.IsSignalled()) goto error_done;
			result += String(str + idx, rtn - idx);
			result += resultFunc.ToString(sig, false);
			if (sig.IsSignalled()) goto error_done;
			idx = pRegion->end[0];
		} else if (rtn == ONIG_MISMATCH) {
			break;
		} else { // error
			SetError_OnigurumaError(sig, rtn);
			goto error_done;
		}
	}
	::onig_region_free(pRegion, 1); // 1:free self, 0:free contents only
	result += String(str + idx);
	return Value(env, result.c_str());
error_done:
	::onig_region_free(pRegion, 1); // 1:free self, 0:free contents only
	return Value::Null;
}

void SetError_OnigurumaError(Signal sig, int errCode)
{
	char errMsg[ONIG_MAX_ERROR_MESSAGE_LEN];
	::onig_error_code_to_str(reinterpret_cast<OnigUChar *>(errMsg), errCode);
	sig.SetError(ERR_ValueError, "oniguruma: %s", errMsg);
}

void SetError_FailInOniguruma(Signal sig)
{
	sig.SetError(ERR_SystemError,
				"something's wrong in the process of Oniguruma library");
}

AScript_EndModule(re, re)

AScript_RegisterModule(re)
