mirror of
				https://github.com/godotengine/godot.git
				synced 2025-10-30 21:21:10 +00:00 
			
		
		
		
	
		
			
	
	
		
			487 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
		
		
			
		
	
	
			487 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
|   | #include "script_class_parser.h"
 | ||
|  | 
 | ||
|  | #include "core/map.h"
 | ||
|  | #include "core/os/os.h"
 | ||
|  | 
 | ||
|  | #include "../utils/string_utils.h"
 | ||
|  | 
 | ||
|  | ScriptClassParser::Token ScriptClassParser::get_token() { | ||
|  | 
 | ||
|  | 	while (true) { | ||
|  | 		switch (code[idx]) { | ||
|  | 			case '\n': { | ||
|  | 				line++; | ||
|  | 				idx++; | ||
|  | 				break; | ||
|  | 			}; | ||
|  | 			case 0: { | ||
|  | 				return TK_EOF; | ||
|  | 			} break; | ||
|  | 			case '{': { | ||
|  | 				idx++; | ||
|  | 				return TK_CURLY_BRACKET_OPEN; | ||
|  | 			}; | ||
|  | 			case '}': { | ||
|  | 				idx++; | ||
|  | 				return TK_CURLY_BRACKET_CLOSE; | ||
|  | 			}; | ||
|  | 			case '[': { | ||
|  | 				idx++; | ||
|  | 				return TK_BRACKET_OPEN; | ||
|  | 			}; | ||
|  | 			case ']': { | ||
|  | 				idx++; | ||
|  | 				return TK_BRACKET_CLOSE; | ||
|  | 			}; | ||
|  | 			case '<': { | ||
|  | 				idx++; | ||
|  | 				return TK_OP_LESS; | ||
|  | 			}; | ||
|  | 			case '>': { | ||
|  | 				idx++; | ||
|  | 				return TK_OP_GREATER; | ||
|  | 			}; | ||
|  | 			case ':': { | ||
|  | 				idx++; | ||
|  | 				return TK_COLON; | ||
|  | 			}; | ||
|  | 			case ',': { | ||
|  | 				idx++; | ||
|  | 				return TK_COMMA; | ||
|  | 			}; | ||
|  | 			case '.': { | ||
|  | 				idx++; | ||
|  | 				return TK_PERIOD; | ||
|  | 			}; | ||
|  | 			case '#': { | ||
|  | 				//compiler directive
 | ||
|  | 				while (code[idx] != '\n' && code[idx] != 0) { | ||
|  | 					idx++; | ||
|  | 				} | ||
|  | 				continue; | ||
|  | 			} break; | ||
|  | 			case '/': { | ||
|  | 				switch (code[idx + 1]) { | ||
|  | 					case '*': { // block comment
 | ||
|  | 						idx += 2; | ||
|  | 						while (true) { | ||
|  | 							if (code[idx] == 0) { | ||
|  | 								error_str = "Unterminated comment"; | ||
|  | 								error = true; | ||
|  | 								return TK_ERROR; | ||
|  | 							} else if (code[idx] == '*' && code[idx + 1] == '/') { | ||
|  | 								idx += 2; | ||
|  | 								break; | ||
|  | 							} else if (code[idx] == '\n') { | ||
|  | 								line++; | ||
|  | 							} | ||
|  | 
 | ||
|  | 							idx++; | ||
|  | 						} | ||
|  | 
 | ||
|  | 					} break; | ||
|  | 					case '/': { // line comment skip
 | ||
|  | 						while (code[idx] != '\n' && code[idx] != 0) { | ||
|  | 							idx++; | ||
|  | 						} | ||
|  | 
 | ||
|  | 					} break; | ||
|  | 					default: { | ||
|  | 						value = "/"; | ||
|  | 						idx++; | ||
|  | 						return TK_SYMBOL; | ||
|  | 					} | ||
|  | 				} | ||
|  | 
 | ||
|  | 				continue; // a comment
 | ||
|  | 			} break; | ||
|  | 			case '\'': | ||
|  | 			case '"': { | ||
|  | 				bool verbatim = idx != 0 && code[idx - 1] == '@'; | ||
|  | 
 | ||
|  | 				CharType begin_str = code[idx]; | ||
|  | 				idx++; | ||
|  | 				String tk_string = String(); | ||
|  | 				while (true) { | ||
|  | 					if (code[idx] == 0) { | ||
|  | 						error_str = "Unterminated String"; | ||
|  | 						error = true; | ||
|  | 						return TK_ERROR; | ||
|  | 					} else if (code[idx] == begin_str) { | ||
|  | 						if (verbatim && code[idx + 1] == '"') { // `""` is verbatim string's `\"`
 | ||
|  | 							idx += 2; // skip next `"` as well
 | ||
|  | 							continue; | ||
|  | 						} | ||
|  | 
 | ||
|  | 						idx += 1; | ||
|  | 						break; | ||
|  | 					} else if (code[idx] == '\\' && !verbatim) { | ||
|  | 						//escaped characters...
 | ||
|  | 						idx++; | ||
|  | 						CharType next = code[idx]; | ||
|  | 						if (next == 0) { | ||
|  | 							error_str = "Unterminated String"; | ||
|  | 							error = true; | ||
|  | 							return TK_ERROR; | ||
|  | 						} | ||
|  | 						CharType res = 0; | ||
|  | 
 | ||
|  | 						switch (next) { | ||
|  | 							case 'b': res = 8; break; | ||
|  | 							case 't': res = 9; break; | ||
|  | 							case 'n': res = 10; break; | ||
|  | 							case 'f': res = 12; break; | ||
|  | 							case 'r': | ||
|  | 								res = 13; | ||
|  | 								break; | ||
|  | 							case '\"': res = '\"'; break; | ||
|  | 							case '\\': | ||
|  | 								res = '\\'; | ||
|  | 								break; | ||
|  | 							default: { | ||
|  | 								res = next; | ||
|  | 							} break; | ||
|  | 						} | ||
|  | 
 | ||
|  | 						tk_string += res; | ||
|  | 
 | ||
|  | 					} else { | ||
|  | 						if (code[idx] == '\n') | ||
|  | 							line++; | ||
|  | 						tk_string += code[idx]; | ||
|  | 					} | ||
|  | 					idx++; | ||
|  | 				} | ||
|  | 
 | ||
|  | 				value = tk_string; | ||
|  | 
 | ||
|  | 				return TK_STRING; | ||
|  | 			} break; | ||
|  | 			default: { | ||
|  | 				if (code[idx] <= 32) { | ||
|  | 					idx++; | ||
|  | 					break; | ||
|  | 				} | ||
|  | 
 | ||
|  | 				if ((code[idx] >= 33 && code[idx] <= 47) || (code[idx] >= 58 && code[idx] <= 63) || (code[idx] >= 91 && code[idx] <= 94) || code[idx] == 96 || (code[idx] >= 123 && code[idx] <= 127)) { | ||
|  | 					value = String::chr(code[idx]); | ||
|  | 					idx++; | ||
|  | 					return TK_SYMBOL; | ||
|  | 				} | ||
|  | 
 | ||
|  | 				if (code[idx] == '-' || (code[idx] >= '0' && code[idx] <= '9')) { | ||
|  | 					//a number
 | ||
|  | 					const CharType *rptr; | ||
|  | 					double number = String::to_double(&code[idx], &rptr); | ||
|  | 					idx += (rptr - &code[idx]); | ||
|  | 					value = number; | ||
|  | 					return TK_NUMBER; | ||
|  | 
 | ||
|  | 				} else if ((code[idx] == '@' && code[idx + 1] != '"') || code[idx] == '_' || (code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || code[idx] > 127) { | ||
|  | 					String id; | ||
|  | 
 | ||
|  | 					id += code[idx]; | ||
|  | 					idx++; | ||
|  | 
 | ||
|  | 					while (code[idx] == '_' || (code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || (code[idx] >= '0' && code[idx] <= '9') || code[idx] > 127) { | ||
|  | 						id += code[idx]; | ||
|  | 						idx++; | ||
|  | 					} | ||
|  | 
 | ||
|  | 					value = id; | ||
|  | 					return TK_IDENTIFIER; | ||
|  | 				} else if (code[idx] == '@' && code[idx + 1] == '"') { | ||
|  | 					// begin of verbatim string
 | ||
|  | 					idx++; | ||
|  | 				} else { | ||
|  | 					error_str = "Unexpected character."; | ||
|  | 					error = true; | ||
|  | 					return TK_ERROR; | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | Error ScriptClassParser::_skip_type_parameters() { | ||
|  | 
 | ||
|  | 	Token tk; | ||
|  | 
 | ||
|  | 	while (true) { | ||
|  | 		tk = get_token(); | ||
|  | 
 | ||
|  | 		if (tk == TK_IDENTIFIER) { | ||
|  | 			tk = get_token(); | ||
|  | 
 | ||
|  | 			if (tk == TK_OP_LESS) { | ||
|  | 				Error err = _skip_type_parameters(); | ||
|  | 				if (err) | ||
|  | 					return err; | ||
|  | 				continue; | ||
|  | 			} else if (tk != TK_COMMA) { | ||
|  | 				error_str = "Unexpected token: " + itos(tk); | ||
|  | 				error = true; | ||
|  | 				return ERR_PARSE_ERROR; | ||
|  | 			} | ||
|  | 		} else if (tk == TK_OP_LESS) { | ||
|  | 			error_str = "Expected identifier before `<`."; | ||
|  | 			error = true; | ||
|  | 			return ERR_PARSE_ERROR; | ||
|  | 		} else if (tk == TK_OP_GREATER) { | ||
|  | 			return OK; | ||
|  | 		} else { | ||
|  | 			error_str = "Unexpected token: " + itos(tk); | ||
|  | 			error = true; | ||
|  | 			return ERR_PARSE_ERROR; | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | Error ScriptClassParser::_parse_class_base(Vector<String> &r_base) { | ||
|  | 
 | ||
|  | 	Token tk; | ||
|  | 
 | ||
|  | 	while (true) { | ||
|  | 		tk = get_token(); | ||
|  | 
 | ||
|  | 		if (tk == TK_IDENTIFIER) { | ||
|  | 			bool generic = false; | ||
|  | 
 | ||
|  | 			String name = value; | ||
|  | 
 | ||
|  | 			tk = get_token(); | ||
|  | 
 | ||
|  | 			if (tk == TK_OP_LESS) { | ||
|  | 				generic = true; | ||
|  | 				Error err = _skip_type_parameters(); | ||
|  | 				if (err) | ||
|  | 					return err; | ||
|  | 			} else if (tk == TK_COMMA) { | ||
|  | 				Error err = _parse_class_base(r_base); | ||
|  | 				if (err) | ||
|  | 					return err; | ||
|  | 			} else if (tk != TK_CURLY_BRACKET_OPEN) { | ||
|  | 				error_str = "Unexpected token: " + itos(tk); | ||
|  | 				error = true; | ||
|  | 				return ERR_PARSE_ERROR; | ||
|  | 			} | ||
|  | 
 | ||
|  | 			r_base.push_back(!generic ? name : String()); // no generics, please
 | ||
|  | 
 | ||
|  | 			return OK; | ||
|  | 		} else { | ||
|  | 			error_str = "Unexpected token: " + itos(tk); | ||
|  | 			error = true; | ||
|  | 			return ERR_PARSE_ERROR; | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | Error ScriptClassParser::_parse_namespace_name(String &r_name, int &r_curly_stack) { | ||
|  | 
 | ||
|  | 	Token tk = get_token(); | ||
|  | 
 | ||
|  | 	if (tk == TK_IDENTIFIER) { | ||
|  | 		r_name += String(value); | ||
|  | 	} else { | ||
|  | 		error_str = "Unexpected token: " + itos(tk); | ||
|  | 		error = true; | ||
|  | 		return ERR_PARSE_ERROR; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	tk = get_token(); | ||
|  | 
 | ||
|  | 	if (tk == TK_PERIOD) { | ||
|  | 		r_name += "."; | ||
|  | 		return _parse_namespace_name(r_name, r_curly_stack); | ||
|  | 	} else if (tk == TK_CURLY_BRACKET_OPEN) { | ||
|  | 		r_curly_stack++; | ||
|  | 		return OK; | ||
|  | 	} else { | ||
|  | 		error_str = "Unexpected token: " + itos(tk); | ||
|  | 		error = true; | ||
|  | 		return ERR_PARSE_ERROR; | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | Error ScriptClassParser::parse(const String &p_code) { | ||
|  | 
 | ||
|  | 	code = p_code; | ||
|  | 	idx = 0; | ||
|  | 	line = 0; | ||
|  | 	error_str = String(); | ||
|  | 	error = false; | ||
|  | 	value = Variant(); | ||
|  | 	classes.clear(); | ||
|  | 
 | ||
|  | 	Token tk = get_token(); | ||
|  | 
 | ||
|  | 	Map<int, NameDecl> name_stack; | ||
|  | 	int curly_stack = 0; | ||
|  | 	int type_curly_stack = 0; | ||
|  | 
 | ||
|  | 	while (!error && tk != TK_EOF) { | ||
|  | 		if (tk == TK_IDENTIFIER && String(value) == "class") { | ||
|  | 			tk = get_token(); | ||
|  | 
 | ||
|  | 			if (tk == TK_IDENTIFIER) { | ||
|  | 				String name = value; | ||
|  | 				int at_level = type_curly_stack; | ||
|  | 
 | ||
|  | 				ClassDecl class_decl; | ||
|  | 
 | ||
|  | 				for (Map<int, NameDecl>::Element *E = name_stack.front(); E; E = E->next()) { | ||
|  | 					const NameDecl &name_decl = E->value(); | ||
|  | 
 | ||
|  | 					if (name_decl.type == NameDecl::NAMESPACE_DECL) { | ||
|  | 						if (E != name_stack.front()) | ||
|  | 							class_decl.namespace_ += "."; | ||
|  | 						class_decl.namespace_ += name_decl.name; | ||
|  | 					} else { | ||
|  | 						class_decl.name += name_decl.name + "."; | ||
|  | 					} | ||
|  | 				} | ||
|  | 
 | ||
|  | 				class_decl.name += name; | ||
|  | 				class_decl.nested = type_curly_stack > 0; | ||
|  | 
 | ||
|  | 				bool generic = false; | ||
|  | 
 | ||
|  | 				while (true) { | ||
|  | 					tk = get_token(); | ||
|  | 
 | ||
|  | 					if (tk == TK_COLON) { | ||
|  | 						Error err = _parse_class_base(class_decl.base); | ||
|  | 						if (err) | ||
|  | 							return err; | ||
|  | 
 | ||
|  | 						curly_stack++; | ||
|  | 						type_curly_stack++; | ||
|  | 
 | ||
|  | 						break; | ||
|  | 					} else if (tk == TK_CURLY_BRACKET_OPEN) { | ||
|  | 						curly_stack++; | ||
|  | 						type_curly_stack++; | ||
|  | 						break; | ||
|  | 					} else if (tk == TK_OP_LESS && !generic) { | ||
|  | 						generic = true; | ||
|  | 
 | ||
|  | 						Error err = _skip_type_parameters(); | ||
|  | 						if (err) | ||
|  | 							return err; | ||
|  | 					} else { | ||
|  | 						error_str = "Unexpected token: " + itos(tk); | ||
|  | 						error = true; | ||
|  | 						return ERR_PARSE_ERROR; | ||
|  | 					} | ||
|  | 				} | ||
|  | 
 | ||
|  | 				NameDecl name_decl; | ||
|  | 				name_decl.name = name; | ||
|  | 				name_decl.type = NameDecl::CLASS_DECL; | ||
|  | 				name_stack[at_level] = name_decl; | ||
|  | 
 | ||
|  | 				if (!generic) { // no generics, thanks
 | ||
|  | 					classes.push_back(class_decl); | ||
|  | 				} else if (OS::get_singleton()->is_stdout_verbose()) { | ||
|  | 					String full_name = class_decl.namespace_; | ||
|  | 					if (full_name.length()) | ||
|  | 						full_name += "."; | ||
|  | 					full_name += class_decl.name; | ||
|  | 					OS::get_singleton()->print(String("Ignoring generic class declaration: " + class_decl.name).utf8()); | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} else if (tk == TK_IDENTIFIER && String(value) == "struct") { | ||
|  | 			String name; | ||
|  | 			int at_level = type_curly_stack; | ||
|  | 			while (true) { | ||
|  | 				tk = get_token(); | ||
|  | 				if (tk == TK_IDENTIFIER && name.empty()) { | ||
|  | 					name = String(value); | ||
|  | 				} else if (tk == TK_CURLY_BRACKET_OPEN) { | ||
|  | 					if (name.empty()) { | ||
|  | 						error_str = "Expected identifier after keyword `struct`. Found `{`."; | ||
|  | 						error = true; | ||
|  | 						return ERR_PARSE_ERROR; | ||
|  | 					} | ||
|  | 
 | ||
|  | 					curly_stack++; | ||
|  | 					type_curly_stack++; | ||
|  | 					break; | ||
|  | 				} else if (tk == TK_EOF) { | ||
|  | 					error_str = "Expected `{` after struct decl. Found `EOF`."; | ||
|  | 					error = true; | ||
|  | 					return ERR_PARSE_ERROR; | ||
|  | 				} | ||
|  | 			} | ||
|  | 
 | ||
|  | 			NameDecl name_decl; | ||
|  | 			name_decl.name = name; | ||
|  | 			name_decl.type = NameDecl::STRUCT_DECL; | ||
|  | 			name_stack[at_level] = name_decl; | ||
|  | 		} else if (tk == TK_IDENTIFIER && String(value) == "namespace") { | ||
|  | 			if (type_curly_stack > 0) { | ||
|  | 				error_str = "Found namespace nested inside type."; | ||
|  | 				error = true; | ||
|  | 				return ERR_PARSE_ERROR; | ||
|  | 			} | ||
|  | 
 | ||
|  | 			String name; | ||
|  | 			int at_level = curly_stack; | ||
|  | 
 | ||
|  | 			Error err = _parse_namespace_name(name, curly_stack); | ||
|  | 			if (err) | ||
|  | 				return err; | ||
|  | 
 | ||
|  | 			NameDecl name_decl; | ||
|  | 			name_decl.name = name; | ||
|  | 			name_decl.type = NameDecl::NAMESPACE_DECL; | ||
|  | 			name_stack[at_level] = name_decl; | ||
|  | 		} else if (tk == TK_CURLY_BRACKET_OPEN) { | ||
|  | 			curly_stack++; | ||
|  | 		} else if (tk == TK_CURLY_BRACKET_CLOSE) { | ||
|  | 			curly_stack--; | ||
|  | 			if (name_stack.has(curly_stack)) { | ||
|  | 				if (name_stack[curly_stack].type != NameDecl::NAMESPACE_DECL) | ||
|  | 					type_curly_stack--; | ||
|  | 				name_stack.erase(curly_stack); | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		tk = get_token(); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (!error && tk == TK_EOF && curly_stack > 0) { | ||
|  | 		error_str = "Reached EOF with missing close curly brackets."; | ||
|  | 		error = true; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (error) | ||
|  | 		return ERR_PARSE_ERROR; | ||
|  | 
 | ||
|  | 	return OK; | ||
|  | } | ||
|  | 
 | ||
|  | Error ScriptClassParser::parse_file(const String &p_filepath) { | ||
|  | 
 | ||
|  | 	String source; | ||
|  | 
 | ||
|  | 	Error ferr = read_all_file_utf8(p_filepath, source); | ||
|  | 	if (ferr != OK) { | ||
|  | 		if (ferr == ERR_INVALID_DATA) { | ||
|  | 			ERR_EXPLAIN("File '" + p_filepath + "' contains invalid unicode (utf-8), so it was not loaded. Please ensure that scripts are saved in valid utf-8 unicode."); | ||
|  | 		} | ||
|  | 		ERR_FAIL_V(ferr); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	return parse(source); | ||
|  | } | ||
|  | 
 | ||
|  | String ScriptClassParser::get_error() { | ||
|  | 	return error_str; | ||
|  | } | ||
|  | 
 | ||
|  | Vector<ScriptClassParser::ClassDecl> ScriptClassParser::get_classes() { | ||
|  | 	return classes; | ||
|  | } |