Skip to content

Come usare l'analizzatore Dart per generare un AST dal sorgente e lavorare con l'AST?

Ti consigliamo di testare questa risoluzione in un ambiente controllato prima di inviarla alla produzione, saluti.

Soluzione:

Wiki

Nota: gli esempi di codice pubblicati qui non sono di grande qualità.
Dovrebbero esserci modi più succinti ed efficienti, anche se finora non sono riuscito a trovarli.

Per ulteriori dettagli, cercare dart-sdk per alcuni nomi di funzioni/classi su github.
Questi sono stati utili:
https://github.com/dart-lang/sdk/blob/dd0c42cb72a4c218e4b04890f0ef666c6f6c0eb6/pkg/analyzer/test/dart/ast/visitor_test.dart
https://github.com/dart-lang/sdk/blob/71e7ed86c060ff2d695777d2c68c20e25cfb8ef9/pkg/analysis_server/lib/src/services/correction/util.dart
https://github.com/dart-lang/sdk/blob/6715573c6eb5e5a3c2b8257fef02e88a7d707d9f/pkg/analyzer/lib/src/generated/incremental_resolver.dart

Per AstVisitor (ce ne sono diversi):https://www.dartdocs.org/documentation/analyzer_experimental/0.8.0/analyzer/RecursiveASTVisitor-class.html

Come ha detto Günter Zöchbauer, si tratta di un lavoro in corso e scarsamente documentato, mentre è indispensabile se si scrive un trasformatore per la modifica del codice in tempo di compilazione.

#Aggiornamento per la nuova versione

import 'package:analyzer/dart/analysis/utilities.dart';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/ast/ast.dart';

var ast = parseFile('./lib/mistletoe.dart', featureSet: FeatureSet.fromEnableFlags([])).unit;
...

#Codice di esempio

Visualizzazione dell'albero

import 'package:analyzer/analyzer.dart';
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/scanner.dart';
import 'dart:io';
//code to parse
String src = r"""
import 'package:mistletoe/mistletoe.dart';
var o = new Object();
main(){
  f(o);
}
f(p){
  var o = p;
  o = o as Object;
  print(o);
}
""";

main() async {
//  var src = await new File('./lib/mistletoe.dart').readAsString();
  var ast = parseCompilationUnit(src
    ,parseFunctionBodies: true);
  var nodes = flatten_tree(ast);
  var types ={};
  for(var n in nodes){
    types[n.runtimeType] ??= [];
    types[n.runtimeType].add(n);
  }
  var data = [];
  for(var k in types.keys){
    data.add(k.toString());
    for(var e in types[k]){
      data.add('t'+e.toString());
    }
  }
  data = data.join('n');
  print(data);
//  await new File('./lib/node_samples.txt').writeAsString(data);
}

List flatten_tree(AstNode n,[int depth=9999999]){
  var que = [];
  que.add(n);
  var nodes = [];
  int nodes_count = que.length;
  int dep = 0;
  int c = 0;
  if(depth == 0) return [n];
  while(que.isNotEmpty){
    var node = que.removeAt(0);
    if(node is! AstNode) continue;
    for(var cn in node.childEntities){
      nodes.add(cn);
      que.add(cn);
    }
    //Keeping track of how deep in the tree
    ++c;
    if(c == nodes_count){
      ++ dep; // One layer done
      if(depth <= dep) return nodes;
      c = 0;
      nodes_count = que.length;
    }
  }
  return nodes;
}
show(node){
  print('Type: ${node.runtimeType}, body: $node');
}

Modifica del nodo

d.on(o).hi = 'bye' a d.on(o).set("hi", 'bye')

