grendel-1.0.0a7/backup/
grendel-1.0.0a7/bin/
grendel-1.0.0a7/boards/
grendel-1.0.0a7/clans/
grendel-1.0.0a7/documentation/todo/
grendel-1.0.0a7/help/
grendel-1.0.0a7/logs/
grendel-1.0.0a7/players/
grendel-1.0.0a7/progs/
grendel-1.0.0a7/races/
grendel-1.0.0a7/src/contrib/
grendel-1.0.0a7/src/modules/speller/
grendel-1.0.0a7/src/modules/status/
grendel-1.0.0a7/src/tests/
grendel-1.0.0a7/src/tests/dunit/
{
	Summary:
		Grendel Virtual (Stack) Machine
	
	## $Id: gvm.pas,v 1.11 2004/04/21 21:08:59 druid Exp $
}

unit gvm;


interface


uses 
{$IFDEF WIN32}
	Windows,
{$ENDIF}
	SysUtils, 
	Variants,
	dtypes;


const
  stackSize = 512;


type
  GVMError = procedure(owner : TObject; const errorMsg : string);
	GSystemTrap = procedure(owner : TObject; const msg : string);
	GExternalTrap = function(obj : variant; const member : string) : variant;
	GSignalTrap = procedure(owner : TObject; const signal : string);
	GWaitTrap = function(owner : TObject; const signal : string) : boolean;

  GSignature = record
    resultType : integer;
    paramTypes : array of integer;
  end;

  GExternalMethod = class
    name : string;
    classAddr, methodAddr : pointer;
    signature : GSignature;
  end;

  GSymbol = class
    id : string;
    addr : integer;
  end;

  GCodeBlock = class
		code : array of char;
		codeSize, dataSize : integer;
		symbols : GHashTable;
  end;

	GContext = class
	private
		stack : array[0..stackSize] of variant;
		data : array of variant;
		pc, sp, bp : integer;

		clockTick : integer;

		owner : TObject;
    	block : GCodeBlock;

	protected
		procedure callMethod(classAddr, methodAddr : pointer; signature : GSignature);

		function findSymbol(const id : string) : integer;
	    procedure setEntryPoint(addr : integer);

		procedure push(v : variant);
		function pop() : variant;

	public
    	procedure load(cb : GCodeBlock);

		procedure run();
		
		function existsSymbol(const id : string) : boolean;
		function runSymbol(const id : string; params : array of variant) : boolean;
		function getResult() : variant;
		
		constructor Create(owner : TObject);
		destructor Destroy(); override;
	end;


var
	cmdline : string;
	input : file;
	vmError : GVMError;
	systemTrap : GSystemTrap;
	externalTrap : GExternalTrap;
	signalTrap : GSignalTrap;
	waitTrap : GWaitTrap;
	externalMethods : GHashTable;
	codeCache : GHashTable;


function loadCode(const fname : string) : GCodeBlock;

procedure setVMError(method : GVMError);
procedure setSystemTrap(method : GSystemTrap);
procedure setExternalTrap(method : GExternalTrap);
procedure setSignalTrap(method : GSignalTrap);
procedure setWaitTrap(method : GWaitTrap);

procedure registerExternalMethod(const name : string; classAddr, methodAddr : pointer; const signature : GSignature); overload;
procedure registerExternalMethod(const name : string; classAddr : TObject; resultType : integer; paramTypes : array of integer); overload;


implementation


uses 
	fsys,
	console,
	gasmdef;


procedure dummyError(owner : TObject; const msg : string);
begin
	writeConsole('Fatal VM error: ' + msg, 0);
end;

procedure dummySystemTrap(owner : TObject; const msg : string);
begin
	writeConsole('Trap: ' + msg, 0);
end;

function dummyExternalTrap(obj : variant; const member : string) : variant;
begin
  Result := Null;
end;

procedure dummySignalTrap(owner : TObject; const signal : string);
begin
end;

function dummyWaitTrap(owner : TObject; const signal : string) : boolean;
begin
  Result := True;
end;

function loadCode(const fname : string) : GCodeBlock;
var
  cb : GCodeBlock;
  input : file;
  sym : GSymbol;
  t : byte;
