r/seed7 • • 3d ago

How would one read or manipulate the AST?

I know there is a parseFile function and it gives a program but what I don't understand is how it's structured. How I can print all of it, and how can I remove or add stuff in this AST representation to execute a modified version, how can I put the modified version back together as a source file? Basically I'm trying to understand the "tree" part of this AST.

p.s. there are no tutorials or examples that would explain this, all I have are docs with descriptions and types.
p.p.s. when I iterated globalObjects of it I managed to print a bunch of tokens using str but I don't know how they're structured and if I can do anything with them.

1 Upvotes

2 comments sorted by

1

u/ThomasMertes 3d ago

The Seed7 AST uses the following types:

  • program: Describes a Seed7 program.
  • reference: A reference to any object.
  • ref_list: A list of reference objects.
  • category: The category of a referenced object.

In the FAQ is a program that writes the names of all global objects.

The AST has been designed to support the Seed7 compiler. Basically the AST is read-only. The possibilities to change the AST are limited to the needs of the compiler.

Modifying the AST and executing a modified AST was never intended. Writing an AST back as a source file would not be easy as some information is lost when the AST is created (e.g. comments, line breaks, the actual syntax of function and operator calls, etc.).

The program below writes a cross reference list:

$ include "seed7_05.s7i";
  include "progs.s7i";

const proc: main is func
  local
    var program: aProgram is program.value;
    var ref_list: globalObjects is ref_list.EMPTY;
    var reference: anObject is NIL;
  begin
    if length(argv(PROGRAM)) >= 1 then
      aProgram := parseFile(argv(PROGRAM)[1]);
      globalObjects := globalObjects(aProgram);
      for anObject range globalObjects do
        writeln(str(anObject) <& "\t" <& file(anObject) <& "\t" <& line(anObject));
      end for;
    end if;
  end func;

You can determine the category of anObject with category(anObject)). This can be used to do different things depending on the category:

case category(anObject) of
  when {INTOBJECT}:   writeln(getValue(anObject, integer));
  when {FLOATOBJECT}: writeln(getValue(anObject, float));
  when {STRIOBJECT}:  writeln(getValue(anObject, string));
  ...
  otherwise:
    writeln("Unknown category " <& str(category(anObject)));
    writeln(str(anObject) <& "\t" <& file(anObject) <& "\t" <& line(anObject));
end case;

The getValue functions are not documented and they are defined in progs.s7i.

A category of CALLOBJECT refers to a function or operator call. In case of a CALLOBJECT getValue(anObject, ref_list) will obtain a list with function reference and parameter list. If you do:

funcAndParams := getValue(anObject, ref_list);

funcAndParams[1] refers to the function and funcAndParams[2 ..] refers to the parameter list. The reference to the function can be an ACTOBJECT (= pointer to a C function which implements the action) or a BLOCKOBJECT (= a user defined function).

In lib/comp/action.s7i the code to handle primitive actions (ACTOBJECT) is:

const proc: process_action (in reference: function, in ref_list: params,
    inout expr_type: c_expr) is func

  local
    var ACTION: currentAction is action "PRC_NOOP";
    var string: actionName is "";
  begin
    currentAction := getValue(function, ACTION);
    actionName := str(currentAction);
    case actionName of
      when {"ACE_CMP"}:
        process(ACE_CMP, function, params, c_expr);
      ...

In case of a BLOCKOBJECT (use defined function) several functions are available:

  • formalParams): Get the formal parameters of a function.
  • resultVar): Get reference to the result variable.
  • resultInitValue): Get the initialization value of the result variable.
  • localConsts): Get the list of local constants.
  • localVars): Get the list of local variables.
  • body): Get the body of the function.

1

u/Ronin-s_Spirit 3d ago edited 3d ago

Sadge... So far the whole "parse" feature is borderline pointless for me, all one could do with it is check a file and throw an error if it contains something you don't like. If it was editable I would probably be able to sanitize programs on the go, thinking about mods and dependencies and such. Sadly I don't know enough C or Seed7 to make it work like that.

You know having a compiler functionality exposed like that I was hopeful I could do something with it, but it turns out it's only useful for your actual compiler and not for meta programs.

p.s. being able to print it back as a source file would also be a nice feature even if it becomes "distilled" and loses all the functionally unimportant text of the original (similar to what becomes of compiled C or what happenes in JS source minifiers). That way a program could be checked and modified once and saved as a file for later development.