import 'package:analyzer/analyzer.dart';
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/error.dart';
import 'package:analyzer/src/generated/parser.dart';
import 'package:analyzer/src/generated/scanner.dart';
String src = """
  Dynamism d = new Dynamism(expert:true);
main(){
  var o = new Object();
  d.on(o).hi = 'bye';
}
""";
main(){
  var ast = parseCompilationUnit(src,parseFunctionBodies: true);
  print('initial value: ');
  print(ast.toSource());
  var v = new Visitor();
  ast.visitChildren(v);
  print('After modification:');
  print(ast.toSource());
}
class Visitor extends RecursiveAstVisitor{
  @override
  visitAssignmentExpression(AssignmentExpression node){
    //filter
    var p = new RegExp(r'.*.on(w)');
    if(!p.hasMatch(node.toString())) return;

    //replace
    SimpleStringLiteral ssl =
    _create_SimpleStringLiteral(node);
    node.parent.accept(new NodeReplacer(node,ssl));
  }
}

SimpleStringLiteral _create_SimpleStringLiteral(AstNode node){
  String new_string = modify(node.toString());
  int line_num = node.offset;
  //holds the position and type
  StringToken st = new StringToken(
      TokenType.STRING,new_string,line_num);
  return new SimpleStringLiteral(st, new_string);
}
String modify(String s){
  List parts = s.split('=');
  var value = parts[1];
  List l = parts[0].split('.');
  String dynamism = l.sublist(0,l.length-1).join('.');
  String propertyName = l.last.trim();
  return '${dynamism}.set("${propertyName}",${value})';
}

Modifica della parola chiave: var o = new Object(); a Object o = new Object():

Nota: la modifica di un elenco di VariableDeclarationList è più difficile della modifica di altri nodi, per qualche motivo.

import 'package:analyzer/analyzer.dart';
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/scanner.dart';

//code to parse
String src = r"""
var d = new Dynamism(expert: true);
var o = new Object();
void main() {
  var e = new Object();
}
""";
main(){
  var result = var_to_object(src);
  print(result);
}
String var_to_object(String s){
  var ast = parseCompilationUnit(
      s,parseFunctionBodies: true);

// list imports
//  var directives = ast.directives;
//  print(directives);

  var v = new Visitor();
  ast.visitChildren(v);
  return ast.toSource();
}
class Visitor extends RecursiveAstVisitor{
  _is_type_Object(n){
    for( var c in n.childEntities){
      if(c is ConstructorName){
        if(c.toString() == 'Object')
          return true;
      }
      if(c is VariableDeclarationList)
        return _is_type_Object(c);
      if(c is VariableDeclaration)
        return _is_type_Object(c);
      if(c is InstanceCreationExpression)
        return _is_type_Object(c);
    }
    return false;
  }
  @override
  visitVariableDeclarationList(VariableDeclarationList n){
    //if n does not contain new Dynamism(expert:true) return;
    if(!_is_type_Object(n)) return;

//    Some note on the Keyword class
//     pseudo keywords are keywords that can be used as identifiers.
//     e.g. 
//     const Keyword("await", isPseudo: true),
//     const Keyword("yield", isPseudo: true)];

// syntax arguments samples
//    static const Keyword LIBRARY = const Keyword._('LIBRARY', "library", true);
//    static const Keyword NEW = const Keyword._('NEW', "new");
//    static const Keyword NULL = const Keyword._('NULL', "null");
//    static const Keyword OPERATOR = const Keyword._('OPERATOR', "operator", true);

    // name and syntax seems to have been switched around
    var kw = const Keyword('class','Object', false);
    var kt = new KeywordToken(kw,n.keyword.offset);
    var ndl = new VariableDeclarationList(
        n.documentationComment,[],kt,
        n.type,n.variables);
    //changing var d to Object d
    n.parent.accept(new NodeReplacer(n,ndl));
  }
}

Trova la dichiarazione/assegnazione/parametro formale/nome del tipo più vicino a una data variabile

Nota: il codice probabilmente contiene ancora dei bug. Dovrei riorganizzare il codice, ma ora ho poco tempo e me ne vado come ho prototipato.

