(* Copyright (C) Doom 2D: Forever Developers * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License ONLY. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . *) {$INCLUDE a_modes.inc} unit exoma; interface uses {$IFDEF USE_MEMPOOL}mempool,{$ENDIF} typinfo, SysUtils, Variants, hashtable, xparser; // ////////////////////////////////////////////////////////////////////////// // type TExomaException = class(Exception) public constructor Create (const amsg: AnsiString); constructor CreateFmt (const afmt: AnsiString; const args: array of const); end; TExomaParseException = class(TExomaException) public tokLine, tokCol: Integer; public constructor Create (pr: TTextParser; const amsg: AnsiString); constructor CreateFmt (pr: TTextParser; const afmt: AnsiString; const args: array of const); end; // ////////////////////////////////////////////////////////////////////////// // type TPropHash = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF} private mClass: TClass; mNames: THashStrInt; pl: PPropList; pc: Integer; public constructor Create (aklass: TClass; const apfx: AnsiString=''); destructor Destroy (); override; function get (obj: TObject; const fldname: AnsiString; out v: Variant): Boolean; function put (obj: TObject; const fldname: AnsiString; var v: Variant): Boolean; end; // ////////////////////////////////////////////////////////////////////////// // type TExprConstList = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF} public function valid (const cname: AnsiString): Boolean; virtual; abstract; function get (const cname: AnsiString; out v: Variant): Boolean; virtual; abstract; end; // ////////////////////////////////////////////////////////////////////////// // type TExprScope = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF} public class procedure error (const amsg: AnsiString); class procedure errorfmt (const afmt: AnsiString; const args: array of const); function getObj (const aname: AnsiString): TObject; virtual; function getField (obj: TObject; const afldname: AnsiString): Variant; virtual; procedure setField (obj: TObject; const afldname: AnsiString; var aval: Variant); virtual; end; TExprBase = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF} public class function coerce2bool (var v0: Variant): Boolean; class function toInt (var v: Variant): LongInt; public class procedure error (const amsg: AnsiString); class procedure errorfmt (const afmt: AnsiString; const args: array of const); class procedure parseError (pr: TTextParser; const amsg: AnsiString); class procedure parseErrorFmt (pr: TTextParser; const afmt: AnsiString; const args: array of const); class function parse (clist: TExprConstList; pr: TTextParser; allowAssign: Boolean=false): TExprBase; class function parse (clist: TExprConstList; const str: AnsiString; allowAssign: Boolean=false): TExprBase; class function parseStatList (clist: TExprConstList; const str: AnsiString): TExprBase; class function isFloat (var v: Variant): Boolean; inline; class function isInt (var v: Variant): Boolean; inline; class function isBool (var v: Variant): Boolean; inline; class function isStr (var v: Variant): Boolean; inline; public function value (scope: TExprScope): Variant; virtual; abstract; procedure assign (scope: TExprScope; var v: Variant); virtual; function clone (): TExprBase; virtual; abstract; end; TExprStatList = class(TExprBase) private mList: array of TExprBase; public constructor Create (); destructor Destroy (); override; procedure append (e: TExprBase); function value (scope: TExprScope): Variant; override; function toString (): AnsiString; override; function clone (): TExprBase; override; end; TObjExpr = class(TExprBase) private mName: AnsiString; public constructor Create (const aval: AnsiString); function value (scope: TExprScope): Variant; override; function toString (): AnsiString; override; function clone (): TExprBase; override; end; TLitExpr = class(TExprBase) private mValue: Variant; public constructor Create (aval: Boolean); constructor Create (aval: LongInt); constructor Create (const aval: AnsiString); constructor Create (var v: Variant); function value (scope: TExprScope): Variant; override; function toString (): AnsiString; override; function clone (): TExprBase; override; end; TUnExpr = class(TExprBase) private mOp0: TExprBase; public constructor Create (aop0: TExprBase); destructor Destroy (); override; function clone (): TExprBase; override; end; TUnExprNeg = class(TUnExpr) public function value (scope: TExprScope): Variant; override; function toString (): AnsiString; override; end; TUnExprNot = class(TUnExpr) public function value (scope: TExprScope): Variant; override; function toString (): AnsiString; override; end; TDotExpr = class(TExprBase) private mOp0: TExprBase; mField: AnsiString; public constructor Create (aop0: TExprBase; const afield: AnsiString); function value (scope: TExprScope): Variant; override; procedure assign (scope: TExprScope; var v: Variant); override; function toString (): AnsiString; override; function clone (): TExprBase; override; end; TBinExpr = class(TExprBase) private mOp0, mOp1: TExprBase; private class procedure coerce (var v0, v1: Variant); // modifies both variants public constructor Create (aop0, aop1: TExprBase); destructor Destroy (); override; function clone (): TExprBase; override; end; TBinExprAdd = class(TBinExpr) public function value (scope: TExprScope): Variant; override; function toString (): AnsiString; override; end; TBinExprSub = class(TBinExpr) public function value (scope: TExprScope): Variant; override; function toString (): AnsiString; override; end; TBinExprMul = class(TBinExpr) public function value (scope: TExprScope): Variant; override; function toString (): AnsiString; override; end; TBinExprDiv = class(TBinExpr) public function value (scope: TExprScope): Variant; override; function toString (): AnsiString; override; end; TBinExprMod = class(TBinExpr) public function value (scope: TExprScope): Variant; override; function toString (): AnsiString; override; end; TBinExprLogAnd = class(TBinExpr) public function value (scope: TExprScope): Variant; override; function toString (): AnsiString; override; end; TBinExprLogOr = class(TBinExpr) public function value (scope: TExprScope): Variant; override; function toString (): AnsiString; override; end; TBinExprCmpLess = class(TBinExpr) public function value (scope: TExprScope): Variant; override; function toString (): AnsiString; override; end; TBinExprCmpGreat = class(TBinExpr) public function value (scope: TExprScope): Variant; override; function toString (): AnsiString; override; end; TBinExprCmpLessEqu = class(TBinExpr) public function value (scope: TExprScope): Variant; override; function toString (): AnsiString; override; end; TBinExprCmpGreatEqu = class(TBinExpr) public function value (scope: TExprScope): Variant; override; function toString (): AnsiString; override; end; TBinExprCmpEqu = class(TBinExpr) public function value (scope: TExprScope): Variant; override; function toString (): AnsiString; override; end; TBinExprCmpNotEqu = class(TBinExpr) public function value (scope: TExprScope): Variant; override; function toString (): AnsiString; override; end; TBinAssign = class(TBinExpr) public function value (scope: TExprScope): Variant; override; function toString (): AnsiString; override; end; TExprCond = class(TExprBase) private mCond, mTrue, mFalse: TExprBase; public constructor Create (); destructor Destroy (); override; function value (scope: TExprScope): Variant; override; function toString (): AnsiString; override; function clone (): TExprBase; override; end; // ////////////////////////////////////////////////////////////////////////// // function typeKind2Str (t: TTypeKind): AnsiString; implementation uses utils; // ////////////////////////////////////////////////////////////////////////// // constructor TExomaException.Create (const amsg: AnsiString); begin inherited Create(amsg); end; constructor TExomaException.CreateFmt (const afmt: AnsiString; const args: array of const); begin inherited Create(formatstrf(afmt, args)); end; // ////////////////////////////////////////////////////////////////////////// // constructor TExomaParseException.Create (pr: TTextParser; const amsg: AnsiString); begin if (pr <> nil) then begin tokLine := pr.tokLine; tokCol := pr.tokCol; end else begin tokLine := 0; tokCol := 0; end; inherited Create(amsg); end; constructor TExomaParseException.CreateFmt (pr: TTextParser; const afmt: AnsiString; const args: array of const); begin if (pr <> nil) then begin tokLine := pr.tokLine; tokCol := pr.tokCol; end else begin tokLine := 0; tokCol := 0; end; inherited Create(formatstrf(afmt, args)); end; // ////////////////////////////////////////////////////////////////////////// // function typeKind2Str (t: TTypeKind): AnsiString; begin case t of tkUnknown: result := 'Unknown'; tkInteger: result := 'Integer'; tkChar: result := 'AnsiChar'; tkEnumeration: result := 'Enumeration'; tkFloat: result := 'Float'; tkSet: result := 'Set'; tkMethod: result := 'Method'; tkSString: result := 'ShortString'; tkLString: result := 'LString'; tkAString: result := 'AnsiString'; tkWString: result := 'WideString'; tkVariant: result := 'Variant'; tkArray: result := 'Array'; tkRecord: result := 'Record'; tkInterface: result := 'Interface'; tkClass: result := 'Class'; tkObject: result := 'Object'; tkWChar: result := 'WideChar'; tkBool: result := 'Boolean'; tkInt64: result := 'Int64'; tkQWord: result := 'UInt64'; tkDynArray: result := 'DynArray'; tkInterfaceRaw: result := 'InterfaceRaw'; tkProcVar: result := 'ProcVar'; tkUString: result := 'UString'; tkUChar: result := 'UChar'; tkHelper: result := 'Helper'; tkFile: result := 'File'; tkClassRef: result := 'ClassRef'; tkPointer: result := 'Pointer'; else result := ''; end; end; // ////////////////////////////////////////////////////////////////////////// // (* procedure dumpPublishedProperties (obj: TObject); var pt: PTypeData; pi: PTypeInfo; i, j: Integer; pp: PPropList; begin if (obj = nil) then exit; //e_LogWritefln('Object of type ''%s'':', [obj.ClassName]); pi := obj.ClassInfo; pt := GetTypeData(pi); //e_LogWritefln('property count: %s', [pt.PropCount]); GetMem(pp, pt^.PropCount*sizeof(Pointer)); try j := GetPropList(pi, [tkInteger, tkBool, tkSString, tkLString, tkAString, tkSet, tkEnumeration], pp); //e_LogWritefln('ordinal property count: %s', [j]); for i := 0 to j-1 do begin { if (typinfo.PropType(obj, pp^[i].name) in [tkSString, tkLString, tkAString]) then begin e_LogWritefln(' #%s: <%s>; type: %s; value: <%s>', [i+1, pp^[i].name, typeKind2Str(typinfo.PropType(obj, pp^[i].name)), GetStrProp(obj, pp^[i])]); end else if (typinfo.PropType(obj, pp^[i].name) = tkSet) then begin e_LogWritefln(' #%s: <%s>; type: %s; value: %s', [i+1, pp^[i].name, typeKind2Str(typinfo.PropType(obj, pp^[i].name)), GetSetProp(obj, pp^[i], true)]); end else if (typinfo.PropType(obj, pp^[i].name) = tkEnumeration) then begin e_LogWritefln(' #%s: <%s>; type: %s; value: <%s>', [i+1, pp^[i].name, typeKind2Str(typinfo.PropType(obj, pp^[i].name)), GetEnumProp(obj, pp^[i])]); end else begin e_LogWritefln(' #%s: <%s>; type: %s; value: %s', [i+1, pp^[i].name, typeKind2Str(typinfo.PropType(obj, pp^[i].name)), GetOrdProp(obj, pp^[i])]); end; } end; finally FreeMem(pp); end; end; *) // ////////////////////////////////////////////////////////////////////////// // constructor TPropHash.Create (aklass: TClass; const apfx: AnsiString=''); var pi: PTypeInfo; pt: PTypeData; idx: Integer; n: AnsiString; begin mClass := aklass; mNames := THashStrInt.Create(); pi := aklass.ClassInfo; pt := GetTypeData(pi); GetMem(pl, pt^.PropCount*sizeof(Pointer)); pc := GetPropList(pi, [tkInteger, tkBool, tkSString, tkLString, tkAString, {tkSet,} tkEnumeration], pl); for idx := 0 to pc-1 do begin if (Length(apfx) > 0) then begin if (Length(pl^[idx].name) < Length(apfx)) then continue; n := pl^[idx].name; if (Copy(n, 1, Length(apfx)) <> apfx) then continue; Delete(n, 1, Length(apfx)); mNames.put(n, idx); end else begin mNames.put(pl^[idx].name, idx); end; end; end; destructor TPropHash.Destroy (); begin mNames.Free(); mNames := nil; if (pl <> nil) then FreeMem(pl); pl := nil; pc := 0; mClass := nil; end; function TPropHash.get (obj: TObject; const fldname: AnsiString; out v: Variant): Boolean; var idx: Integer; begin result := false; if mNames.get(fldname, idx) then begin result := true; case pl^[idx].PropType.Kind of tkSString, tkLString, tkAString: v := GetStrProp(obj, pl^[idx]); tkEnumeration: v := GetEnumProp(obj, pl^[idx]); tkBool: if (GetOrdProp(obj, pl^[idx]) = 0) then v := false else v := true; tkInteger, tkChar: v := LongInt(GetOrdProp(obj, pl^[idx])); //tkFloat: result := 'Float'; //tkClass: result := 'Class'; //tkInt64: result := 'Int64'; //tkClassRef: result := 'ClassRef'; else result := false; end; if result then exit; end; v := Unassigned; end; function TPropHash.put (obj: TObject; const fldname: AnsiString; var v: Variant): Boolean; var idx: Integer; begin result := false; if mNames.get(fldname, idx) then begin result := true; case pl^[idx].PropType.Kind of tkSString, tkLString, tkAString: SetStrProp(obj, pl^[idx], VarToStr(v)); tkEnumeration: SetEnumProp(obj, pl^[idx], VarToStr(v)); tkBool: if TExprBase.coerce2bool(v) then SetOrdProp(obj, pl^[idx], 1) else SetOrdProp(obj, pl^[idx], 0); tkInteger, tkChar: SetOrdProp(obj, pl^[idx], TExprBase.toInt(v)); //tkFloat: result := 'Float'; //tkClass: result := 'Class'; //tkInt64: result := 'Int64'; //tkClassRef: result := 'ClassRef'; else result := false; end; if result then exit; end; end; // ////////////////////////////////////////////////////////////////////////// // class procedure TExprScope.error (const amsg: AnsiString); begin raise TExomaException.Create(amsg); end; class procedure TExprScope.errorfmt (const afmt: AnsiString; const args: array of const); begin raise TExomaException.CreateFmt(afmt, args); end; function TExprScope.getObj (const aname: AnsiString): TObject; begin result := nil; errorfmt('unknown object ''%s''', [aname]); end; function TExprScope.getField (obj: TObject; const afldname: AnsiString): Variant; begin result := Unassigned; errorfmt('unknown field ''%s''', [afldname]); end; procedure TExprScope.setField (obj: TObject; const afldname: AnsiString; var aval: Variant); begin errorfmt('unknown field ''%s''', [afldname]); end; // ////////////////////////////////////////////////////////////////////////// // class procedure TExprBase.error (const amsg: AnsiString); begin raise TExomaException.Create(amsg); end; class procedure TExprBase.errorfmt (const afmt: AnsiString; const args: array of const); begin raise TExomaException.CreateFmt(afmt, args); end; class procedure TExprBase.parseError (pr: TTextParser; const amsg: AnsiString); begin raise TExomaParseException.Create(pr, amsg); end; class procedure TExprBase.parseErrorFmt (pr: TTextParser; const afmt: AnsiString; const args: array of const); begin raise TExomaParseException.CreateFmt(pr, afmt, args); end; class function TExprBase.coerce2bool (var v0: Variant): Boolean; begin case varType(v0) of varEmpty: result := false; varNull: result := false; varSingle: result := (Single(v0) <> 0.0); varDouble: result := (Double(v0) <> 0.0); varString: result := (Length(AnsiString(v0)) <> 0); varBoolean: result := Boolean(v0); varShortInt, varSmallint, varInteger, varByte, varWord, varLongWord: result := (LongInt(v0) <> 0); varInt64: result := (Int64(v0) <> 0); varQWord: result := (UInt64(v0) <> 0); else begin result := false; error('can''t coerce type to boolean'); end; end; end; class function TExprBase.isFloat (var v: Variant): Boolean; inline; begin case varType(v) of varSingle, varDouble: result := true; else result := false; end; end; class function TExprBase.isInt (var v: Variant): Boolean; inline; begin case varType(v) of varShortInt, varSmallint, varInteger, varByte, varWord, varLongWord: result := true; else result := false; end; end; class function TExprBase.isBool (var v: Variant): Boolean; inline; begin result := (varType(v) = varBoolean); end; class function TExprBase.isStr (var v: Variant): Boolean; inline; begin result := (varType(v) = varString); end; class function TExprBase.toInt (var v: Variant): LongInt; begin case varType(v) of varSingle: result := trunc(Single(v)); varDouble: result := trunc(Double(v)); varBoolean: if Boolean(v) then result := 1 else result := 0; varShortInt, varSmallint, varInteger, varByte, varWord, varLongWord: result := LongInt(v); varInt64: result := LongInt(Int64(v)); else begin result := 0; TExprBase.error('can''t coerce type to integer'); end; end; end; procedure TExprBase.assign (scope: TExprScope; var v: Variant); begin error('not an lvalue'); end; // ////////////////////////////////////////////////////////////////////////// // constructor TExprStatList.Create (); begin mList := nil; end; destructor TExprStatList.Destroy (); var f: Integer; begin for f := 0 to High(mList) do mList[f].Free(); mList := nil; end; procedure TExprStatList.append (e: TExprBase); begin if (e <> nil) then begin SetLength(mList, Length(mList)+1); mList[High(mList)] := e; end; end; function TExprStatList.value (scope: TExprScope): Variant; var f: Integer; begin result := false; for f := 0 to High(mList) do result := mList[f].value(scope); end; function TExprStatList.toString (): AnsiString; var f: Integer; begin result := ''; for f := 0 to High(mList) do result += mList[f].toString()+';'; end; function TExprStatList.clone (): TExprBase; var r: TExprStatList; f: Integer; begin r := TExprStatList.Create(); SetLength(r.mList, Length(mList)); for f := 0 to High(mList) do r.mList[f] := nil; try for f := 0 to High(mList) do r.mList[f] := mList[f].clone(); except r.Free(); end; result := r; end; // ////////////////////////////////////////////////////////////////////////// // constructor TExprCond.Create (); begin mCond := nil; mTrue := nil; mFalse := nil; end; destructor TExprCond.Destroy (); begin mFalse.Free(); mTrue.Free(); mCond.Free(); end; function TExprCond.value (scope: TExprScope): Variant; begin result := mCond.value(scope); if coerce2bool(result) then result := mTrue.value(scope) else result := mFalse.value(scope); end; function TExprCond.toString (): AnsiString; begin result := '('+mCond.toString()+'?'+mTrue.toString()+':'+mFalse.toString()+')'; end; function TExprCond.clone (): TExprBase; begin result := TExprCond.Create(); TExprCond(result).mCond := mCond.clone(); TExprCond(result).mTrue := mTrue.clone(); TExprCond(result).mFalse := mFalse.clone(); end; // ////////////////////////////////////////////////////////////////////////// // constructor TObjExpr.Create (const aval: AnsiString); begin mName := aval; end; function TObjExpr.value (scope: TExprScope): Variant; begin result := UInt64(PtrUInt(Pointer(scope.getObj(mName)))); end; function TObjExpr.toString (): AnsiString; begin result := mName; end; function TObjExpr.clone (): TExprBase; begin result := TObjExpr.Create(mName); end; // ////////////////////////////////////////////////////////////////////////// // constructor TLitExpr.Create (aval: Boolean); begin mValue := aval; end; constructor TLitExpr.Create (aval: LongInt); begin mValue := aval; end; constructor TLitExpr.Create (const aval: AnsiString); begin mValue := aval; end; constructor TLitExpr.Create (var v: Variant); begin mValue := v; end; function TLitExpr.value (scope: TExprScope): Variant; begin result := mValue; end; function TLitExpr.toString (): AnsiString; begin result := VarToStr(mValue); if isStr(mValue) then result := quoteStr(result); end; function TLitExpr.clone (): TExprBase; begin result := TLitExpr.Create(0); (result as TLitExpr).mValue := mValue; end; // ////////////////////////////////////////////////////////////////////////// // constructor TUnExpr.Create (aop0: TExprBase); begin mOp0 := aop0; end; destructor TUnExpr.Destroy (); begin mOp0.Free(); inherited; end; function TUnExpr.clone (): TExprBase; begin result := (self.ClassType.Create() as TUnExpr); (result as TUnExpr).mOp0 := mOp0.clone(); end; function TUnExprNeg.value (scope: TExprScope): Variant; begin result := mOp0.value(scope); case varType(result) of varSingle: result := -Single(result); varDouble: result := -Double(result); varShortInt, varSmallInt, varInteger, varByte, varWord: result := -LongInt(result); varInt64: result := -Int64(result); varLongWord: result := -LongInt(result); else error('can''t negate non-number'); end; end; function TUnExprNeg.toString (): AnsiString; begin result := '-('+mOp0.toString()+')'; end; function TUnExprNot.value (scope: TExprScope): Variant; begin result := mOp0.value(scope); result := not coerce2bool(result); end; function TUnExprNot.toString (): AnsiString; begin result := '!('+mOp0.toString()+')'; end; // ////////////////////////////////////////////////////////////////////////// // constructor TDotExpr.Create (aop0: TExprBase; const afield: AnsiString); begin mOp0 := aop0; mField := afield; end; function TDotExpr.value (scope: TExprScope): Variant; begin result := mOp0.value(scope); if (varType(result) <> varQWord) then errorfmt('can''t take field ''%s'' value of non-object', [mField]); result := scope.getField(TObject(PtrUInt(UInt64(result))), mField); end; procedure TDotExpr.assign (scope: TExprScope; var v: Variant); var o: Variant; begin o := mOp0.value(scope); if (varType(o) <> varQWord) then errorfmt('can''t assign value to field ''%s'' of non-object', [mField]); scope.setField(TObject(PtrUInt(UInt64(o))), mField, v); end; function TDotExpr.clone (): TExprBase; begin result := TDotExpr.Create(mOp0, mField); end; function TDotExpr.toString (): AnsiString; begin result := mOp0.toString()+'.'+mField; end; // ////////////////////////////////////////////////////////////////////////// // constructor TBinExpr.Create (aop0, aop1: TExprBase); begin mOp0 := aop0; mOp1 := aop1; end; destructor TBinExpr.Destroy (); begin mOp1.Free(); mOp0.Free(); inherited; end; function TBinExpr.clone (): TExprBase; begin result := (self.ClassType.Create() as TBinExpr); (result as TBinExpr).mOp0 := mOp0.clone(); (result as TBinExpr).mOp1 := mOp1.clone(); end; class procedure TBinExpr.coerce (var v0, v1: Variant); begin if (varType(v0) <> varType(v1)) then begin if isStr(v0) or isStr(v1) then begin if isFloat(v0) then v0 := formatstrf('%s', [Double(v0)]) else if isInt(v0) then v0 := formatstrf('%s', [LongInt(v0)]) else if isBool(v0) then v0 := formatstrf('%s', [Boolean(v0)]) else if isStr(v0) then begin end else error('can''t coerce value to string'); if isFloat(v1) then v1 := formatstrf('%s', [Double(v1)]) else if isInt(v1) then v1 := formatstrf('%s', [LongInt(v1)]) else if isBool(v1) then v1 := formatstrf('%s', [Boolean(v1)]) else if isStr(v0) then begin end else error('can''t coerce value to string'); end else if isFloat(v0) or isFloat(v1) then begin if isFloat(v0) or isInt(v0) then v0 := Double(v0) else if isBool(v0) then begin if Boolean(v0) then v0 := Double(1.0) else v0 := Double(0.0); end else error('can''t coerce value to float'); if isFloat(v1) or isInt(v1) then v1 := Double(v1) else if isBool(v1) then begin if Boolean(v1) then v1 := Double(1.0) else v1 := Double(0.0); end else error('can''t coerce value to float'); end else if isInt(v0) or isInt(v1) then begin if isBool(v0) then begin if Boolean(v0) then v0 := LongInt(1) else v0 := LongInt(0); end else if isFloat(v0) then v0 := LongInt(trunc(Double(v0))) else if isInt(v0) then begin end else error('can''t coerce value to integer'); if isBool(v1) then begin if Boolean(v1) then v1 := LongInt(1) else v1 := LongInt(0); end else if isFloat(v1) then v1 := LongInt(trunc(Double(v1))) else if isInt(v1) then begin end else error('can''t coerce value to integer'); end else begin error('can''t operate with value of invalid type'); end; end; end; // ////////////////////////////////////////////////////////////////////////// // function TBinExprAdd.value (scope: TExprScope): Variant; var r1: Variant; begin result := mOp0.value(scope); r1 := mOp1.value(scope); coerce(result, r1); case varType(result) of varSingle, varDouble: result := Double(result)+Double(r1); varString: result := AnsiString(result)+AnsiString(r1); varShortInt, varSmallint, varInteger, varByte, varWord, varLongWord: result := LongInt(result)+LongInt(r1); varInt64: result := Int64(result)+Int64(r1); else error('can''t add non-numbers and non-strings'); end; end; function TBinExprAdd.toString (): AnsiString; begin result := '('+mOp0.toString()+'+'+mOp1.toString+')'; end; function TBinExprSub.value (scope: TExprScope): Variant; var r1: Variant; begin result := mOp0.value(scope); r1 := mOp1.value(scope); coerce(result, r1); case varType(result) of varSingle, varDouble: result := Double(result)-Double(r1); varShortInt, varSmallint, varInteger, varByte, varWord, varLongWord: result := LongInt(result)-LongInt(r1); varInt64: result := Int64(result)-Int64(r1); else error('can''t subtract non-numbers'); end; end; function TBinExprSub.toString (): AnsiString; begin result := '('+mOp0.toString()+'-'+mOp1.toString+')'; end; function TBinExprMul.value (scope: TExprScope): Variant; var r1: Variant; begin result := mOp0.value(scope); r1 := mOp1.value(scope); coerce(result, r1); case varType(result) of varSingle, varDouble: result := Double(result)*Double(r1); varShortInt, varSmallint, varInteger, varByte, varWord, varLongWord: result := LongInt(result)*LongInt(r1); varInt64: result := Int64(result)*Int64(r1); else error('can''t multiply non-numbers'); end; end; function TBinExprMul.toString (): AnsiString; begin result := '('+mOp0.toString()+'*'+mOp1.toString+')'; end; function TBinExprDiv.value (scope: TExprScope): Variant; var r1: Variant; begin result := mOp0.value(scope); r1 := mOp1.value(scope); coerce(result, r1); case varType(result) of varSingle, varDouble: result := Double(result)/Double(r1); varShortInt, varSmallint, varInteger, varByte, varWord, varLongWord: result := LongInt(result) div LongInt(r1); varInt64: result := Int64(result) div Int64(r1); else error('can''t divide non-numbers'); end; end; function TBinExprDiv.toString (): AnsiString; begin result := '('+mOp0.toString()+'/'+mOp1.toString+')'; end; function TBinExprMod.value (scope: TExprScope): Variant; var r1: Variant; begin result := mOp0.value(scope); r1 := mOp1.value(scope); coerce(result, r1); case varType(result) of varShortInt, varSmallint, varInteger, varByte, varWord, varLongWord: result := LongInt(result) mod LongInt(r1); varInt64: result := Int64(result) mod Int64(r1); else error('can''t do modulo on non-numbers'); end; end; function TBinExprMod.toString (): AnsiString; begin result := '('+mOp0.toString()+'%'+mOp1.toString+')'; end; function TBinExprLogAnd.value (scope: TExprScope): Variant; begin result := mOp0.value(scope); if not coerce2bool(result) then begin result := false; exit; end; result := mOp1.value(scope); result := coerce2bool(result); end; function TBinExprLogAnd.toString (): AnsiString; begin result := '('+mOp0.toString()+'&&'+mOp1.toString+')'; end; function TBinExprLogOr.value (scope: TExprScope): Variant; begin result := mOp0.value(scope); if coerce2bool(result) then begin result := true; exit; end; result := mOp1.value(scope); result := coerce2bool(result); end; function TBinExprLogOr.toString (): AnsiString; begin result := '('+mOp0.toString()+'||'+mOp1.toString+')'; end; function TBinExprCmpLess.value (scope: TExprScope): Variant; var r1: Variant; begin result := mOp0.value(scope); r1 := mOp1.value(scope); coerce(result, r1); case varType(result) of varSingle, varDouble: result := Boolean(Double(result) < Double(r1)); varShortInt, varSmallint, varInteger, varByte, varWord, varLongWord: result := Boolean(LongInt(result) < LongInt(r1)); varInt64: result := Boolean(Int64(result) < Int64(r1)); varString: result := Boolean(AnsiString(result) < AnsiString(r1)); else error('can''t compare non-numbers and non-strings'); end; end; function TBinExprCmpLess.toString (): AnsiString; begin result := '('+mOp0.toString()+'<'+mOp1.toString+')'; end; function TBinExprCmpGreat.value (scope: TExprScope): Variant; var r1: Variant; begin result := mOp0.value(scope); r1 := mOp1.value(scope); coerce(result, r1); case varType(result) of varSingle, varDouble: result := Boolean(Double(result) > Double(r1)); varShortInt, varSmallint, varInteger, varByte, varWord, varLongWord: result := Boolean(LongInt(result) > LongInt(r1)); varInt64: result := Boolean(Int64(result) > Int64(r1)); varString: result := Boolean(AnsiString(result) > AnsiString(r1)); else error('can''t compare non-numbers and non-strings'); end; end; function TBinExprCmpGreat.toString (): AnsiString; begin result := '('+mOp0.toString()+'>'+mOp1.toString+')'; end; function TBinExprCmpLessEqu.value (scope: TExprScope): Variant; var r1: Variant; begin result := mOp0.value(scope); r1 := mOp1.value(scope); coerce(result, r1); case varType(result) of varSingle, varDouble: result := Boolean(Double(result) <= Double(r1)); varShortInt, varSmallint, varInteger, varByte, varWord, varLongWord: result := Boolean(LongInt(result) <= LongInt(r1)); varInt64: result := Boolean(Int64(result) <= Int64(r1)); varString: result := Boolean(AnsiString(result) <= AnsiString(r1)); else error('can''t compare non-numbers and non-strings'); end; end; function TBinExprCmpLessEqu.toString (): AnsiString; begin result := '('+mOp0.toString()+'<='+mOp1.toString+')'; end; function TBinExprCmpGreatEqu.value (scope: TExprScope): Variant; var r1: Variant; begin result := mOp0.value(scope); r1 := mOp1.value(scope); coerce(result, r1); case varType(result) of varSingle, varDouble: result := Boolean(Double(result) >= Double(r1)); varShortInt, varSmallint, varInteger, varByte, varWord, varLongWord: result := Boolean(LongInt(result) >= LongInt(r1)); varInt64: result := Boolean(Int64(result) >= Int64(r1)); varString: result := Boolean(AnsiString(result) >= AnsiString(r1)); else error('can''t compare non-numbers and non-strings'); end; end; function TBinExprCmpGreatEqu.toString (): AnsiString; begin result := '('+mOp0.toString()+'>='+mOp1.toString+')'; end; function TBinExprCmpEqu.value (scope: TExprScope): Variant; var r1: Variant; begin result := mOp0.value(scope); r1 := mOp1.value(scope); coerce(result, r1); case varType(result) of varSingle, varDouble: result := Boolean(Double(result) = Double(r1)); varShortInt, varSmallint, varInteger, varByte, varWord, varLongWord: result := Boolean(LongInt(result) = LongInt(r1)); varInt64: result := Boolean(Int64(result) = Int64(r1)); varString: result := Boolean(AnsiString(result) = AnsiString(r1)); varBoolean: result := (Boolean(result) = Boolean(r1)); varQWord: result := (UInt64(result) = UInt64(r1)); else error('can''t compare non-numbers and non-strings'); end; end; function TBinExprCmpEqu.toString (): AnsiString; begin result := '('+mOp0.toString()+'=='+mOp1.toString+')'; end; function TBinExprCmpNotEqu.value (scope: TExprScope): Variant; var r1: Variant; begin result := mOp0.value(scope); r1 := mOp1.value(scope); coerce(result, r1); case varType(result) of varSingle, varDouble: result := Boolean(Double(result) <> Double(r1)); varShortInt, varSmallint, varInteger, varByte, varWord, varLongWord: result := Boolean(LongInt(result) <> LongInt(r1)); varInt64: result := Boolean(Int64(result) <> Int64(r1)); varString: result := Boolean(AnsiString(result) <> AnsiString(r1)); varBoolean: result := (Boolean(result) <> Boolean(r1)); varQWord: result := (UInt64(result) <> UInt64(r1)); else error('can''t compare non-numbers and non-strings'); end; end; function TBinExprCmpNotEqu.toString (): AnsiString; begin result := '('+mOp0.toString()+'<>'+mOp1.toString+')'; end; // ////////////////////////////////////////////////////////////////////////// // function TBinAssign.value (scope: TExprScope): Variant; begin result := mOp1.value(scope); mOp0.assign(scope, result); end; function TBinAssign.toString (): AnsiString; begin result := mOp0.toString()+'='+mOp1.toString(); end; // ////////////////////////////////////////////////////////////////////////// // class function TExprBase.parse (clist: TExprConstList; const str: AnsiString; allowAssign: Boolean=false): TExprBase; var pr: TTextParser; begin pr := TStrTextParser.Create(str); try result := parse(clist, pr, allowAssign); if (pr.tokType <> pr.TTEOF) then begin result.Free(); parseError(pr, 'extra code in expression'); end; finally pr.Free(); end; end; class function TExprBase.parseStatList (clist: TExprConstList; const str: AnsiString): TExprBase; var pr: TTextParser = nil; r: TExprStatList = nil; e: TExprBase = nil; begin pr := TStrTextParser.Create(str); if (pr.tokType = pr.TTEOF) then begin pr.Free(); result := nil; exit; end; r := TExprStatList.Create(); result := nil; try try while true do begin while pr.eatDelim(';') do begin end; if (pr.tokType = pr.TTEOF) then break; e := parse(clist, pr, true); if (e = nil) then break; //writeln(': ', e.toString()); r.append(e); if (pr.tokType = pr.TTEOF) then break; //writeln('tt=', pr.tokType, ' <', pr.tokStr, '>'); //writeln(r.toString()); pr.expectDelim(';'); end; result := r; r := nil; except on e: TExomaException do raise TExomaParseException.Create(pr, e.message); on e: Exception do raise TExomaParseException.Create(pr, e.message); else raise; end; finally r.Free(); pr.Free(); end; end; class function TExprBase.parse (clist: TExprConstList; pr: TTextParser; allowAssign: Boolean=false): TExprBase; function expr (): TExprBase; forward; function doTerm (): TExprBase; var id: AnsiString; v: Variant; begin result := nil; try if pr.eatDelim('(') then begin result := expr(); pr.expectDelim(')'); exit; end; if pr.eatDelim('!') then begin result := doTerm(); result := TUnExprNot.Create(result); exit; end; if pr.eatDelim('+') then begin result := doTerm(); exit; end; if pr.eatDelim('-') then begin result := doTerm(); result := TUnExprNeg.Create(result); exit; end; if (pr.tokType = pr.TTInt) then begin result := TLitExpr.Create(pr.expectInt()); exit; end; if (pr.tokType = pr.TTStr) then begin result := TLitExpr.Create(pr.expectStr(true)); exit; end; if (pr.tokType = pr.TTId) then begin if (pr.tokStr = 'true') then begin result := TLitExpr.Create(true); pr.skipToken(); exit; end; if (pr.tokStr = 'false') then begin result := TLitExpr.Create(false); pr.skipToken(); exit; end; if (CompareText(pr.tokStr, 'true') = 0) or (CompareText(pr.tokStr, 'false') = 0) then parseError(pr, '`true` and `false` are case-sensitive'); id := pr.expectId(); if (clist <> nil) then begin if clist.get(id, v) then begin result := TLitExpr.Create(v); exit; end; if not clist.valid(id) then parseErrorFmt(pr, 'unknown identifier ''%s''', [id]); end; result := TObjExpr.Create(id); while (pr.tokType = pr.TTDelim) and (pr.tokChar = '.') do begin pr.skipToken(); result := TDotExpr.Create(result, pr.expectId()); end; exit; end; except result.Free(); raise; end; parseError(pr, 'invalid term'); end; function doMulDiv (): TExprBase; begin result := doTerm(); try while true do begin if pr.eatDelim('*') then result := TBinExprMul.Create(result, doTerm()) else if pr.eatDelim('/') then result := TBinExprDiv.Create(result, doTerm()) else if pr.eatDelim('%') then result := TBinExprMod.Create(result, doTerm()) else break; end; except result.Free(); raise; end; end; function doPlusMinus (): TExprBase; begin result := doMulDiv(); try while true do begin if pr.eatDelim('+') then result := TBinExprAdd.Create(result, doMulDiv()) else if pr.eatDelim('-') then result := TBinExprSub.Create(result, doMulDiv()) else break; end; except result.Free(); raise; end; end; function doCmp (): TExprBase; begin result := doPlusMinus(); try while true do begin if pr.eatDelim('<') then result := TBinExprCmpLess.Create(result, doPlusMinus()) else if pr.eatDelim('>') then result := TBinExprCmpGreat.Create(result, doPlusMinus()) else if pr.eatTT(pr.TTLessEqu) then result := TBinExprCmpLessEqu.Create(result, doPlusMinus()) else if pr.eatTT(pr.TTGreatEqu) then result := TBinExprCmpGreatEqu.Create(result, doPlusMinus()) else break; end; except result.Free(); raise; end; end; function doCmpEqu (): TExprBase; begin result := doCmp(); try while true do begin if pr.eatTT(pr.TTEqu) then result := TBinExprCmpEqu.Create(result, doCmp()) else if pr.eatTT(pr.TTNotEqu) then result := TBinExprCmpNotEqu.Create(result, doCmp()) else break; end; except result.Free(); raise; end; end; function doLogAnd (): TExprBase; begin result := doCmpEqu(); try while true do begin if pr.eatTT(pr.TTLogAnd) then result := TBinExprLogAnd.Create(result, doCmpEqu()) else break; end; except result.Free(); raise; end; end; function doLogOr (): TExprBase; begin result := doLogAnd(); try while true do begin if pr.eatTT(pr.TTLogOr) then result := TBinExprLogOr.Create(result, doLogAnd()) else break; end; except result.Free(); raise; end; end; // funcall, [], dot // !, ~ // *, /, % // +, - // <<, >> // <, <=, >, >= // ==, != // & // ^ // | // && // || function expr0 (): TExprBase; var neg: Boolean; e: TExprBase = nil; list: TExprStatList = nil; begin result := nil; try while true do begin if pr.eatDelim('-') then neg := true else if pr.eatDelim('+') then neg := false else neg := false; e := doLogOr(); if neg then e := TUnExprNeg.Create(e); if allowAssign and pr.eatDelim('=') then e := TBinAssign.Create(e, expr()); if not pr.eatDelim(',') then begin if (result = nil) then result := e else list.append(e); break; end; //assert(false); if (list = nil) then begin list := TExprStatList.Create(); result := list; end; list.append(e); e := nil; end; except e.Free(); list.Free(); end; end; function expr (): TExprBase; var c: TExprCond; begin result := expr0(); // ternary if pr.eatDelim('?') then begin c := TExprCond.Create(); c.mCond := result; try c.mTrue := expr(); pr.expectDelim(':'); c.mFalse := expr(); result := c; except c.Free(); end; end; end; var oas: TTextParser.TOptions; begin if (pr = nil) or (pr.tokType = pr.TTEOF) then begin result := nil; exit; end; oas := pr.options; try pr.options := pr.options-[pr.TOption.SignedNumbers]; try result := expr(); finally pr.options := oas; end; except on e: TExomaException do raise TExomaParseException.Create(pr, e.message); on e: Exception do raise TExomaParseException.Create(pr, e.message); else raise; end; end; { varEmpty: varNull: varSingle: varDouble: varDecimal: varCurrency: varDate: varOleStr: varStrArg: varString: varDispatch: varBoolean: varVariant: varUnknown: varShortInt: varSmallint: varInteger: varInt64: varByte: varWord: varLongWord: varQWord: varError: } end.