begin
  Result := nil;
  cb := GCodeBlock(codeCache.get(fname));

  if (cb = nil) then
    begin
    cb := GCodeBlock.Create;
    cb.symbols := GHashTable.Create(128);
    
    codeCache.put(fname, cb);

    assign(input, translateFileName(fname));
    {$I-}
    reset(input, 1);
    {$I+}

    if (IOResult <> 0) then
      begin
      vmError(nil, 'could not open ' + fname);
      exit;
      end;

    blockread(input, cb.codeSize, 4);
    blockread(input, cb.dataSize, 4);

    setLength(cb.code, cb.codeSize);

    blockread(input, cb.code[0], cb.codeSize);

    while (not eof(input)) do
      begin
      sym := GSymbol.Create;

      blockread(input, t, 1);
      setLength(sym.id, t);
      blockread(input, sym.id[1], t);
      blockread(input, sym.addr, 4);

      cb.symbols.put(sym.id, sym);
      end;

    closefile(input);
    end;

  Result := cb;
end;

// GContext
constructor GContext.Create(owner : TObject);
begin
	inherited Create();
	
	Self.owner := owner;
end;

destructor GContext.Destroy();
begin
	SetLength(data, 0);
	
	inherited Destroy();
end;

function GContext.findSymbol(const id : string) : integer;
var
  sym : GSymbol;
begin
  Result := -1;

  if (block = nil) then
    exit;

  sym := GSymbol(block.symbols.get(id));

  if (sym <> nil) then
    Result := sym.addr;
end;

procedure GContext.setEntryPoint(addr : integer);
begin
  if (addr >= 0) then
    push(pc);

  pc := addr;
end;

procedure GContext.push(v : variant);
begin
  if (sp > stackSize) then
    vmError(owner, 'data stack overflow');

  inc(sp);
  stack[sp] := v;
end;

function GContext.pop() : variant;
begin
  if (sp < 0) then
    vmError(owner, 'data stack underflow');

  Result := stack[sp];
  dec(sp);
end;

procedure GContext.load(cb : GCodeBlock);
var
	i : integer;
begin
  sp := -1;
  pc := -1;

  block := cb;
  clockTick := 0;

  if (cb <> nil) then
    begin
    setLength(data, cb.dataSize);

    for i := 0 to cb.dataSize-1 do
      data[i] := 0;
    end
  else
    setLength(data, 0);
end;

procedure GContext.callMethod(classAddr, methodAddr : pointer; signature : GSignature);
var
	i : integer;
  v, vd : variant;
  resstr : string;
begin
  if (methodAddr = nil) then
    exit;
    
  for i := length(signature.ParamTypes) - 1 downto 0 do
    begin
    v := stack[sp - i];

    VarCast(vd, v, signature.ParamTypes[i]);

    case varType(vd) of
      varBoolean: asm
                  xor eax, eax
                  mov ax, vd.TVarData.VBoolean
                  push eax
                  end;
      varInteger: asm
                  mov eax, vd.TVarData.VInteger
                  push eax
                  end;
       varSingle: asm
                  mov eax, vd.TVarData.VSingle
                  push eax
                  end;
       varString: asm
                  mov eax, vd.TVarData.VString
                  push eax
                  end;
    end;
    end;
    
  dec(sp, length(signature.paramTypes));

  asm
    mov eax, classAddr
    test eax, eax
    jz @strresult

    @methodcall:
    push eax

    @strresult:
    mov eax, signature.ResultType
    cmp eax, varString
    jne @call
    lea eax, resstr
    push eax

    @call:
    call methodAddr

    mov edx, signature.ResultType

    cmp edx, varSingle
    je @varSingle

    cmp edx, varInteger
    je @varInteger

    cmp edx, varBoolean
    je @varBoolean

    jmp @end

@varSingle:
    fstp dword ptr vd.TVarData.VSingle
    mov vd.TVarData.VType, varSingle
    jmp @end

@varInteger:
    mov vd.TVarData.VType, varInteger
    mov vd.TVarData.VInteger, eax
    jmp @end

@varBoolean:
    mov vd.TVarData.VType, varBoolean   
    xor ah,ah   
    mov vd.TVarData.VBoolean, ax
    jmp @end

@end:
  end;
  
  if (signature.ResultType = varString) then
    push(resstr)
  else
  if (signature.ResultType <> varEmpty) then
    push(vd);
end;


procedure GContext.run();
var
	i : integer;
  f : single;
  p : pchar;
	v1, v2 : variant;
  meth : GExternalMethod;