import 'package:analyzer/analyzer.dart';
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/scanner.dart';

String src = r"""
final String a = 'a';
class A{
  static Dynamism d = new Dynamism(expert:true);
  A(a){
    a = 'changed';
    print(a);
    var o = new Object();
    o = a as String;
    print(o);
  }
}""";

main() async{
  var ast = parseCompilationUnit(
      src,parseFunctionBodies: true);
  //Get all SimpleIdentifier nodes in ast
  var simple_nodes = new List();
  for(var n in flatten_tree(ast)){
    if(n is SimpleIdentifier){
      simple_nodes.add(n);
    }
  }
  for(SimpleIdentifier n in simple_nodes){
    var a = guess_effective_assignment_of(n);
    var d = get_declaration_of(n);
    var f = get_formal_parameter_of(n);
    if(a == null && d == null && f == null)
      continue;
    print('------------');
    print('For $n at: ${n.offset} in the node:'
        '${n.parent.parent}');
    print('The effective definition is:');
    var definitions = guess_effective_definition_of(n);
    var first_candidate = definitions.first;
    var second_candidate = definitions.second;
    var third_candidate = definitions.third;
    print('t`${first_candidate}` of type '
        '${first_candidate.runtimeType}');
    print('tSecond candidate is `${second_candidate}`');
    print('tThird candidate is `${third_candidate}`');
    print('Where the enclosing block is:');
    print('t${get_surrounding_block(n)}');
    var type = get_type_name(n);
    print('type or class name:$type');
  }

}
AstNode get_surrounding_block(node){
  if(node == null) return null;
  while(true){
    if(node.parent == null) return null;
    node = node.parent;
    if(is_scope(node))
      return node;

  }
}
String get_type_name(SimpleIdentifier node){
  Definitions definitions =
    guess_effective_definition_of(node);
  var g = definitions.first;
  //Takes only SimpleFormalParameter or Declaration
  TypeName _extract_TypeName_from(d){
    if(d == null) return null;
    for(var t in d.childEntities)
      if(t is TypeName) return t;
  }
  var name;
  if(g is VariableDeclaration){
    name = _extract_TypeName_from(g);
    if(name == null || name == 'var'){
      name = extract_constructor_name_or_rvalue(g);
      //Not supporting MethodInvocation
      if(name is MethodInvocation)
        return null;

      //rvalue is a variable. calling self.
      if(name is SimpleIdentifier)
        return get_type_name(name);
    }
  }
  if(g is AssignmentExpression){
    // checking FormalParameter or
    // VariableDeclaration for type
    var closer = definitions.second;
    var type_name = _extract_TypeName_from(closer);
    if(type_name != 'var' && type_name != null)
      return type_name.toString();
    //TypeName is var

    for(var e in g.childEntities){
      if(e is AsExpression){
        return e.childEntities.last.toString();
      }
    }
    name = extract_constructor_name_or_rvalue(g);
    //Not supporting MethodInvocation
    if(name is MethodInvocation)
      return null;

    //rvalue is a variable. calling self.
    if(name is SimpleIdentifier)
      return get_type_name(name);
    //A literal is handled later
  }
  if(g is FormalParameter){
    if(g.childEntities.length>1){
      name = _extract_TypeName_from(g);
    }else{
      name = 'var';
    }
  }

  if(name is Literal){
    name = name.runtimeType.toString();
    name = name.replaceAll('Simple','')
        .replaceAll('Literal','');
  }else{
    name = name.toString();
  }
  return name;
}

