SOURCE

const sql = `
CREATE TABLE \`pf_station_info\` (
  \`id\` int unsigned NOT NULL AUTO_INCREMENT,
  \`name\` varchar(20) NOT NULL DEFAULT '' COMMENT '站点名称',
  \`location\` json NOT NULL COMMENT '站点所在经纬度',
  \`ip_address\` varchar(15) NOT NULL DEFAULT '' COMMENT '站点 ip 地址,非此 IP 数据不接收',
  \`tiger_shaped\` char(32) NOT NULL DEFAULT '' COMMENT '握手符号',
  \`heartbeat\` int unsigned NOT NULL DEFAULT '0' COMMENT '上次心跳时间戳',
  \`status\` int NOT NULL DEFAULT '0' COMMENT '站点状态:0 正常,非 0 为停用时间戳',
  \`add_time\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  \`update_time\` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (\`id\`)
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

CREATE TABLE \`player\` (
  \`user_id\` bigint(20) NOT NULL COMMENT '用户ID',
  \`user_name\` varchar(32) COLLATE utf8mb4_bin NOT NULL COMMENT '用户昵称',
  \`user_level\` int(11) NOT NULL COMMENT '用户等级',
  \`head_skin\` int(11) NOT NULL COMMENT '用户头像皮肤',
  \`head_box_skin\` int(11) NOT NULL COMMENT '用户头像框皮肤',
  \`world_space_id\` int(11) NOT NULL COMMENT '世界空间ID',
  \`is_online\` bit(1) NOT NULL COMMENT '是否在线',
  \`login_time\` datetime DEFAULT NULL COMMENT '最近登录时间',
  \`login_ip\` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '最近登录IP地址',
  \`create_time\` datetime NOT NULL COMMENT '创建时间',
  \`revision\` int(11) NOT NULL COMMENT '乐观锁版本号',
  PRIMARY KEY (\`user_id\`) USING BTREE,
  UNIQUE KEY \`uk_user_name\` (\`user_name\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

CREATE TABLE \`player_features\` (
  \`guid\` char(36) COLLATE utf8mb4_bin NOT NULL COMMENT 'GUID',
  \`user_id\` bigint(20) NOT NULL COMMENT '用户ID',
  \`feature_type\` bigint(20) NOT NULL COMMENT '特性类型',
  \`feature_value\` bigint(20) NOT NULL COMMENT '特性值',
  \`create_time\` datetime NOT NULL COMMENT '创建时间',
  \`duration\` int(11) NOT NULL COMMENT '持续时间,单位秒,为0则不过期',
  PRIMARY KEY (\`guid\`) USING BTREE,
  KEY \`idx_user_id\` (\`user_id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
`;

function* read_tokens(sql_string) {
  sql_string = sql_string.replaceAll('\r', '');
  sql_string = sql_string.replaceAll('\n', '');
	const singl_token_table = [',', ';', '(', ')', '='];
  const chars = [...sql_string];
  let token = "";
  let in_quote = false;
  for(let char of chars) {
    if (in_quote || char === '\'') {
      if ( char === '\''){
      	if (in_quote) {
        	yield token;
          token = '';
        	in_quote = false;
        }
        else {
        	in_quote = true;
        }
      }
      else {
      	token += char;
      }
    }
    else if (char === ' ') {
      if (token !== '')
      {
        yield token;
        token = '';
      }
    }
    else if (singl_token_table.includes(char)) {
      if (token !== '')
      {
        yield token;
        token = '';
      }
      yield char;
    }
    else {
      token += char;
    }
  }
}

function create_token_accessor(tokens) {
	return {
  	_tokens: tokens,
    _current_token: tokens.next(),
    
    current_token: function() {
    	return this._current_token.value;
    },
    
    has_next: function() {
    	return !this._current_token.done;
    },
    
    next: function() {
    	this._current_token = this._tokens.next();
      return this;
    },
    
    next_while: function(...keys) {
    	while(this.has_next()) {
      	if (keys.includes(this.next().current_token())) {
        	return this;
        }
      }
      
      throw new Error("Cannot found keys until token finished.");
    }
  }
}

//for(let token of read_tokens(sql)) {
//  console.log(token);
//}


function read_table_field_type(token_accessor) {
  let result = {
  	name: token_accessor.next().current_token(),
  }
  
  if (!token_accessor.has_next()) {
  	return result
  }
  
  if (token_accessor.next().current_token() === "(") {
  	result.length = token_accessor.next().current_token();
    
    // close )
    if (token_accessor.next().current_token() !== ")") {
    	throw new Error("Field type mismatch end [ ) ] of length, field name: " + result);
    }
  }
  
  return result;
}

function read_table_field(token_accessor) {
	let result = {};
	result.name = token_accessor.current_token();
	result.type = read_table_field_type(token_accessor);
  
	while (token_accessor.has_next()) {
  	var token = token_accessor.next().current_token();
    switch(token.toUpperCase()) {
    	case "COMMENT":
      	result.comment = token_accessor.next().current_token();
        break;
      case ",":
      case ")":
      	return result;
      default:
        // ignore all options
        // console.log("FIELD: [" + token + "] not handle");
      	break;
    }
  }
  
  throw new Error("Read table field not complete");
}

function read_table_key(token_accessor) {
	let keyFields = [];
  token_accessor.next_while("(");
  
  // key fields
  while (token_accessor.has_next()) {
  	let token = token_accessor.next().current_token();
    if (token === ')') {
      break;
    }
    if (token !== ',') {
      keyFields.push(token);
    }
  }
  
  // key options
  while (token_accessor.has_next()) {
  	let token = token_accessor.next().current_token();
    if (token === ')' || token === ',') {
      break;
    }
    
    //
    // ignore all options...
  }
  
  return {
    fields: keyFields
  }
}

function read_table(token_accessor) {
	const key_types = ["PRIMARY", "UNIQUE", "KEY"];
	let table_name = token_accessor.next().current_token();
  let fields = [];
  let keys = [];
  
  token_accessor.next_while("(");
  while (token_accessor.has_next()) {
  	// end of table
    let token = token_accessor.current_token();
  	if (token === ')') {
    	break;
    }
    
    token = token_accessor.next().current_token();
  	console.log("TABLE: " + token);
    
    // keys
    if (key_types.includes(token)) {
    	let key = read_table_key(token_accessor);
      key.type = token;
    	keys.push(key);
    }
    else {
    	var field = read_table_field(token_accessor);
    	fields.push(field);
    }
  }
  
  token_accessor.next_while(";");
  
  return {
  	name: table_name,
    keys: keys,
    fields: fields
  }
}



function read(ddl) {
	const tokens = read_tokens(ddl);
  const token_accessor = create_token_accessor(tokens);
  
  let result = {
  	create_tables: [],
  };
  
  while (token_accessor.has_next()) {
  	console.log("GLOBAL: " + token_accessor.current_token());
    
    switch(token_accessor.current_token().toUpperCase()) {
    	case "CREATE":
      	if (token_accessor.next().current_token().toUpperCase() === 'TABLE') {
        	var table = read_table(token_accessor);
          result.create_tables.push(table);
          token_accessor.next();
        }
        else {
        	throw new Error("Invalid token: " + token_accessor.current_token());
        }
        break;
      default:
      	// other ddl haven't handle...
      	throw new Error("Invalid token: " + token_accessor.current_token());
    }
  }
  
  return result;
}


var results = read(sql);
// console.log(results);
console.log(JSON.stringify(results));




console 命令行工具 X clear

                    
>
console