Undeclared identifiers a global variable?


Is there a way to make undeclared identifiers in a script global variables while the scripter is running?
our program, our users use a lot of scripts and with our old scripter
every undeclared identifier was made a global variable so this could be
used in all of their other scripts that follow.
I know about the
option OptionExplicit to enable users to use undeclared identifiers, but
this makes local variables of these identifiers.
To solve this, I
compile a script before I run it and as long as I get the error 'Unknown
identifier or variable is not declared:' I get the name of that
identifier and add it as a global variable and try to compile again
until I get a different error or get no errors at all.
This is time intensive, especially with larger scripts that use a lot of undeclared identifiers.
Is there a better way to do this?


Yes, it's possible. You can use the low-level event "OnUnknownElement" which is raised by the compiler whenever it can't find an identifier. Then you have the opportunity to declare such variable using the common scripter procedures (DefineProp method for example) and ask for the compiler to check again. 

Here is an example:

procedure TMyForm.UnknownElementEvent(Sender: TObject;
  var Context: TElementContext);
  scripter: TatCustomScripter;
  if Sender is TatCustomScripter then
    scripter := TatCustomScripter(sender)

  if SameText(Context.ElementName, 'MyProp') and not Context.HasUpnode then
    scripter.DefineProp('MyProp', tkInteger, GetMyPropProc, nil, nil);
    Context.RepeatChecking := true;

TElementContext is relatively low level information, but I can help you with any specific.

If you want to declare both SomeObject and DoSomething on the fly, then you must of course declare both. You must first call DefineClass when SomeObject is detected, and then call DefineMethod when DoSomething is detected. It’s up to you (of course) to know if SomeObject is a property, a class, a method, etc. TElementContext just gives you some extra information about how the identifier is detected by the parser. Here is a rough explanation:

  TElementContext = record
    HasArgList: boolean;
    HasUpnode: boolean;
    HasSubNode: boolean;
    IsNewClause: boolean;
    Operation: TDataOperation;
    ArgCount: integer;
    IdxCount: integer;
    RepeatChecking: boolean;
    ElementName: string;

HasArgList – the identifier has a list of parameters associated with it (so it’s probably a method/function) 

Hasupnode – the identifier is a member of another identifier (for example, SomeClass.SomeMethod -> SomeClass has no upnode, and SomeMethod has upnode) 

IsNewClause – the identifier is in a basic new clause (e.g. Something = new Identifier) 

Operation – can be doRead (the identifier is in a read expression), doWrite (the identifier is in a write expression), doExec (the identifier is in a calling expression (method, function, etc.)) 

ArgCount – the number of parameters of the identifier 

IdxCount – the number of indexed parameters of the identifier (Identifier[2, 5], for example, has IdxCount = 2) 

ElementName – the identifier itself RepeatChecking – must be set to true if you want the parser to check for the identifier again (in case you have added it manually)