///finds the variable declaration for the given node
///Takes SimpleIdentifier representing a variable
List get_declaration_of(var n){
  // Check if n is part of VariableDeclaration
  // and is the variable being defined.
  var r = new List(2);
  var original = n;
  while(n.parent !=null){
    if(n is VariableDeclaration)
      if(n.childEntities.first == original){
        r[0]=n;r[1]=0;
        return r;
      }else{
        //n is not the variable being defined
        break;
      }
    n = n.parent;
  }
  n = original;

  // The variable's identifier is defined up the lines
  // or in outer scope.

  //searching local scope
  var block = get_surrounding_block(n);
  var declarations = extract_scope_wide_declarations(block);
  for(var d in declarations) {
    if(d.offset > n.offset) break;
    var v = d?.childEntities?.first;
    if(v == original){
      r[0]=d;r[1]=0;
      return r;}
  }

  //searching the outer scopes
  block = get_surrounding_block(block);
  declarations.clear();
  int count = -1;
  while(block != null){
    var decls = extract_scope_wide_declarations(block);
    for(var d in decls) {
      var v = d.childEntities.first;
      if(v.toString() == original.toString()){
        r[0]=d;r[1]=count;
        return r;
      }
    }
    block = get_surrounding_block(block);
    --count;
  }
  return null;//could not find the declaration
}
///Fetch declarations that have effect in
///all the children blocks of the given node.
///Does not cover FormalParameterList.
///Does not enter a node that is a block.
List extract_scope_wide_declarations(AstNode node){
  var nodes = [];
  var declarations = [];
  nodes.addAll(node.childEntities);

  //pushing more nodes and skipping blocks
  while(nodes.isNotEmpty){
    var e = nodes.removeAt(0);
    if(e is! AstNode) continue;
    if(e is VariableDeclaration){
      declarations.add(e);
      continue;
    }
    if(!is_scope(e) && e is AstNode){
        nodes.addAll(e.childEntities);
    }
  }
  return declarations;
}
///Returns true if the given node has its
///scope.
///Returns also true for:
/// MethodDeclaration
/// FunctionDeclaration
/// CompilationUnit
/// ClassDeclaration
///
///as they have a scope.
bool is_scope(node){
    if(node is Block||
        node is CompilationUnit ||
        node is Block||
        //below are for extracting arguments
        node is MethodDeclaration ||
        node is FunctionDeclaration ||
        node is ConstructorDeclaration||
        //FieldDeclarations
        node is ClassDeclaration ||
        //Block should cover these but jus in case
        node is IfStatement ||
        node is WhileStatement||
        node is DoStatement ||
        node is TryStatement ||
        node is SwitchStatement) return true;
  return false;
}

/// Takes a VariableDeclaration node.
/// Returns a ConstructorName node or
/// rvalue; includes MethodInvocation.
/// todo write test
extract_constructor_name_or_rvalue(n){
  var nodes = flatten_tree(n,1);
  for(var node in nodes){
    if(node is Literal) return node;
    if(node is TypeName){
      return node;
    }
    if(node is InstanceCreationExpression)
      for(var e in node.childEntities)
        if(e is ConstructorName) return e;
    if(node is MethodInvocation){
      return node;
    }
  }
  //rvalue is a variable
  return nodes.last;
}

