sources/ast.c (589 lines of code) (raw):
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
// Assorted definitions for the CQL abstract syntax tree
#define AST_EMIT_DEFS 1
#include "cql.h"
#include "minipool.h"
#include "ast.h"
#include "sem.h"
#include "gen_sql.h"
#include "cg_common.h"
#include "encoders.h"
cql_data_defn( minipool *ast_pool );
cql_data_defn( minipool *str_pool );
cql_data_defn( char *_Nullable current_file );
// Helper object to just hold info in find_attribute_str(...)
typedef struct misc_attrs_type {
  CSTR attribute_name;
  void * context;
  find_ast_str_node_callback str_node_callback;
  bool_t presence_only;
  uint32_t count;
} misc_attrs_type;
cql_noexport void ast_init() {
  minipool_open(&ast_pool);
  minipool_open(&str_pool);
}
cql_noexport void ast_cleanup() {
  minipool_close(&ast_pool);
  minipool_close(&str_pool);
  run_lazy_frees();
}
cql_noexport void ast_set_rewrite_info(int32_t lineno, CSTR filename) {
  yylineno = lineno;
  current_file = (char *)filename;
}
cql_noexport void ast_reset_rewrite_info() {
  yylineno = -1;
  current_file = NULL;
}
cql_noexport bool_t is_ast_num(ast_node *node) {
  return node && (node->type == k_ast_num);
}
cql_noexport bool_t is_ast_int(ast_node *node) {
  return node && (node->type == k_ast_int);
}
cql_noexport bool_t is_ast_str(ast_node *node) {
  return node && (node->type == k_ast_str);
}
cql_noexport bool_t is_ast_blob(ast_node *node) {
  return node && (node->type == k_ast_blob);
}
cql_noexport bool_t is_at_rc(ast_node *node) {
  return is_ast_str(node) && !Strcasecmp("@RC", ((str_ast_node*)node)->value);
}
cql_noexport bool_t is_proclit(ast_node *node) {
  return is_ast_str(node) && !Strcasecmp("@proc", ((str_ast_node*)node)->value);
}
cql_noexport bool_t is_strlit(ast_node *node) {
  return is_ast_str(node) && ((str_ast_node *)node)->value[0] == '\'';
}
cql_noexport bool_t is_id(ast_node *node) {
  return is_ast_str(node) && ((str_ast_node *)node)->value[0] != '\'';
}
cql_noexport bool_t is_id_or_dot(ast_node *node) {
  return is_id(node) || is_ast_dot(node);
}
cql_noexport bool_t is_primitive(ast_node *node) {
  return is_ast_num(node) || is_ast_str(node) || is_ast_blob(node) || is_ast_int(node);
}
cql_noexport bool_t is_proc(ast_node *node) {
  return is_ast_create_proc_stmt(node) || is_ast_declare_proc_stmt(node);
}
cql_noexport bool_t is_region(ast_node *ast) {
  return is_ast_declare_schema_region_stmt(ast) || is_ast_declare_deployable_region_stmt(ast);
}
cql_noexport bool_t is_select_stmt(ast_node *ast) {
  return is_ast_select_stmt(ast) ||
         is_ast_explain_stmt(ast) ||
         is_ast_with_select_stmt(ast);
}
cql_noexport bool_t is_delete_stmt(ast_node *ast) {
  return is_ast_delete_stmt(ast) ||
         is_ast_with_delete_stmt(ast);
}
cql_noexport bool_t is_update_stmt(ast_node *ast) {
  return is_ast_update_stmt(ast) ||
         is_ast_with_update_stmt(ast);
}
cql_noexport bool_t is_insert_stmt(ast_node *ast) {
  return is_ast_insert_stmt(ast) ||
         is_ast_with_insert_stmt(ast) ||
         is_ast_upsert_stmt(ast) ||
         is_ast_with_upsert_stmt(ast);
}
cql_noexport bool_t ast_has_left(ast_node *node) {
  if (is_primitive(node)) {
    return false;
  }
  return (node->left != NULL);
}
cql_noexport bool_t ast_has_right(ast_node *node) {
  if (is_primitive(node)) {
    return false;
  }
  return (node->right != NULL);
}
cql_noexport void ast_set_right(ast_node *parent, ast_node *right)  {
  parent->right = right;
  if (right) {
    right->parent = parent;
  }
}
cql_noexport void ast_set_left(ast_node *parent, ast_node *left) {
  parent->left = left;
  if (left) {
    left->parent = parent;
  }
}
cql_noexport ast_node *new_ast(const char *type, ast_node *left, ast_node *right) {
  Contract(current_file && yylineno > 0);
  ast_node *ast = _ast_pool_new(ast_node);
  ast->type = type;
  ast->left = left;
  ast->right = right;
  ast->lineno = yylineno;
  ast->filename = current_file;
  ast->sem = NULL;
  if (left) left->parent = ast;
  if (right) right->parent = ast;
  return ast;
}
cql_noexport ast_node *new_ast_opt(int32_t value) {
  Contract(current_file && yylineno > 0);
  int_ast_node *iast = _ast_pool_new(int_ast_node);
  iast->type = k_ast_int;
  iast->value = value;
  iast->lineno = yylineno;
  iast->filename = current_file;
  iast->sem = NULL;
  return (ast_node *)iast;
}
cql_noexport ast_node *new_ast_str(CSTR value) {
  Contract(current_file && yylineno > 0);
  str_ast_node *sast = _ast_pool_new(str_ast_node);
  sast->type = k_ast_str;
  sast->value = value;
  sast->lineno = yylineno;
  sast->filename = current_file;
  sast->sem = NULL;
  sast->cstr_literal = false;
  return (ast_node *)sast;
}
cql_noexport ast_node *new_ast_num(int32_t num_type, CSTR value) {
  Contract(current_file && yylineno > 0);
  Contract(value);
  num_ast_node *nast = _ast_pool_new(num_ast_node);
  nast->type = k_ast_num;
  nast->value = value;
  nast->lineno = yylineno;
  nast->filename = current_file;
  nast->sem = NULL;
  nast->num_type = num_type;
  Contract(nast->value);
  return (ast_node *)nast;
}
cql_noexport ast_node *new_ast_blob(CSTR value) {
  Contract(current_file && yylineno > 0);
  str_ast_node *sast = _ast_pool_new(str_ast_node);
  sast->type = k_ast_blob;
  sast->value = value;
  sast->lineno = yylineno;
  sast->filename = current_file;
  sast->sem = NULL;
  sast->cstr_literal = false;
  return (ast_node *)sast;
}
// Get the compound operator name. crash if compound operation integer is
// invalid
cql_noexport CSTR get_compound_operator_name(int32_t compound_operator) {
  CSTR result = NULL;
  switch (compound_operator) {
    case COMPOUND_OP_EXCEPT:
      result = "EXCEPT";
      break;
    case COMPOUND_OP_INTERSECT:
      result = "INTERSECT";
      break;
    case COMPOUND_OP_UNION:
      result = "UNION";
      break;
    case COMPOUND_OP_UNION_ALL:
      result = "UNION ALL";
      break;
  }
  Invariant(result);
  return result;
}
// This converts C string literal syntax into SQL string literal syntax
// the test of the program expects the SQL style literals.  We support
// C style literals largely because they pass through the C pre-processor better.
// Even stuff like the empty string '' causes annoying warnings.  However
// the escaping is lightly different.  Also C string literals have useful escape sequences
cql_noexport CSTR convert_cstrlit(CSTR cstr) {
  CHARBUF_OPEN(decoded);
  CHARBUF_OPEN(encoded);
  cg_decode_c_string_literal(cstr, &decoded);
  cg_encode_string_literal(decoded.ptr, &encoded);
  CSTR result = Strdup(encoded.ptr);
  CHARBUF_CLOSE(encoded);
  CHARBUF_CLOSE(decoded);
  return result;
}
cql_noexport ast_node *new_ast_cstr(CSTR value) {
  value = convert_cstrlit(value);
  str_ast_node *sast = (str_ast_node *)new_ast_str(value);
  sast->cstr_literal = true;
  return (ast_node *)sast;
}
static char padbuffer[4096];
cql_noexport bool_t print_ast_value(struct ast_node *node) {
  bool_t ret = false;
  if (is_ast_str(node)) {
    cql_output("%s", padbuffer);
    if (is_strlit(node)) {
      cql_output("{strlit %s}", ((struct str_ast_node *)node)->value);
    }
    else {
      cql_output("{name %s}", ((struct str_ast_node *)node)->value);
    }
    ret = true;
  }
  if (is_ast_num(node)) {
    cql_output("%s", padbuffer);
    EXTRACT_NUM_TYPE(num_type, node);
    EXTRACT_NUM_VALUE(val, node);
    if (num_type == NUM_BOOL) {
      cql_output("{bool %s}", val);
    }
    else if (num_type == NUM_INT) {
      cql_output("{int %s}", val);
    }
    else if (num_type == NUM_LONG) {
      cql_output("{longint %s}", val);
    }
    else if (num_type == NUM_REAL) {
      cql_output("{dbl %s}", val);
    }
    ret = true;
  }
  if (is_ast_blob(node)) {
    cql_output("%s", padbuffer);
    cql_output("{blob %s}", ((struct str_ast_node *)node)->value);
    ret = true;
  }
  if (is_ast_int(node)) {
    cql_output("%s", padbuffer);
    cql_output("{int %lld}", (llint_t)((struct int_ast_node *)node)->value);
    ret = true;
  }
  if (ret && node->sem) {
    cql_output(": ");
    print_sem_type(node->sem);
  }
  if (ret) {
    cql_output("\n");
  }
  return ret;
}
cql_noexport void print_ast_type(ast_node *node) {
  cql_output("%s", padbuffer);
  cql_output("{%s}", node->type);
  if (node->sem) {
    cql_output(": ");
    print_sem_type(node->sem);
  }
  cql_output("\n");
}
// Helper function to get the parameters node out of the ast for a proc.
cql_noexport ast_node *get_proc_params(ast_node *ast) {
  Contract(is_ast_create_proc_stmt(ast) || is_ast_declare_proc_stmt(ast));
  // works for both
  EXTRACT_NOTNULL(proc_params_stmts, ast->right);
  EXTRACT(params, proc_params_stmts->left);
  return params;
}
// Helper function to get the proc name from a declare_proc_stmt or create_proc_stmt
cql_noexport ast_node *get_proc_name(ast_node *ast) {
  if (is_ast_create_proc_stmt(ast)) {
    return ast->left;
  }
  Contract(is_ast_declare_proc_stmt(ast));
  EXTRACT_NOTNULL(proc_name_type, ast->left);
  return proc_name_type->left;
}
// Helper function to get the parameters node out of the ast for a func.
cql_noexport ast_node *get_func_params(ast_node *func_stmt) {
  Contract(is_ast_declare_func_stmt(func_stmt) || is_ast_declare_select_func_stmt(func_stmt));
  EXTRACT_NOTNULL(func_params_return, func_stmt->right);
  EXTRACT(params, func_params_return->left);
  return params;
}
// Helper function to extract the list of attribute.
// Walk through a misc_attrs node and call the callbacks :
//  - find_ast_misc_attr_callback if misc_attr node is found
// Let's take the example below and see what values will be passed to callbacks
// e.g:
//  @attribute(cql:foo=(baa, (name, 'nelly')))
//  @attribute(cql:base=raoul)
//  create procedure sample()
//  begin
//    select * from baa;
//  end;
//
//  1- find_ast_misc_attr_callback("cql", "foo", <(baa, (name, 'nelly'))>, <context>)
//  2- find_ast_misc_attr_callback("cql", "foo", <raoul>, <context>)
//  3- End
cql_noexport void find_misc_attrs(
  ast_node *_Nullable ast_misc_attrs,
  find_ast_misc_attr_callback _Nonnull misc_attr_callback,
  void *_Nullable context)
{
  Contract(is_ast_misc_attrs(ast_misc_attrs));
  for (ast_node *misc_attrs = ast_misc_attrs; misc_attrs; misc_attrs = misc_attrs->right) {
    Invariant(is_ast_misc_attrs(misc_attrs));
    ast_node *misc_attr = misc_attrs->left;
    ast_node *misc_attr_key = misc_attr->left;
    ast_node *values = misc_attr->right;
    CSTR misc_attr_prefix = NULL;
    CSTR misc_attr_name = NULL;
    if (is_ast_dot(misc_attr_key)) {
      EXTRACT_STRING(prefix, misc_attr_key->left);
      EXTRACT_STRING(name, misc_attr_key->right);
      misc_attr_prefix = prefix;
      misc_attr_name = name;
    } else {
      EXTRACT_STRING(name, misc_attr_key);
      misc_attr_name = name;
    }
    Invariant(misc_attr_name);
    misc_attr_callback(misc_attr_prefix, misc_attr_name, values, context);
  }
}
// This callback helper dispatches matching string or list of string values
// for the indicated cql:attribute_name.  Non-string values are ignored in
// this path.  Note that the attribute might be badly formed hence there are
// few Contract enforcement here.  We can't crash if the value is unexpected
// we just don't recognize it as properly attributed for whatever (e.g. it just
// isn't a base fragment decl if it has an integer value for the fragment name)
static void ast_find_ast_misc_attr_callback(
  CSTR misc_attr_prefix,
  CSTR misc_attr_name,
  ast_node *ast_misc_attr_values,
  void *_Nullable context)
{
  misc_attrs_type* misc = (misc_attrs_type*) context;
  // First make sure that there is a prefix and name and that they match
  if (misc_attr_prefix &&
      misc_attr_name &&
      !Strcasecmp(misc_attr_prefix, "cql") &&
      !Strcasecmp(misc_attr_name, misc->attribute_name)) {
    // callback regardless of value, could be any payload
    if (misc->presence_only) {
      Invariant(!misc->str_node_callback);
      misc->count++;
      return;
    }
    // The attribute value might be a string or a list of strings.
    // Non-string, non-list attributes are ignored for this callback type
    if (is_ast_str(ast_misc_attr_values)) {
      if (misc->str_node_callback) {
        EXTRACT_STRING(value, ast_misc_attr_values);
        misc->str_node_callback(value, ast_misc_attr_values, misc->context);
      }
      misc->count++;
    }
    else if (is_ast_misc_attr_value_list(ast_misc_attr_values)) {
      for (ast_node *list = ast_misc_attr_values; list; list = list->right) {
        // any non-string values are ignored, loop over the rest calling on each string
        if (is_ast_str(list->left)) {
          EXTRACT_STRING(value, list->left);
          if (misc->str_node_callback) {
            misc->str_node_callback(value, list->left, misc->context);
          }
          misc->count++;
        }
      }
    }
  }
}
// Helper function to extract the specified string type attribute (if any) from the misc attributes
// provided, and invoke the callback function
cql_noexport uint32_t find_attribute_str(
  ast_node *_Nullable misc_attr_list,
  find_ast_str_node_callback _Nullable callback,
  void *_Nullable context,
  const char *attribute_name)
{
  Contract(is_ast_misc_attrs(misc_attr_list));
  misc_attrs_type misc = {
    .str_node_callback = callback,
    .context = context,
    .attribute_name = attribute_name,
    .count = 0,
  };
  find_misc_attrs(misc_attr_list, ast_find_ast_misc_attr_callback, &misc);
  return misc.count;
}
// This callback helper tests only if the attribute matches the search condition
// the value is irrelevant for this type of attribute
static void ast_exists_ast_misc_attr_callback(
  CSTR misc_attr_prefix,
  CSTR misc_attr_name,
  ast_node *ast_misc_attr_values,
  void *_Nullable context)
{
  misc_attrs_type* misc = (misc_attrs_type*) context;
  // First make sure that there is a prefix and name and that they match
  if (misc_attr_prefix &&
      misc_attr_name &&
      !Strcasecmp(misc_attr_prefix, "cql") &&
      !Strcasecmp(misc_attr_name, misc->attribute_name)) {
          misc->count++;
  }
}
// Helper function to extract the specified string type attribute (if any) from the misc attributes
// provided, and invoke the callback function
cql_noexport uint32_t exists_attribute_str(ast_node *_Nullable misc_attr_list, const char *attribute_name)
{
  if (!misc_attr_list) {
    return 0;
  }
  Contract(is_ast_misc_attrs(misc_attr_list));
  misc_attrs_type misc = {
    .str_node_callback = NULL,
    .context = NULL,
    .attribute_name = attribute_name,
    .count = 0,
  };
  find_misc_attrs(misc_attr_list, ast_exists_ast_misc_attr_callback, &misc);
  return misc.count;
}
// Helper function to extract the ok_table_scan nodes (if any) from the misc attributes
// provided, and invoke the callback function.
cql_noexport uint32_t find_ok_table_scan(
  ast_node *_Nonnull list,
  find_ast_str_node_callback _Nonnull callback,
  void *_Nullable context)
{
  return find_attribute_str(list, callback, context, "ok_table_scan");
}
// Helper function to extract the auto-drop nodes (if any) from the misc attributes
// provided, and invoke the callback function.
cql_noexport uint32_t find_autodrops(
  ast_node *_Nonnull list,
  find_ast_str_node_callback _Nonnull callback,
  void *_Nullable context)
{
  return find_attribute_str(list, callback, context, "autodrop");
}
// Helper function to extract the identity columns (if any) from the misc attributes
// provided, and invoke the callback function
cql_noexport uint32_t find_identity_columns(
  ast_node *_Nullable misc_attr_list,
  find_ast_str_node_callback _Nonnull callback,
  void *_Nullable context)
{
  return find_attribute_str(misc_attr_list, callback, context, "identity");
}
// Helper function to extract the shared fragment node (if any) from the misc attributes
// provided, and invoke the callback function.
cql_noexport uint32_t find_shared_fragment_attr(
  ast_node *_Nullable misc_attr_list)
{
  Contract(is_ast_misc_attrs(misc_attr_list));
  misc_attrs_type misc = {
    .presence_only = 1,
    .attribute_name = "shared_fragment",
    .count = 0,
  };
  find_misc_attrs(misc_attr_list, ast_find_ast_misc_attr_callback, &misc);
  return misc.count;
}
// Helper function to extract the blob storage node (if any) from the misc attributes
// provided, and invoke the callback function.
cql_noexport bool_t find_blob_storage_attr(
  ast_node *_Nullable misc_attr_list)
{
  Contract(is_ast_misc_attrs(misc_attr_list));
  misc_attrs_type misc = {
    .presence_only = 1,
    .attribute_name = "blob_storage",
    .count = 0,
  };
  find_misc_attrs(misc_attr_list, ast_find_ast_misc_attr_callback, &misc);
  return misc.count != 0;
}
// Helper function to extract the base fragment node (if any) from the misc attributes
// provided, and invoke the callback function.
cql_noexport uint32_t find_base_fragment_attr(
  ast_node *_Nullable misc_attr_list,
  find_ast_str_node_callback _Nullable callback,
  void *_Nullable context)
{
  return find_attribute_str(misc_attr_list, callback, context, "base_fragment");
}
// Helper function to extract the extension fragment node (if any) from the misc attributes
// provided, and invoke the callback function.
cql_noexport uint32_t find_extension_fragment_attr(
  ast_node *_Nullable misc_attr_list,
  find_ast_str_node_callback _Nullable callback,
  void *_Nullable context)
{
  return find_attribute_str(misc_attr_list, callback, context, "extension_fragment");
}
// Helper function to extract the assembled fragment node (if any) from the misc attributes
// provided, and invoke the callback function.
cql_noexport uint32_t find_assembly_query_attr(
  ast_node *_Nullable misc_attr_list,
  find_ast_str_node_callback _Nullable callback,
  void *_Nullable context)
{
  return find_attribute_str(misc_attr_list, callback, context, "assembly_fragment");
}
// Keep record of the assembly query fragment for result set type reference if
// we are presently emitting an extension fragment stored proc
cql_data_defn ( CSTR base_fragment_name );
// The name of the base fragment is used as part of the field getters names
// we get it from the attribute associated with whichever fragment we're looking at
// semantic rules ensure these are consistent.  The base fragment name basically
// let's us tie together extensions so we know which ones are part of which query
// and then we put those together.  This function is the callback used to harvest
// the base fragment name from wherever we found it.  Each fragment has it.
static void cg_set_base_fragment_name(CSTR _Nonnull name, ast_node *_Nonnull _misc_attr, void *_Nullable _context) {
  if (_context) {
    CSTR *base_name = (CSTR *)_context;
    *base_name = name;
  }
}
// Look for the assembly fragment annotations, return the type or MIXED if
// there is no unique type. This is used to find cases where the same attribute
// is present more than once or different/incompatible attributes are present
cql_noexport uint32_t find_fragment_attr_type(ast_node *_Nullable misc_attr_list, CSTR *_Nullable base_name) {
  if (!misc_attr_list) {
    base_fragment_name = NULL;
    return FRAG_TYPE_NONE;
  }
  uint32_t base = find_base_fragment_attr(misc_attr_list, cg_set_base_fragment_name, base_name);
  uint32_t extension = find_extension_fragment_attr(misc_attr_list, cg_set_base_fragment_name, base_name);
  uint32_t assembly = find_assembly_query_attr(misc_attr_list, cg_set_base_fragment_name, base_name);
  uint32_t shared = find_shared_fragment_attr(misc_attr_list);
  if (base + extension + assembly + shared > 1) {
    return FRAG_TYPE_MIXED;
  }
  if (base + extension + assembly + shared == 0) {
    return FRAG_TYPE_NONE;
  }
  if (shared) {
    return FRAG_TYPE_SHARED;
  }
  if (base) {
    return FRAG_TYPE_BASE;
  }
  if (extension) {
    return FRAG_TYPE_EXTENSION;
  }
  Invariant(assembly);
  return FRAG_TYPE_ASSEMBLY;
}
// helper to get the fragment type of a given procedure
cql_noexport uint32_t find_proc_frag_type(ast_node *ast) {
  Contract(is_ast_create_proc_stmt(ast) || is_ast_declare_proc_stmt(ast));
  EXTRACT_MISC_ATTRS(ast, misc_attrs);
  return find_fragment_attr_type(misc_attrs, NULL);
}
// helper to get the fragment type of a given procedure
cql_noexport bool_t is_table_blob_storage(ast_node *ast) {
  Contract(is_ast_create_table_stmt(ast) || is_ast_create_virtual_table_stmt(ast));
  EXTRACT_MISC_ATTRS(ast, misc_attrs);
  return misc_attrs && find_blob_storage_attr(misc_attrs);
}
// This can be easily called in the debugger
cql_noexport void print_root_ast(ast_node *node) {
  print_ast(node, NULL, 0, false);
}
cql_noexport void print_ast(ast_node *node, ast_node *parent, int32_t pad, bool_t flip) {
  if (pad == 0) {
    padbuffer[0] = '\0';
  }
  if (!node) {
    return;
  }
  if (print_ast_value(node)) {
    return;
  }
  if (is_ast_stmt_list(parent) && is_ast_stmt_list(node)) {
    print_ast(node->left, node, pad, !node->right);
    print_ast(node->right, node, pad, 1);
  }
  else {
    if (pad == 2) {
      cql_output("\n");
      if (parent && is_ast_stmt_list(parent)) {
        cql_output("The statement ending at line %d\n\n", node->lineno);
        gen_stmt_level = 1;
        EXTRACT_STMT_AND_MISC_ATTRS(stmt, misc_attrs, parent);
        if (misc_attrs) {
          gen_misc_attrs_to_stdout(misc_attrs);
        }
        gen_one_stmt_to_stdout(stmt);
        cql_output("\n");
#if defined(CQL_AMALGAM_LEAN) && !defined(CQL_AMALGAM_SEM)
        // sem off, nothing to print here
#else
        // print any error text
        if (stmt->sem && stmt->sem->sem_type == SEM_TYPE_ERROR && stmt->sem->error) {
          cql_output("%s\n", stmt->sem->error);
        }
#endif
      }
    }
    print_ast_type(node);
    if (flip && pad >= 2) {
      padbuffer[pad-2] = ' ';
    }
    if (pad == 0) {
      padbuffer[pad] = ' ';
    }
    else {
      padbuffer[pad] = '|';
    }
    padbuffer[pad+1] = ' ';
    padbuffer[pad+2] = '\0';
    print_ast(node->left, node, pad+2, !node->right);
    print_ast(node->right, node, pad+2, 1);
    padbuffer[pad] = '\0';
  }
}
// Clone a ast tree by the given root ast_node.
// The sems of all nodes are NULL in the new tree.
cql_noexport ast_node *copy_ast_tree(ast_node *_Nonnull node) {
  Contract(node);
  ast_node *new_node = NULL;
  // Note: ast_has_left includes the is_primitive check so it's safe to do on leaf nodes too
  // even though they have no left/right at all.
  ast_node *left_node = NULL;
  ast_node *right_node = NULL;
  if (ast_has_left(node)) {
    left_node = copy_ast_tree(node->left);
  }
  if (ast_has_right(node)) {
    right_node = copy_ast_tree(node->right);
  }
  AST_REWRITE_INFO_SET(node->lineno, node->filename);
  if (is_ast_num(node)) {
    EXTRACT_NUM_TYPE(num_type, node);
    EXTRACT_NUM_VALUE(val, node);
    new_node = new_ast_num(num_type, val);
  } else if (is_ast_int(node)) {
    EXTRACT_OPTION(value, node);
    new_node = new_ast_opt(value);
  } else if (is_ast_blob(node)) {
    EXTRACT_BLOBTEXT(value, node);
    new_node = new_ast_blob(value);
  } else if (is_ast_str(node)) {
    EXTRACT_STRING(value, node);
    new_node = new_ast_str(value);
  } else {
    new_node = new_ast(node->type, left_node, right_node);
  }
  AST_REWRITE_INFO_RESET();
  Invariant(new_node);
  return new_node;
}