begin
  if (block = nil) or (pc < 0) or (pc >= block.codeSize) then
    exit;

  try
    while (pc >= 0) and (pc < block.codeSize) do
    case ord(block.code[pc]) of
      _TOF    : begin
                VarCast(v1, pop(), varSingle);
                push(v1);
                inc(pc);
                end;
      _TOI    : begin
                VarCast(v1, pop(), varInteger);
                push(v1);
                inc(pc);
                end;
      _TOS    : begin
                VarCast(v1, pop(), varString);
                push(v1);
                inc(pc);
                end;
      _PUSHI 	: begin
                move(block.code[pc + 1], i, 4);
                inc(pc, 5);
                push(i);
                end;
      _PUSHF  : begin
                move(block.code[pc + 1], f, 4);
                inc(pc, 5);
                push(f);
                end;
      _PUSHS  : begin
                p := @block.code[pc + 1];
                inc(pc, strlen(p) + 2);

                push(string(p));
                end;
       _PUSHR : begin
                move(block.code[pc + 1], i, 4);
                inc(pc, 5);
                push(data[i]);
                end;
        _POPR : begin
                move(block.code[pc + 1], i, 4);
                inc(pc, 5);
                data[i] := pop();
                end;
    _PUSHDISP : begin
                move(block.code[pc + 1], i, 4);
                inc(pc, 5);
                push(stack[bp + i]);
                end;
     _POPDISP : begin
                move(block.code[pc + 1], i, 4);
                inc(pc, 5);
                stack[bp + i] := pop();
                end;
      _ADD		: begin
                v2 := pop();
                v1 := pop();
                push(v1 + v2);
                inc(pc);
                end;
      _SUB		: begin
                v2 := pop();
                v1 := pop();
                push(v1 - v2);
                inc(pc);
                end;
      _MUL		: begin
                v2 := pop();
                v1 := pop();
                push(v1 * v2);
                inc(pc);
                end;
      _DIV		: begin
                v2 := pop();
                v1 := pop();
                push(v1 / v2);
                inc(pc);
                end;
      _NOT    : begin
                push(not pop());
                inc(pc);
                end;
      _AND    : begin
                push(pop() and pop());
                inc(pc);
                end;
      _OR     : begin
                push(pop() or pop());
                inc(pc);
                end;
      _LT     : begin
                v2 := pop();
                v1 := pop();
                push(v1 < v2);
                inc(pc);
                end;
      _GT     : begin
                v2 := pop();
                v1 := pop();
                push(v1 > v2);
                inc(pc);
                end;
      _LTE    : begin
                v2 := pop();
                v1 := pop();
                push(v1 <= v2);
                inc(pc);
                end;
      _GTE    : begin
                v2 := pop();
                v1 := pop();
                push(v1 >= v2);
                inc(pc);
                end;
      _EQ     : begin
                v2 := pop();
                v1 := pop();
                push(v1 = v2);
                inc(pc);
                end;
      _GET		: begin
                v2 := pop();
                v1 := pop();
                push(externalTrap(v1, v2));
                inc(pc);
                end;
      _TRAP		: begin
                systemTrap(owner, pop());
                inc(pc);
                end;
      _SLEEP  : begin
                v1 := pop();

                if (v1 <= 0) then
                  inc(pc)
                else
                  begin
                  push(v1 - 1);
                  break;
                  end;
                end;
      _WAIT   : begin
                v1 := pop();

                if (not waitTrap(owner, v1)) then
                  begin
                  push(v1);
                  break;
                  end
                else
                  inc(pc);
                end;
      _SIGNAL : begin
                v1 := pop();
                inc(pc);
                signalTrap(owner, v1);
                end;
      _RET		: begin
                i := pop();
                pc := i;
                end;
      _CALL 	: begin
                move(block.code[pc + 1], i, 4);

                if (i < 0) or (i > block.codeSize) then
                  vmError(owner, 'procedure call outside of boundary');

                push(pc + 5);					// save return address
                pc := i;
                end;
      _CALLE  : begin
                p := @block.code[pc + 1];
                inc(pc, strlen(p) + 2);

                meth := GExternalMethod(externalMethods.get(string(p)));

                if (meth <> nil) then
                  callMethod(meth.classAddr, meth.methodAddr, meth.signature)
                else
                  vmError(owner, 'unregistered external method "' + p + '"');
                end;
      _JMP    : begin
                move(block.code[pc + 1], i, 4);

                if (i < 0) or (i > block.codeSize) then
                  vmError(owner, 'jump outside of boundary');

                pc := i;
                end;
      _JZ     : begin
                move(block.code[pc + 1], i, 4);
                v1 := pop();

                if (i < 0) or (i > block.codeSize) then
                  vmError(owner, 'jump outside of boundary');

                if (integer(v1) = 0) then
                  pc := i
                else
                  inc(pc, 5);
                end;
      _JNZ    : begin
                move(block.code[pc + 1], i, 4);
                v1 := pop();

                if (i < 0) or (i > block.codeSize) then
                  vmError(owner, 'jump outside of boundary');

                if (integer(v1) <> 0) then
                  pc := i
                else
                  inc(pc, 5);
                end;
      _PUSHBP : begin 
                push(bp);
                inc(pc);
                end;
       _POPBP : begin 
                bp := pop();
                inc(pc);
                end;
       _MBPSP : begin
                sp := bp;
                inc(pc);
                end;
       _MSPBP : begin
                bp := sp;
                inc(pc);
                end;
       _ADDSP : begin
                move(block.code[pc + 1], i, 4);
                inc(sp, i);
                inc(pc, 5);
                end;
       _SUBSP : begin
                move(block.code[pc + 1], i, 4);
                dec(sp, i);
                inc(pc, 5);
                end;
        _MTSD : begin
                move(block.code[pc + 1], i, 4);
                stack[sp - i] := stack[sp];
                inc(pc, 5);
                end;
      	_HALT : pc := block.codeSize;
    else
      inc(pc);
    end;
  except
    on E : Exception do
      begin
      vmError(owner, 'stack error: ' + E.Message);

      // reset state, stop program
      sp := -1;
      bp := 0;
      pc := -1;
      end;
  end;