/// Returns the closest definition of n.
/// sub blocks are ignored.
///
///  A guess because it would fail
/// if a conditional modifies the value
/// of the variable in runtime.
///
/// e.g.
///
///     var a = 'hi';
///     if(user_input){
///       a = new Object();
///     }
///     //a is an Object not String
///
/// also does not look into constructor.
///
/// todo write test
Definitions guess_effective_definition_of(n){
  List al = guess_effective_assignment_of(n);
  List dl = get_declaration_of(n);
  List fl = get_formal_parameter_of(n);

  if(al == null && dl == null && fl == null )
    return null;

  if(al == null && fl == null && dl != null)
    return new Definitions()
      ..first = dl[0]
      ..first_relative_depth=dl[1]
      ..length = 1;

  if(dl == null && fl == null && al != null)
    return new Definitions()
      ..first = al[0]
      ..first_relative_depth = al[1]
      ..length = 1;

  if(fl != null && al == null && dl == null)
    return new Definitions()
      ..first = fl[0]
      ..first_relative_depth = fl[1]
      ..length = 1;;

  Definitions r = new Definitions();
  // Guessing which declaration or
  // definition takes precedence.
  var l = []..add(dl)..add(al)..add(fl);
  l.removeWhere((e)=>e==null);

  // 0 means local scope.
  // -1 means one scope up.
  //Comparator returns negative if a comes before b,
  //0 if equal, positive if a comes after b.
  l.sort((a,b)=> b[1] - a[1]);
  // if AssignmentExpression and
  // VariableDeclaration are
  // in the same scope,
  // AssignmentExpression always
  // takes precedence.
  l.sort((a,b)=>
    a == al && b == dl && al[1] == dl[1] ?
      -1:0
  );
  //maybe I should just return a list?
  try {
    r.length = l.length;
    r.first = l[0][0];
    r.first_relative_depth = l[0][1];
    r.second = l[1][0];
    r.second_relative_depth = l[1][1];
    r.third = l[2][0];
    r.third_relative_depth = l[2][1];
  }on RangeError catch(e){
    // do nothing
  }
  return r;
}
/// Searches for an AssignmentExpression
/// that is most likely to define the value
/// of the variable denoted by the identifier
/// in n.
///
///  A guess because it would fail
/// if a conditional modifies the value
/// of the variable on runtime.
///
/// e.g.
///
///     var a = 'hi';
///     if(user_input){
///       a = new Object();
///     }
///     //a is an Object not String
///     //but this function does not
///     //know that.
///
guess_effective_assignment_of(SimpleIdentifier n){
  //checks if n is part of a variable declaration
  //Code is mostly duplicate of get_declaration_of
  // AssignmentExpression does not contain
  // VariableDeclaration or vice versa.
  var r = new List(2);
  var node = n;
  while(node.parent !=null){
    if(node is AssignmentExpression)
      if(node.childEntities.first == n){
        r[0]=node;r[1]=0;
        return r;
      }else{
        //n is not the variable being defined
        break;
      }
    node = node.parent;
  }

  //Search local scope
  var b = get_surrounding_block(n);
  AstNode closest;
  for(var node in extract_assignments_to_n_from(n, b)){
    if(n.offset > node.offset){
      closest = node;
    }else{break;}
  }
  if(closest != null){
    r[0]=closest;r[1]=0;
    return r;
  }
  //outer scopes
  b = get_surrounding_block(b);
  int count = -1;
  while(b != null){
    for(var a in extract_assignments_to_n_from(n,b)) {
      var v = a?.childEntities?.first;
      if(v.toString() == n.toString()){
        r[0]=a;r[1]=count;
        return r;
      }
    }
    b = get_surrounding_block(b);
    --count;
  }
  return null;
}

