Skip to content

Analysing a Repository

Benedict Albrecht edited this page Jun 2, 2026 · 2 revisions

Analysing a Repository

A step-by-step guide to writing a Crodox grammar for your own repository and interpreting the results. This page uses the Basics grammar and the Test_Repo as a worked example, but the process applies to any language or project.

Work in progress: This guide is based on the Basics grammar, which is not complete. It is a starting point meant to be extended and adapted to your needs.


Step 1 - Study the Source Code

Before writing any grammar, look at the files in your repository and identify the patterns:

  • What file types are in the project?
  • What constructs appear? (classes, functions, imports, variables, etc.)
  • How do constructs nest? (functions inside classes, variables inside functions)
  • How do files reference each other? (imports, includes, require)

Example: Test_Repo

testFile.txt
  import { C2 } from './bridgeTest2.txt';
  let aaa ;
  class C1 (a , b , c ) {
      function F1 ( V1 ) { let xxx = zzz; }
  }
  C1.F1(aaa);

Patterns identified:

Pattern Example Construct
import { X } from 'file' import { C2 } from './bridgeTest2.txt' Import (cross-file link)
class X (...) { ... } class C1 (a , b , c ) { ... } Class with parameters
function X (...) { ... } function F1 ( V1 ) { ... } Function with parameters
let X ; or let X = Y ; let xxx = zzz; Variable declaration
X.Y(...) C1.F1(aaa) Method call (reference)

Step 2 - Define the File Scope

Start your grammar with the file definition. This tells Crodox which files to parse and sets up the path variable for cross-file linking:

<~"FROM".txt~>

<~>
  • "FROM" is the path variable - it stores the relative path of each file
  • .txt is the file extension to match

Tip: The path variable name must match between the file definition and any import references. Here both use "FROM".


Step 3 - Define Objects Top-Down

Start with the most common constructs and work inward.

3a - Imports

Imports link files together. The key elements are the imported name and the file path:

<:import:>
    import <| <<name:up>> <||> { <<name:up>> } |> from <<"FROM".txt>> <| ; <||> \n |>
<:>

Decisions made here:

  • <<name:up>> - the imported name is visible to the parent scope (so siblings can use it)
  • <<"FROM".txt>> - creates the cross-file dependency
  • <| <<name:up>> <||> { <<name:up>> } |> - or-statement handles both import X and import { X }
  • <| ; <||> \n |> - import ends with either ; or a newline

3b - Classes

Classes have a name, optional parameters, and a body containing other objects:

<:class:>
    class <<NAME:up>> ( <? <| , <||> <<NAME:down>> |> ?> ) { <-function, class, refference, variable-> }
<:>

Decisions made here:

  • <<NAME:up>> - uppercase (no dependency) with :up scope (visible to parent)
  • <<NAME:down>> - parameters use :down scope (visible inside the class body only)
  • <-function, class, refference, variable-> - typed sub-body listing exactly which object types can appear as children

3c - Functions

Similar structure to classes, but with a wildcard sub-body:

<:function:>
    function <<NAME:up>> ( <? <| , <||> <<NAME:down>> |> ?> ) { <-function_-> }
<:>
  • <-function_-> - the underscore makes this a wildcard sub-body: any object type can appear as children

3d - Variables

Variable declarations, with an optional initializer:

<:variable:>
    let <<NAME>> <? = <<'name'>> ?> ;
<:>
  • <<NAME>> - uppercase so declaring a variable does not create a dependency
  • <<'name'>> - reference variable for the initializer, creates a dependency without overwriting

3e - References (method calls)

Method calls on objects:

<:refference:>
    <<'name'>> . <<![class|function]name!>> ( <? <| , <||> |> <<'name'>> ?> ) ;
<:>
  • <<'name'>> - reference to the object being called on
  • <<![class|function]name!>> - dependency selection limits the reference to class or function types only
  • Parameters use <<'name'>> references with repeat and optional commas

Step 4 - Save and Parse

  1. Copy the complete grammar into the Template tab and click save
  2. Create a Bubble linked to your repository
  3. Wait for parsing to complete

