docs: public Rule parseRule(String id, String rule, boolean silent, PipelineClassloader ruleClassLoader) throws ParseException {
          final ParseContext parseContext = new ParseContext(silent);
          final SyntaxErrorListener errorListener = new SyntaxErrorListener(parseContext);
  
          final RuleLangLexer lexer = new RuleLangLexer(new ANTLRInputStream(rule));
          lexer.removeErrorListeners();
          lexer.addErrorListener(errorListener);
  
          final RuleLangParser parser = new RuleLangParser(new CommonTokenStream(lexer));
          parser.setErrorHandler(new DefaultErrorStrategy());
          parser.removeErrorListeners();
          parser.addErrorListener(errorListener);
  
          final RuleLangParser.RuleDeclarationContext ruleDeclaration = parser.ruleDeclaration();
  
  
          // parsing stages:
          // 1. build AST nodes, checks for invalid var, function refs
          // 2. type annotator: infer type information from var refs, func refs
          // 3. checker: static type check w/ coercion nodes
          // 4. optimizer: TODO
  
          WALKER.walk(new RuleAstBuilder(parseContext), ruleDeclaration);
          WALKER.walk(new RuleTypeAnnotator(parseContext), ruleDeclaration);
          WALKER.walk(new RuleTypeChecker(parseContext), ruleDeclaration);
  
          if (parseContext.getErrors().isEmpty()) {
              Rule parsedRule = parseContext.getRules().get(0).withId(id);
              if (ruleClassLoader != null && ConfigurationStateUpdater.isAllowCodeGeneration()) {
                  try {
                      final Class<? extends GeneratedRule> generatedClass = codeGenerator.generateCompiledRule(parsedRule, ruleClassLoader);
                      if (generatedClass != null) {
                          parsedRule = parsedRule.toBuilder().generatedRuleClass(generatedClass).build();
                      }
                  } catch (Exception e) {
                      log.warn("Unable to compile rule {} to native code, falling back to interpreting it: {}", parsedRule.name(), e.getMessage());
                  }
              }
              return parsedRule;
          }
          throw new ParseException(parseContext.getErrors());
      }