///Searches scopes upward for an
///AssignmentExpression for the
///variable denoted by the identifier
///in n.
///
/// Returns a list of AssignmentExpression.
/// todo test this
extract_assignments_to_n_from(
    SimpleIdentifier n,
    AstNode in_scope,
    [int search_depth=2]){
  var nodes = flatten_tree(in_scope,search_depth)
      .where((e)=>e is AssignmentExpression);
  var r = [];
  for(AstNode node in nodes){
    String i = node.childEntities.first.toString();
    if(i == n.toString()){
      r.add(node);
    }
  }
  return r;
}
///Takes any node.
///
///Searches scopes upward to find
///the closest FormalParameterList
///
/// Returns a list of
///   1.  FormalParameterList
///   2.  Its depth relative to n
///
/// Or null.
///
List get_nearest_formal_parameter_list(n){
  var r = new List(2);
  int count = 0;
  while(true){
    if(n is FunctionDeclaration ||
        n is MethodDeclaration ||
        n is ConstructorDeclaration)
      break;
    n = get_surrounding_block(n);
    --count;
    if(n == null) return n;
  }
  for(var e in n.childEntities){
    if(e is FormalParameterList){
      r[0] = e;r[1] = count;
      return r;
    }
  }
  //empty FormalParameterList
  return null;
}
///Searches up for a FormalParameterList.
///
///Returns a list of:
///
///  1. FormalParameter matching
///  the identifier of n when stringified
///  if such exists. Returns null
///  otherwise.
///
///   2.  The number of scopes moved up
///   from the scope n belongs to.
///
get_formal_parameter_of(n){
  var r = new List(2);

  //returns [FormalParameterList, int]
  var fl = get_nearest_formal_parameter_list(n);

  if( fl == null) return null;
  List names = extract_arg_names(fl[0]);
  for(var name in names){
    if(name.toString() == n.toString()){
      r[0]=name.parent;
      r[1]=fl[1];
      return r;
    }
  }
  return null;
}
///Takes:
///FunctionDeclaration
///FunctionExpression
///FormalParameterList
///
///Returns:
///Positional argument names :e.g. a and b in `f(a,b){return a+b;}`
///Named option names:e.g. your_name in `f({String your_name}){...}`
///
List extract_arg_names(AstNode n){
  var fpl;
  if(n is! FormalParameterList){
    for(var e in flatten_tree(n)){
      if(e is FormalParameterList){
        fpl = e;
        break;
      }
    }
  }else if(n is FormalParameterList){
    fpl = n;
  }
  var r = [];
  for(var c in fpl.childEntities){
    if(c is! FormalParameter)
      continue;
    for(var cc in c.childEntities){
      //filtering String int etc
      if(cc is! TypeName) r.add(cc);
    }
  }
  return r;
}
List flatten_tree(AstNode n,[int depth=9999999]){
  var que = [];
  que.add(n);
  var nodes = [];
  int nodes_count = que.length;
  int dep = 0;
  int c = 0;
  if(depth == 0) return [n];
  while(que.isNotEmpty){
    var node = que.removeAt(0);
    if(node is! AstNode) continue;
    for(var cn in node.childEntities){
      nodes.add(cn);
      que.add(cn);
    }
    //Keeping track of how deep in the tree
    ++c;
    if(c == nodes_count){
      ++ dep; // One layer done
      if(depth <= dep) return nodes;
      c = 0;
      nodes_count = que.length;
    }
  }
  return nodes;
}
show(node){
  print('Type: ${node.runtimeType}, body: $node');
}
//todo test this: could not tokenize a node completely
class FlattenVisitor extends BreadthFirstVisitor{
  List _nodes;
  FlattenVisitor(this._nodes):super();
  @override
  visitNode(AstNode n){
    _nodes.add(n);
  }
}

/// Returned by guess_effective_definition_of
///
/// Confusing but a Definitions instance may
/// include `var a;`; a declaration but also
/// defining the type of a as dynamic.
///
/// Having moved upward to find the definition/
/// declaration results in a negative depth.
/// If definition/declaration is found locally,
/// depth is set to 0.
///
class Definitions{
  AstNode first;
  int first_relative_depth;
  AstNode second;
  int second_relative_depth;
  AstNode third;
  int third_relative_depth;
  int length;
}

Il codice dell'analizzatore è ancora in corso d'opera. Credo che la rielaborazione del codice Dart generato automaticamente dai primi sorgenti Java non sia ancora terminata. Stanno lavorando per migliorare l'API pubblica e poi lavoreranno anche su una documentazione migliore.

Senza aver ancora dato un'occhiata da vicino, penso che anche questi pacchetti dovrebbero farne uso

  • https://github.com/dart-lang/linter
  • https://github.com/dart-lang/dart_style
  • https://github.com/dart-lang/source_gen
  • https://github.com/dart-lang/dartdoc

Vi mostriamo i commenti e le valutazioni degli utenti

Ricorda che hai la possibilità di chiudere la tua esperienza se hai trovato il tuo puzzle.


Tags : /

Utilizzate il nostro motore di ricerca

Ricerca
Generic filters

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.