If the parser reports errors, check the Syntax Overview and verify your grammar matches the actual source code patterns.


Step 5 - Read the Object Trees

After parsing, each file shows a tree of recognized objects. For testFile.txt:

FILE (FROM = ./testFile)
 |- import      name = C2  (:up)       FROM = ./bridgeTest2
 |- import      name = F5  (:up)       FROM = ./folder/bridgeTest
 |- variable    NAME = aaa
 |- variable    NAME = bbb
 |- variable    NAME = ccc
 |- class       NAME = C1  (:up)       params: a, b, c (:down)
 |   |- variable    NAME = hhh
 |   |- variable    NAME = zzz
 |   |- function    NAME = F1  (:up)   param: V1 (:down)
 |   |   |- variable    NAME = xxx     ref = 'zzz'
 |   |   |- variable    NAME = yyy
 |   |- function    NAME = F2  (:up)   param: V2 (:down)
 |       |- variable    NAME = zzz     ref = 'bbb'
 |       |- variable    NAME = yyy     ref = 'hhh'
 |- refference  'C1' . ![class|function] F1 ( 'aaa' )
 |- refference  'C1' . ![class|function] F2 ( 'ccc' )
 |- refference  'C2' . ![class|function] F3 ( )
 |- refference  'C2' . ![class|function] F4 ( )

What to check:

  • Are all constructs recognized? If a line is missing, the grammar does not match it
  • Are variable scopes correct? :up names should appear on imports and class/function names
  • Do cross-file links resolve? Import FROM values should match other file paths

Tip: F2(); in bridgeTest2.txt is not parsed because it has no dot - it does not match the refference pattern. Unmatched lines are silently skipped. To capture a new pattern, add a new object to your grammar.


Step 6 - Trace Variable Resolution

Reference variables (<<'name'>>) create dependencies. Trace each one to verify it resolves correctly:

  1. Start at the reference location
  2. Search upward through parent scopes
  3. The first matching variable wins

Example: let xxx = zzz inside function F1

Step Scope checked Found?
1 F1 body No zzz declared here
2 C1 body variable zzz exists

Result: xxx depends on zzz in C1.

Example: let zzz = bbb inside function F2

Step Scope checked Found?
1 F2 body No bbb here
2 C1 body No bbb here
3 File scope variable bbb exists

Result: zzz in F2 depends on bbb at file level.

Shadowing

When a :down parameter has the same name as an outer variable, the parameter shadows the outer one inside that scope. In bridgeTest2.txt, function F4 has parameter eee (:down), which shadows the class-level variable eee inside F4's body.


Step 7 - Verify Cross-file Links

Path variables in imports create file-to-file dependencies:

  1. import matches from './bridgeTest2.txt'
  2. <<"FROM".txt>> extracts path ./bridgeTest2
  3. Crodox finds the file where FROM = ./bridgeTest2
  4. The files are linked - imported names become available

Check that:

  • Every import resolves to an existing file
  • Imported names (:up scope) are used correctly in the importing file
  • The dependency chain makes sense (no circular dependencies you did not intend)

Step 8 - Iterate

Your first grammar will not capture everything. That is expected. Improve it by:

Issue Fix
Missing constructs Add new objects for unmatched patterns
Wrong scope Change the variable scope modifier (:up, :down, :following, etc.)
Too many false matches Make the object pattern more specific (add literal tokens)
Comments confuse parser Add a jump instruction for comment syntax
Unresolved references Check variable types - use <<'name'>> for references, <<NAME>> for declarations

See the full Repo Analysis for the complete parsed output of the test repository.


Quick Reference

Task Grammar element
Match a file type <~"PATH".ext~>
Define a construct <:name:> ... <:>
Allow children <---> or <-type1, type2->
Capture a name <<name>> / <<NAME>>
Create a file link <<"PATH".ext>>
Create a dependency <<'name'>>
Handle alternatives <| a <||> b |>
Handle repetition <? ... ?>
Skip to a token -->> token

Clone this wiki locally