end;

function GContext.existsSymbol(const id : string) : boolean;
begin
	Result := (findSymbol(id) <> -1);
end;

function GContext.runSymbol(const id : string; params : array of variant) : boolean;
var
	addr : integer;
	i : integer;
begin
	Result := false;
	
	addr := findSymbol(id);
	
	if (addr <> - 1) then
		begin
		// parameters in params are left to right, we need to push them in reverse
		for i := length(params) - 1 downto 0 do
			push(params[i]);
			
		setEntryPoint(addr);
		run();
		
		Result := true;
		end;
end;

function GContext.getResult() : variant;
begin
	Result := pop();
end;

procedure setVMError(method : GVMError);
begin
  if (Assigned(method)) then
    vmError := method;
end;

procedure setSystemTrap(method : GSystemTrap);
begin
  if (Assigned(method)) then
    systemTrap := method;
end;

procedure setExternalTrap(method : GExternalTrap);
begin
  if (Assigned(method)) then
    externalTrap := method;
end;

procedure setSignalTrap(method : GSignalTrap);
begin
  if (Assigned(method)) then
    signalTrap := method;
end;

procedure setWaitTrap(method : GWaitTrap);
begin
  if (Assigned(method)) then
    waitTrap := method;
end;

procedure registerExternalMethod(const name : string; classAddr, methodAddr : pointer; const signature : GSignature);
var
	meth : GExternalMethod;
begin
  meth := GExternalMethod.Create;

  meth.name := name;
  meth.classAddr := classAddr;
  meth.methodAddr := methodAddr;
  meth.signature := signature;

  externalMethods.put(name, meth);
end;

procedure registerExternalMethod(const name : string; classAddr : TObject; resultType : integer; paramTypes : array of integer);
var
	sig : GSignature;
	x : integer;
begin
	sig.resultType := resultType;
	
	SetLength(sig.paramTypes, Length(paramTypes));
	
	for x := 0 to High(paramTypes) do
		sig.paramTypes[x] := paramTypes[x];
	
	registerExternalMethod(name, classAddr, classAddr.MethodAddress(name), sig);
end;

initialization
	DecimalSeparator := '.';

	setVMError(dummyError);
	setSystemTrap(dummySystemTrap);
	setExternalTrap(dummyExternalTrap);
	setSignalTrap(dummySignalTrap);
	setWaitTrap(dummyWaitTrap);

	codeCache := GHashTable.Create(128);
	externalMethods := GHashTable.Create(256);

finalization
	codeCache.Clear();
	codeCache.Free();
	
	externalMethods.Clear();
	externalMethods.Free();
	
end.