|
| 1 | +# Analyzing student code with the GDScript AST |
| 2 | + |
| 3 | +The app uses a custom version of the Godot engine that exposes the GDScriptParser's parse tree. |
| 4 | + |
| 5 | +## Access |
| 6 | + |
| 7 | +1. Create a `GDScriptErrorChecker` |
| 8 | +2. Call `set_source()` and provide the source code. The code should be a valid, parsable class. |
| 9 | +3. Provided there are no parser, analyzer, or compiler errors, you can then call `get_root_parse_node()`, which returns a `GDClassNode`. |
| 10 | + |
| 11 | +`set_source()` will return `OK` if there are no parser, analyzer, or compiler errors. |
| 12 | + |
| 13 | +If you mean to analyze the parser nodes outside of the scope where you set the checker up, make sure to keep a reference to it. Once the `GDScriptErrorChecker` has its RefCount go to 0, it will clear the parser and all the nodes, and you will get garbage data out of the GDNodes at best. |
| 14 | + |
| 15 | +### Low level access |
| 16 | + |
| 17 | +The most raw way of analyzing student code is to directly access the functions in the root `GDClassNode`, like `get_member(function_name)` to get a `GDFunctionNode`. |
| 18 | + |
| 19 | +See the in-editor documentation for the individual `GDNode` RefCounted objects for the API. |
| 20 | + |
| 21 | +### High level access |
| 22 | + |
| 23 | +The `GDScriptASTAnalyzer` class is available to make accessing data easier without worrying about the API as much. |
| 24 | + |
| 25 | +```gdscript |
| 26 | +var checker = GDScriptErrorChecker.new() |
| 27 | +checker.set_source(class_code) |
| 28 | +var root := _checker.get_root_parse_node() |
| 29 | +var analyzer = GDScriptASTAnalyzer.new(root) |
| 30 | +``` |
| 31 | + |
| 32 | +From there, you can access friendly functions like `analyzer.get_function_named(function_name)` and `analyzer.get_function_parameter_name(function, parameter_index)`. |
| 33 | + |
| 34 | +#### Expression system |
| 35 | + |
| 36 | +If you want to check that the tree has a particular shape and uses particular patterns, you can use the `GDExpr` class' static functions to build a comparator tree. |
| 37 | + |
| 38 | +For example, the expected code is |
| 39 | + |
| 40 | +```gdscript |
| 41 | +func run(): |
| 42 | + var health = 100 |
| 43 | +
|
| 44 | + if health > 5: |
| 45 | + print("health is greater than five.") |
| 46 | +``` |
| 47 | + |
| 48 | +To verify it, I can thus write an expression against it: |
| 49 | + |
| 50 | +```gdscript |
| 51 | +func test_statement_is_true() -> String: |
| 52 | + var run_function := _analyzer.get_function_named("run") |
| 53 | + |
| 54 | + # suites are the bodies of functions, loops, match branches, etc |
| 55 | + if not GDExpr.suite( |
| 56 | + # if |
| 57 | + GDExpr.if_block( |
| 58 | + # health > 5 |
| 59 | + GDExpr.bin_op( |
| 60 | + GDExpr.identifier("health"), |
| 61 | + GDExpr.literal(5), |
| 62 | + GDBinaryOpNode.OP_COMP_GREATER |
| 63 | + ) |
| 64 | + ) |
| 65 | + ).matches(run_function): |
| 66 | + return tr("The comparison is not correct. Did you use the right comparison?") |
| 67 | + return "" |
| 68 | +``` |
0 commit comments