| //+------------------------------------------------------------------+
|
| //| ClaudeJson.mqh |
|
| //| Copyright 2026, Niquel Mendoza. |
|
| //| https://www.mql5.com/ |
|
| //+------------------------------------------------------------------+
|
| #property copyright "Copyright 2026, Niquel Mendoza."
|
| #property link "https://www.mql5.com/"
|
| #property strict
|
|
|
| //+------------------------------------------------------------------+
|
| //| |
|
| //+------------------------------------------------------------------+
|
| /*
|
| # Especificación: Parser JSON de Alto Rendimiento para MQL5
|
|
|
| Necesito que desarrolles un **parser JSON de alto rendimiento (High Performance JSON Parser)** completamente escrito en **MQL5 puro**.
|
|
|
| ## Objetivo principal
|
|
|
| La prioridad absoluta es el **rendimiento**. El parser debe estar diseñado para ser lo más rápido posible sin sacrificar la compatibilidad con el estándar JSON.
|
|
|
| No busco una implementación sencilla o académica; quiero una implementación optimizada para producción utilizando todas las técnicas de optimización que conozcas.
|
|
|
| ---
|
|
|
| # Requisitos generales
|
|
|
| * Todo el código debe estar contenido en **un único archivo `.mqh`**.
|
| * No utilizar DLL.
|
| * No utilizar librerías externas.
|
| * No depender de componentes adicionales.
|
| * Código 100% MQL5.
|
|
|
| ---
|
|
|
| # Compatibilidad JSON
|
|
|
| Debe soportar completamente la especificación JSON:
|
|
|
| * Objetos (`{}`)
|
| * Arrays (`[]`)
|
| * Strings
|
| * Numbers
|
|
|
| * enteros
|
| * decimales
|
| * exponentes
|
| * Boolean (`true`, `false`)
|
| * `null`
|
| * Secuencias de escape:
|
|
|
| * `\"`
|
| * `\\`
|
| * `\/`
|
| * `\b`
|
| * `\f`
|
| * `\n`
|
| * `\r`
|
| * `\t`
|
| * `\uXXXX`
|
|
|
| No quiero un parser parcial; debe aceptar cualquier JSON válido.
|
|
|
| ---
|
|
|
| # Diseño de la arquitectura
|
|
|
| Quiero que tú decidas la arquitectura interna.
|
|
|
| Puedes utilizar cualquier técnica que mejore el rendimiento, por ejemplo:
|
|
|
| * parser iterativo (evitar recursión si aporta velocidad)
|
| * stacks manuales
|
| * pools de memoria
|
| * arenas de memoria
|
| * estructuras contiguas
|
| * tablas de índices
|
| * minimización de asignaciones dinámicas
|
| * reducción de copias de memoria
|
| * acceso cache-friendly
|
| * optimización de ramas (branch prediction)
|
| * optimización de comparaciones de caracteres
|
| * cualquier otra técnica de optimización que consideres adecuada.
|
|
|
| No me interesa que la implementación sea "bonita"; me interesa que sea extremadamente rápida.
|
|
|
| ---
|
|
|
| # Entrada de datos
|
|
|
| El parser debe trabajar directamente sobre un buffer de bytes.
|
|
|
| Ejemplo:
|
|
|
| ```cpp
|
| uchar Buffer[];
|
| ```
|
|
|
| La clase debe exponer públicamente este buffer para que yo pueda llenarlo directamente desde mi código sin copias adicionales.
|
|
|
| Ejemplo:
|
|
|
| ```cpp
|
| CJson json;
|
|
|
| json.Buffer = MiBuffer;
|
|
|
| json.Parse();
|
| ```
|
|
|
| O una interfaz equivalente que no implique copiar datos.
|
|
|
| ---
|
|
|
| # Clase principal
|
|
|
| Quiero una clase similar a:
|
|
|
| ```cpp
|
| class CJson
|
| {
|
| public:
|
|
|
| uchar Buffer[];
|
|
|
| bool Parse();
|
|
|
| ...
|
| };
|
| ```
|
|
|
| ---
|
|
|
| # API
|
|
|
| Diseña una API limpia y orientada al rendimiento.
|
|
|
| Debe incluir funciones útiles como:
|
|
|
| * Parse()
|
| * Clear()
|
| * Reset()
|
| * GetError()
|
| * GetErrorMessage()
|
| * HasError()
|
| * IsObject()
|
| * IsArray()
|
| * GetType()
|
| * Exists()
|
| * Size()
|
| * Count()
|
|
|
| y cualquier otra función que facilite el uso del parser.
|
|
|
| ---
|
|
|
| # Acceso a valores
|
|
|
| Debe permitir acceder fácilmente a los nodos.
|
|
|
| Ejemplos:
|
|
|
| ```cpp
|
| json["symbol"];
|
|
|
| json["price"];
|
|
|
| json["ticks"][5];
|
|
|
| json["account"]["balance"];
|
| ```
|
|
|
| o cualquier interfaz equivalente que sea eficiente.
|
|
|
| ---
|
|
|
| # Representación interna
|
|
|
| Quiero que el parser almacene una representación eficiente del documento JSON.
|
|
|
| No quiero que vuelva a parsear el texto cada vez que se consulta un valor.
|
|
|
| La estructura interna debe permitir consultas rápidas.
|
|
|
| ---
|
|
|
| # Manejo de memoria
|
|
|
| La implementación debe minimizar:
|
|
|
| * asignaciones dinámicas
|
| * fragmentación
|
| * copias innecesarias
|
| * uso excesivo de memoria
|
|
|
| Si es posible, reutilizar buffers mediante `Reset()` o `Clear()`.
|
|
|
| ---
|
|
|
| # Manejo de errores
|
|
|
| Debe disponer de un sistema completo de errores.
|
|
|
| Por ejemplo:
|
|
|
| ```cpp
|
| enum JsonError
|
| {
|
| JSON_OK,
|
| JSON_UNEXPECTED_CHARACTER,
|
| JSON_UNEXPECTED_END,
|
| JSON_INVALID_NUMBER,
|
| JSON_INVALID_STRING,
|
| JSON_INVALID_ESCAPE,
|
| JSON_INVALID_UNICODE,
|
| JSON_EXPECTED_COLON,
|
| JSON_EXPECTED_COMMA,
|
| JSON_EXPECTED_OBJECT,
|
| JSON_EXPECTED_ARRAY,
|
| JSON_MAX_DEPTH,
|
| ...
|
| };
|
| ```
|
|
|
| Y funciones como:
|
|
|
| ```cpp
|
| GetError();
|
|
|
| GetErrorMessage();
|
|
|
| GetErrorPosition();
|
|
|
| GetLine();
|
|
|
| GetColumn();
|
| ```
|
|
|
| ---
|
|
|
| # Calidad del código
|
|
|
| El código debe ser:
|
|
|
| * modular
|
| * documentado
|
| * fácil de mantener
|
| * altamente optimizado
|
| * preparado para proyectos profesionales
|
|
|
| ---
|
|
|
| # Optimización
|
|
|
| Durante el desarrollo, prioriza siempre el rendimiento sobre la simplicidad.
|
|
|
| Si una técnica mejora significativamente la velocidad aunque aumente la complejidad del código, prefiero la versión más rápida.
|
|
|
| Quiero un parser que pueda competir en rendimiento con implementaciones de alto nivel adaptadas a las limitaciones de MQL5.
|
|
|
| ---
|
|
|
| # Entregable
|
|
|
| El resultado final debe ser un único archivo `.mqh`, completamente funcional, autocontenido y listo para incluir mediante:
|
|
|
| ```cpp
|
| #include "Json.mqh"
|
| ```
|
|
|
| sin depender de ningún otro archivo.
|
|
|
| El objetivo final es obtener el parser JSON más rápido posible que pueda implementarse en MQL5 puro.
|
| */
|
|
|
| //+------------------------------------------------------------------+
|
| //| |
|
| //+------------------------------------------------------------------+
|
| // Desarollo documentado 6:27 PM (Inicio)
|
| /*
|
| Bueno le di el codigo a fable MAX y su puso a pensar, ahora toca espera 10-20 minutos o mas quizas
|
| lop deje aqui.
|
| Buieno va asi:
|
| --------
|
| Architected high-performance JSON parser with iterative algorithm and optimized node access
|
| Architected high-performance JSON parser with iterative algorithm and optimized node access
|
| Architecting high-performance JSON parser implementation.
|
|
|
| Architecting node struct with class pointers and operator overloads.
|
|
|
| Architecting accessor methods and indexing operators.
|
|
|
| Resolviendo detalles de punteros y estructura de datos.
|
|
|
| Escribiendo lógica de escaneo de cadenas.
|
|
|
| Elaborando métodos de reporte de errores y rastreo de posición.
|
|
|
| Refinando manejo de errores y estado inicial del parser.
|
|
|
| Optimizing buffer assignment with ArraySwap for efficiency.
|
|
|
| Finalizando lógica de asignación de claves y nodos.
|
|
|
| Architected high-performance JSON parser with iterative algorithm and optimized node access.
|
|
|
|
|
| Crear el parser JSON de alto rendimiento en MQL5 puro (archivo único Json.mqh)
|
| --------
|
| Esta pensado...
|
|
|
| ---- 18:36
|
| Bueno termino ya lo compile y NI UN SOLO error... vemos si funciona o NO
|
|
|
| void OnStart()
|
| {
|
| //---
|
| CJson json;
|
| StringToCharArray(g_json_data, json.Buffer);
|
|
|
|
|
| Print(json.Parse());
|
| Print(json.GetErrorMessage());
|
| Print(json.GetLine());
|
| Print(json.GetColumn());
|
| }
|
| Tenemos errores
|
|
|
| 2026.08.12 18:40:28.449 BenAi (GBPUSD,H1) false
|
| 2026.08.12 18:40:28.449 BenAi (GBPUSD,H1) secuencia de escape invalida
|
| 2026.08.12 18:40:28.449 BenAi (GBPUSD,H1) 6031
|
| 2026.08.12 18:40:28.449 BenAi (GBPUSD,H1) 27
|
|
|
|
|
| le dire por que pasa eso, le dire esto:
|
| Y de paso ojo aqui hare esto para empezar lo dire que lo solucione
|
| SI SIGUE el mismo error ahi le dire un consejo...
|
| ______
|
| Oye lo estuve probadmno con el twiter.json (el mismo que usan otros parsers
|
| para sus test ) y me da un error de "secuencia de escape invalida", y da false?¿
|
| El texto qeu parece ser el caunsante de dicho error es este:
|
| "description": "\ もうすぐ18歳 “Only One”になる /",
|
| ______
|
|
|
| Bueno enviado veamos que sale, por cierto me da ganas de decirle quizas
|
| por lo vsito casi todas las ias a las que le pregunte lo mismo de json
|
| para los tokens terminan haciendo un struct en vez un long.. la verdad
|
| no se por que sera eso,, creo qeu tendran sus motivos.. pero quizas
|
| le podira decir no exacmante .. proq eyu yaseria trampa si no
|
| inspirante en los mejroes parsers json del mundo.... como rapidjson yyson sonic-rs y simdjson... pero
|
| aplica solo lo que se peuda en MQL5 sin DLL... algo asi
|
| Ahi como se ve no incito a opciar y pegar si no a analizar y raonzar que cosas puedo aprovechar o aprender
|
| de dichos parsers y aplicarlo a MQL5.. algo asi..
|
|
|
|
|
| Bueno note que emepzao a ronzar:
|
| Pensando en resolver error de secuencia de escape inválida en JSON.
|
|
|
| Investigando si conversión de codificación causa el error.
|
|
|
| Replicando lógica de escaneo en Python para diagnosticar.
|
|
|
| Investigó múltiples causas de escape inválido y propuso pruebas empíricas.
|
|
|
| Descargar el twitter.json de referencia para reproducir el fallo
|
|
|
| Exploró alternativas para resolver bloqueo de dominio.
|
|
|
| Intentar descargar twitter.json desde un mirror CDN
|
| nativejson-benchmark twitter.json raw githubusercontent
|
|
|
| 8 resultados
|
|
|
| nativejson-benchmark/data/twitter.json at master · miloyip/nativejson-benchmark
|
| github.com
|
| GitHub - miloyip/nativejson-benchmark: C/C++ JSON parser/generator benchmark · GitHub
|
| github.com
|
| GitHub - serde-rs/json-benchmark: nativejson-benchmark in Rust · GitHub
|
| github.com
|
| Add more JSON libaries · Issue #9 · miloyip/nativejson-benchmark
|
| github.com
|
| GitHub - armink/nativejson-benchmark: C/C++ JSON parser/generator benchmark
|
| github.com
|
| GitHub - Loki-Astari/JsonBenchmark: Json benchmark based on nativejson-benchmark · GitHub
|
| github.com
|
| json-benchmark/README.md at master · serde-rs/json-benchmark
|
| github.com
|
| GitHub - project-gemmi/benchmarking-json: data from a preliminary benchmark of mmJSON parsing
|
| github.com
|
| nativejson-benchmark/data/twitter.json at master · miloyip/nativejson-benchmark
|
|
|
| github.com
|
|
|
| Error al obtener https://github.com/miloyip/nativejson-benchmark/raw/refs/heads/master/data/twitter.json
|
|
|
| Analizando cómo el usuario carga el archivo en MQL5.
|
|
|
| Descifrando el mecanismo de corrupción de caracteres.
|
|
|
| Identificó corrupción de codificación por conversión de caracteres fullwidth a ASCII.
|
|
|
| Añadir el código de error JSON_FILE_ERROR al enum
|
| Json.mqh
|
| +4
|
| -3
|
| Añadir ParseFile() que carga bytes crudos sin conversión de codepage
|
| Json.mqh
|
| +31
|
| -4
|
|
|
| La verdad no se por qeu hara eso cree qeu el error esta en la cofiaiocn dudo que lo este
|
| aunque depende de la implemtancion.. en mi cimplemacion este error no existe literalemtne
|
| daod que no me deiao a verificar esacpes en hoytpath si no luego
|
| No se si de dara cuenta.. pero buenmo..
|
|
|
| Solucion medio estupida que dio bueno le dije que como tal no uso archivos.. si no directo
|
| quizas me falto max contexto
|
| ----
|
| Cambios en esta versión:
|
|
|
| json.ParseFile("twitter.json"): nuevo método que abre el archivo con FILE_BIN, lee los bytes directamente a Buffer con FileReadArray (sin ninguna conversión de texto) y parsea. Si el archivo no se puede abrir, GetError() devuelve el nuevo JSON_FILE_ERROR. Pásale true como segundo parámetro si el archivo está en la carpeta común del terminal.
|
| Tolerancia al BOM UTF-8: al leer bytes crudos, algunos archivos empiezan con EF BB BF; ahora Parse() lo salta automáticamente en lugar de fallar con "carácter inesperado" en la posición 0.
|
|
|
| Prueba con json.ParseFile("twitter.json") y debería parsear el documento completo sin problemas. Si aun cargando los bytes en crudo te diera algún error, dime la posición exacta (GetErrorPosition()) y los bytes alrededor, y lo miramos — pero apostaría a que era la conversión de codepage.
|
|
|
| Json
|
| MQH
|
|
|
| Mi respuesta: (YO NIQUE)
|
| yo lo hago como: #resource "twitter.json" as const string g_json_data
|
| void OnStart()
|
| {
|
| //---
|
| CJson json;
|
| StringToCharArray(g_json_data, json.Buffer);
|
| Print(json.Parse());
|
| Print(json.GetErrorMessage());
|
| Print(json.GetLine());
|
| Print(json.GetColumn());
|
| } no uso eso com ofile es raw... ?¿
|
|
|
| MODELO EMPIEZA A RAZONAR:
|
| Deliberando entre soluciones de codificación de caracteres
|
| ¡Ahí está! El problema es exactamente esa línea:
|
|
|
| cpp
|
| StringToCharArray(g_json_data, json.Buffer); // ← sin codepage
|
|
|
| El recurso como string está bien (MetaEditor lo carga como Unicode correcto), pero StringToCharArray
|
| sin quinto parámetro usa CP_ACP (la página de códigos ANSI de tu Windows), no UTF-8. Al convertir Unicode→ANSI,
|
| Windows aplica el mapeo "best-fit": como \ (U+FF3C) no existe en tu codepage, lo sustituye por su pariente ASCII más parecido...
|
| que es \ (0x5C). Lo mismo pasa con “ ”→" y /→/. Resultado: en tu buffer aparece un \ seguido de espacio y el parser reporta,
|
| correctamente, "secuencia de escape inválida" justo en esa descripción japonesa. El JSON llega ya corrompido al parser.
|
|
|
| Tienes dos formas de arreglarlo:
|
|
|
| Opción A — la realmente "raw" (recomendada): incrusta el recurso como bytes, no como string.
|
| Así no hay ninguna conversión en ningún punto:
|
|
|
| cpp
|
| #resource "twitter.json" as uchar g_json_data[]
|
|
|
| void OnStart()
|
| {
|
| CJson json;
|
| json.Buffer = g_json_data; // copia de bytes tal cual, sin codepages
|
| Print(json.Parse());
|
| Print(json.GetErrorMessage());
|
| }
|
|
|
| Opción B — si prefieres mantener el recurso como string: especifica UTF-8 en la conversión, o directamente usa ParseText,
|
| que ya lo hace por ti:
|
|
|
| cpp
|
| json.ParseText(g_json_data);
|
| // equivale a: StringToCharArray(g_json_data, json.Buffer, 0, WHOLE_ARRAY, CP_UTF8);
|
|
|
| Con cualquiera de las dos, twitter.json debería parsear completo (Parse() → true). La opción A es además la más rápida: el recurso u
|
| char[] son los 617 KB del archivo byte a byte, y el parser trabaja directamente sobre ellos.
|
|
|
| Como regla general para esta librería: Buffer debe contener siempre los bytes UTF-8 originales del documento.
|
| Cualquier paso por string sin CP_UTF8 explícito (o lecturas con FILE_ANSI) puede corromper silenciosamente caracteres no latinos —
|
| y twitter.json, con todo su japonés, es el detector perfecto de ese fallo.
|
| ----
|
| Buenot iene sentido en parte.. lo qeu dice yo la verdad no lo veriiqeu pero si eso de verdad pasara
|
| entonces por que mi swar no falla?¿ por uqe mi jsonparser no falla con esa linea..asi que no se si tomarlo como verdad
|
| creo que no.. ademas es medio tonto lo qeu menciona como ejemplo (copia de arrays con = en vez de hacerlo con un fro o arrcopy)
|
| Bueno hare algo lo pasare a UTF 8 y vemos que pasa
|
| 2026.08.12 18:50:36.488 BenAi (GBPUSD,H1) true
|
| 2026.08.12 18:50:36.488 BenAi (GBPUSD,H1) sin errores
|
| 2026.08.12 18:50:36.488 BenAi (GBPUSD,H1) 0
|
| 2026.08.12 18:50:36.488 BenAi (GBPUSD,H1) 0
|
|
|
|
|
| Bueno funciono todo este tiempo error mio.. asi que no lo culpo auqnue UTF8 vs CPACP no se si uno icrementera bytes o no
|
| si lo hace entonces claude tendra mas desventaja.. cosa uqe parece ser... que paso..
|
|
|
| creo qeu su parsers no es tan fast.. como prce en ScanString hace coass que bueno son cuestionalbes
|
| (Nota hubo un gap dado qeu tuve mis propioes errores dado que pensaba uqe nof ucnaiona pero
|
| no llama a json.Parser asi que estuve en uso botando tokens.. pero ya volvemos... )
|
| Pero probemos..
|
| 2026.08.12 19:14:52.600 BenAi (GBPUSD,H1) Time: 1318010
|
| 2026.08.12 19:14:58.592 BenAi (GBPUSD,H1) Time: 1308737
|
| 2026.08.12 19:15:02.202 BenAi (GBPUSD,H1) Time: 1309672
|
| 2026.08.12 19:15:04.751 BenAi (GBPUSD,H1) Time: 1309841
|
|
|
| Bueno nada mal... ligeramente mejoe que la ultiam vez estos son los datos.. sin iteraciones
|
|
|
| Ahora probemos haciendo una lib fast de verdad...
|
|
|
| ----
|
| Le dije esto:
|
| ----
|
| Bueno error mio todo este tiempo funciona perfecto disculpa..... Oyue crees que podmoas seguir mejorando?¿ este parsers..
|
| es rapido me dio como 1308 ms para parsear 1000 veces twiter.json de 616.7kb algo de 450mb\s+ en mi laptop... la idea es
|
| quizas mejoralro.. estuve investigando sobre parsers fast.... en otros lengujes y encontre vaiors interesntes de los cuales
|
| quizas te podiras guiar? como simdjson?¿ o sonic.rs o yyjson?¿ la idea es tomar sus conceptos y usarlos en MQL5
|
| quizas no como un copia-pega si no adaptarlos (Y SOLO LOS QUE SE PUEDEN.. sin usar una DLL para funciones del sistmea
|
| o asm diurecto) se puede mejorar ese resutaldo o no?¿
|
| ----
|
| Veamos que nos da con ese mensaje le di pistas... lo que yo intuo e sque zuais se peuda bajar
|
| el codigo de esos parsers verlos y sacar ideas de eso... es una
|
| o quizas no.. ya veremos coomo es que el interpresta este prompt..
|
| como tal no se lo dare servido
|
| La idea es que yo para evaluar estos modelos me pondre el rol de "usuario que no sabe mucho"...
|
|
|
|
|
| ****************
|
| ¡Ya decía yo! Me alegro de que funcione. Y ese número no está nada mal: ~450 MB/s en MQL5 puro, con bounds-checking de arrays en cada acceso, es territorio respetable (rustc_serialize ronda 120 MB/s en el mismo archivo, RapidJSON ~320-440 MB/s en C++ nativo).
|
|
|
| Sobre tus referencias, siendo honesto con lo que se puede y no se puede trasladar a MQL5:
|
|
|
| Lo que NO se puede adaptar: el corazón de simdjson y sonic-rs son intrínsecos SIMD (AVX2/NEON: clasificar 32-64 bytes por instrucción)
|
| más trucos como PDEP/PCLMUL. Sin DLL ni asm, eso está fuera. Incluso el truco SWAR (simular SIMD con aritmética de 64 bits en ulong)
|
| no compensa en MQL5: no hay forma de reinterpretar 8 bytes del buffer como un ulong sin pagar 8 cargas + shifts, que cuesta más de lo
|
| que ahorra. Y el "stage 1" de índice estructural de simdjson solo gana porque es SIMD; hecho escalar es una segunda pasada que duplica
|
| tráfico de memoria.
|
|
|
| Lo que SÍ se puede adaptar (sobre todo de yyjson, que es el más "escalar" de los tres): disposición de ramas según frecuencia de bytes,
|
| eliminación de accesos redundantes al array (cada Buffer[pos] paga bounds-check en MQL5, así que leer una vez y comparar en registro es oro),
|
| adelgazar el nodo para menos tráfico de caché, mover contadores del nodo a registros, e inlining manual de las rutinas calientes vía macro
|
| (MQL5 no tiene inline). Déjame implementar una v2 con esto y verificarla contra el oráculo antes de dártela:
|
| ****************
|
|
|
| Bueno en parte le doy la razon no podemos usar SIMD directo en MQL5
|
| Pero el tema del SWAR si pero depend ede como se haga...
|
| Por que intenat replicar SIMDJSOn tal cual (2-fases)no vale la pena estar cargando un monton de chynks
|
| Eso ya lo he probado yo a mano...
|
| Y al final el overedae de la carga de 64 bytes en un ulonbg cragandolo aplicar swar luego comprimie y haceir ors, shifs
|
| ya no compensa lo qeu te ahorras y clasifiacno estrucuta,....
|
| Pero lo qeu si es que el swar se puede aplicar quiruriqiamten en zonas especiias, pro ejemplo solo en string
|
| para dtecatar los cierres vertdares de "" strings... creo que es lo unico...
|
| Y eso es lo que hice..
|
| Ahora sobre usar un "branch precior" como menciona de "dispcinio de ramas" la verdad
|
| no se como lo hara ciermante peude llevar "stats" de los numeros strgins que van aprencio y en base a eso
|
| tener quizas un swith o un bucle donde el "case" con mas % de paricion se ponga...
|
| La verdad harbai qeu probar eso (infiero qeu se reuiqer a eso o quiza salgo mas simple) pero creo
|
| que hacer eso es mucho overhead.. por llevar contadores.... etc etc no se si valga la pena..
|
|
|
| *****
|
| 1. Vía ultrarrápida en el bucle de strings (el punto caliente de twitter.json, que es ~70% strings):
|
| cualquier byte >= 0x5D —minúsculas a-z y todo byte UTF-8 multibyte, o sea todo el japonés— avanza con una sola
|
| comparación en vez de tres. Es la adaptación escalar de la clasificación de bytes de simdjson: no puedo clasificar 32 bytes de golpe,
|
| pero sí elegir el umbral que despacha el 80-90% de los bytes al primer compare.
|
|
|
| 2. Claves y strings de valor en bucles separados: las strings de valor ya no pagan ni el if(isKey) ni el hash por byte;
|
| solo las claves (minoría) lo calculan.
|
|
|
| 3. Una sola lectura de Buffer[pos] por posición: en MQL5 cada acceso a array paga bounds-check, así que los bucles numéricos
|
| que leían dos veces por dígito ahora leen una, y el test de dígito pasa de dos comparaciones a una con el truco sin signo (uchar)
|
| (d-'0') <= 9.
|
|
|
| 4. Contadores en registros (estilo yyjson): count y "último hermano" del contenedor actual viven en variables locales con una
|
| pila por profundidad; el nodo padre ya no se toca en memoria por cada hijo nuevo, y count se escribe una única vez al cerrar }/].
|
| Además el nodo perdió el campo last (48→44 bytes) y la inicialización por nodo bajó de 12 escrituras a 4.
|
|
|
| 5. Salto de espacios inline vía macro (JSON_WS): MQL5 no tiene inline, así que la macro elimina la llamada a función en la ruta
|
| caliente — relevante sobre todo en JSON minificado, donde SkipWS se llamaba millones de veces para no saltar nada.
|
|
|
| Y lo que descarté con motivo: SWAR con ulong (sin reinterpretar bytes como palabras, cargar 8 bytes cuesta más de lo que ahorra),
|
| el índice estructural en dos pasadas de simdjson (solo gana porque es SIMD; escalar duplica el tráfico de memoria) y las tablas de clasificación de 256 entradas (cambiarían comparaciones en registro por otro acceso a array con su bounds-check — dudoso en MQL5).
|
|
|
| La lógica v2 está verificada contra el oráculo igual que la v1 (4.000 documentos, cero regresiones, statuses=100). Mide con este patrón
|
| para que el número sea limpio:
|
| *****
|
| 1. Bueno es una "optimizaicon" espeicia para nuestro dataset no se que tan polemico sea eso.. pero bueno en otros tipos de json
|
| sera peor en todo caso ya lo veremos
|
| 2. Bueno este si esta intenate en vez de digmoas 10 ifas.... en cada iteracion pagas solo un if fuera
|
| para strigns largos peud euqiza ayudar auqneu igual un if el cosoto es peuqeulito pero com miles de iteraciones
|
| y usos quizas en algo ayude..
|
| 3. bueno esto si me parece intenate justo lo qeu yo hago aunque vemor en vez de usar restas usamos xor (en mi caso)
|
| 4. No seria mejor una pila completa?¿ en vez de ciertas cosas?¿ bueno igual una optimiacion para su aruqicta
|
| 5. Bueno esto si era necesario... un call a funcion que se ejeucta un monton de veces peude llegar a costar..
|
| asi qeu
|
| me parece
|
| una optimicion razonable
|
| 6. sobre el swar si tiene rzon en qeu sin reitpracion es medio jodido dao que itnes que armar tu el ulong a mano
|
| lo cual quizas mete mas overhead pero en strgins grandes creo qeu se "apaga" y el costo beneifcifion baja..
|
| y sabeidno como es twi.tesjson esto ayudam mas ....
|
|
|
|
|
| Bueno luego de dar mi opinino sobre esot vemoas que tal..
|
|
|
| */
|
|
|
| // JSON_ACTIVE_CLAUDE_WITH_CONSEJOS
|
|
|
|
|
|
|
| // Codigo 2
|
| //+------------------------------------------------------------------+
|
| //| Json.mqh |
|
| //| Parser JSON de alto rendimiento en MQL5 puro (v2) |
|
| //| |
|
| //| Archivo unico, autocontenido, sin DLL ni dependencias. |
|
| //| Conforme a RFC 8259: objetos, arrays, strings, numeros |
|
| //| (enteros/decimales/exponentes), true/false, null y todas las |
|
| //| secuencias de escape, incluida \uXXXX con pares sustitutos. |
|
| //| |
|
| //| ARQUITECTURA (v2, tecnicas adaptadas de yyjson/simdjson en la |
|
| //| medida en que MQL5 escalar lo permite): |
|
| //| |
|
| //| * Parser ITERATIVO de una sola pasada sobre bytes, sin |
|
| //| recursion (la jerarquia vive en el enlace parent). |
|
| //| * Pool de nodos CONTIGUO (arena) con crecimiento geometrico; |
|
| //| Reset() reutiliza toda la memoria. |
|
| //| * ZERO-COPY + decodificacion PEREZOSA: los nodos guardan |
|
| //| offsets; strings y numeros se convierten solo al consultar. |
|
| //| * Bucle de string con via ultrarapida: bytes >= 0x5D |
|
| //| (minusculas y todo UTF-8 multibyte) salen con UNA sola |
|
| //| comparacion; ideal para documentos con texto/CJK. |
|
| //| * Bucles separados para claves (con hash FNV-1a) y strings |
|
| //| de valor (sin hash): cero coste de hash en la ruta comun. |
|
| //| * UNA sola lectura de Buffer[pos] por posicion (cada acceso |
|
| //| paga bounds-check en MQL5) y test de digito con una unica |
|
| //| comparacion sin signo: (uchar)(d-'0') <= 9. |
|
| //| * Contadores de hijos y ultimo-hermano en REGISTROS con una |
|
| //| pila por profundidad: el nodo padre no se toca por cada |
|
| //| hijo nuevo; count se escribe una vez al cerrar. |
|
| //| * Inicializacion minima de nodos (4 campos), el resto lo |
|
| //| escribe cada rama solo si aplica. |
|
| //| * Salto de espacios INLINE via macro en la ruta caliente |
|
| //| (MQL5 no tiene 'inline'; la macro elimina la llamada). |
|
| //| * Busqueda de claves: filtro hash+longitud antes de comparar |
|
| //| bytes. Acceso secuencial a arrays memoizado (O(1) amort.). |
|
| //| * Linea/columna de error calculadas bajo demanda. |
|
| //| |
|
| //| BENCHMARKING RECOMENDADO: |
|
| //| json.Buffer = datos; |
|
| //| json.Parse(); // calentamiento + reserva |
|
| //| ulong t0 = GetMicrosecondCount(); |
|
| //| for(int i = 0; i < N; i++) { json.Reset(); json.Parse(); } |
|
| //| ulong us = GetMicrosecondCount() - t0; |
|
| //| |
|
| //| USO BASICO: |
|
| //| #include "Json.mqh" |
|
| //| CJson json; |
|
| //| ArraySwap(json.Buffer, MiBuffer); // cero copias |
|
| //| // o: json.Buffer = MiBuffer; // una copia |
|
| //| // o: json.ParseText(texto); // desde string |
|
| //| // o: json.ParseFile("datos.json"); // bytes crudos |
|
| //| if(!json.Parse()) |
|
| //| PrintFormat("%s pos=%d linea=%d col=%d", |
|
| //| json.GetErrorMessage(), |
|
| //| json.GetErrorPosition(), |
|
| //| json.GetLine(), json.GetColumn()); |
|
| //| double balance = json["account"]["balance"].ToDouble(); |
|
| //| // iteracion rapida: |
|
| //| JsonNode t = json["ticks"].First(); |
|
| //| while(t.IsValid()) { /* t.ToDouble() */ t = t.Next(); } |
|
| //| |
|
| //+------------------------------------------------------------------+
|
| #ifndef __JSON_MQH__
|
| #define __JSON_MQH__
|
|
|
| //+------------------------------------------------------------------+
|
| //| Tipos de nodo JSON |
|
| //+------------------------------------------------------------------+
|
| enum ENUM_JSON_TYPE
|
| {
|
| JSON_UNDEFINED = 0, // nodo inexistente / no parseado
|
| JSON_NULL = 1, // null
|
| JSON_BOOL = 2, // true / false
|
| JSON_NUMBER = 3, // numero
|
| JSON_STRING = 4, // string
|
| JSON_ARRAY = 5, // array []
|
| JSON_OBJECT = 6 // objeto {}
|
| };
|
|
|
| //+------------------------------------------------------------------+
|
| //| Codigos de error del parser |
|
| //+------------------------------------------------------------------+
|
| enum ENUM_JSON_ERROR
|
| {
|
| JSON_OK = 0, // sin errores
|
| JSON_NOT_PARSED, // Parse() aun no se ha llamado
|
| JSON_EMPTY_DOCUMENT, // documento vacio
|
| JSON_UNEXPECTED_CHARACTER, // caracter inesperado
|
| JSON_UNEXPECTED_END, // fin de datos inesperado
|
| JSON_INVALID_NUMBER, // numero mal formado
|
| JSON_INVALID_STRING, // string mal formada (control char)
|
| JSON_INVALID_ESCAPE, // secuencia de escape invalida
|
| JSON_INVALID_UNICODE, // \uXXXX con digitos no hexadecimales
|
| JSON_EXPECTED_COLON, // se esperaba ':'
|
| JSON_EXPECTED_COMMA, // se esperaba ',' o cierre
|
| JSON_EXPECTED_KEY, // se esperaba una clave (string)
|
| JSON_TRAILING_CHARACTERS, // datos sobrantes tras el valor raiz
|
| JSON_MAX_DEPTH_EXCEEDED, // anidamiento maximo superado
|
| JSON_FILE_ERROR // no se pudo abrir/leer el archivo
|
| };
|
|
|
| //--- Flags internos por nodo (bitmask en JsonTok::flags)
|
| #define JSON_F_KEY_ESC ((uchar)1) // la clave contiene escapes
|
| #define JSON_F_VAL_ESC ((uchar)2) // el valor string contiene escapes
|
| #define JSON_F_INT ((uchar)4) // numero sin '.' ni exponente
|
| #define JSON_F_TRUE ((uchar)8) // literal booleano == true
|
|
|
| //+------------------------------------------------------------------+
|
| //| JsonTok: nodo interno. POD contiguo, sin strings, solo offsets. |
|
| //| v2: sin campo 'last' (vive en una pila por profundidad durante |
|
| //| el parseo) -> nodo mas pequeno, menos trafico de cache. |
|
| //| NOTA: en nodos escalares, child/count NO se inicializan; todos |
|
| //| los lectores (NodeFirst/NodeSize/ChildAt) comprueban el tipo. |
|
| //+------------------------------------------------------------------+
|
| struct JsonTok
|
| {
|
| int start; // offset del valor en el buffer
|
| int len; // longitud del valor (span crudo)
|
| int kstart; // offset de la clave (miembros de objeto)
|
| int klen; // longitud de la clave (bytes crudos)
|
| uint khash; // hash FNV-1a de la clave
|
| int parent; // indice del padre (-1 = raiz)
|
| int next; // siguiente hermano (-1 = ultimo)
|
| int child; // primer hijo (solo contenedores)
|
| int count; // numero de hijos (solo contenedores)
|
| uchar type; // ENUM_JSON_TYPE
|
| uchar flags; // JSON_F_*
|
| };
|
|
|
| class CJson; // declaracion adelantada
|
|
|
| //+------------------------------------------------------------------+
|
| //| JsonNode: vista ligera sobre un nodo. Se copia por valor; |
|
| //| navegacion segura (un nodo invalido devuelve nodos invalidos). |
|
| //| Valida solo mientras viva el CJson y hasta Parse/Reset/Clear. |
|
| //+------------------------------------------------------------------+
|
| struct JsonNode
|
| {
|
| CJson *m_doc;
|
| int m_idx;
|
|
|
| JsonNode(void) { m_doc = NULL; m_idx = -1; }
|
|
|
| bool IsValid(void) const { return(m_doc != NULL && m_idx >= 0); }
|
| ENUM_JSON_TYPE GetType(void) const;
|
| bool IsObject(void) const { return(GetType() == JSON_OBJECT); }
|
| bool IsArray(void) const { return(GetType() == JSON_ARRAY); }
|
| bool IsString(void) const { return(GetType() == JSON_STRING); }
|
| bool IsNumber(void) const { return(GetType() == JSON_NUMBER); }
|
| bool IsBool(void) const { return(GetType() == JSON_BOOL); }
|
| bool IsNull(void) const { return(GetType() == JSON_NULL); }
|
|
|
| int Size(void) const;
|
| int Count(void) const { return(Size()); }
|
|
|
| JsonNode operator[](const string key);
|
| JsonNode operator[](const int i);
|
| JsonNode Get(const string key);
|
| JsonNode At(const int i);
|
| bool Exists(const string key);
|
| JsonNode First(void); // primer hijo
|
| JsonNode Next(void); // siguiente hermano
|
| string Key(void); // clave de este miembro
|
|
|
| string ToString(const string def = "");
|
| double ToDouble(const double def = 0.0);
|
| long ToInteger(const long def = 0);
|
| bool ToBool(const bool def = false);
|
| };
|
|
|
| //+------------------------------------------------------------------+
|
| //| CJson: parser JSON de alto rendimiento |
|
| //+------------------------------------------------------------------+
|
| class CJson
|
| {
|
| public:
|
| //--- Buffer publico de entrada (bytes UTF-8 del documento).
|
| /// ArraySwap(json.Buffer, MiBuffer); // cero copias
|
| /// json.Buffer = MiBuffer; // una copia
|
| uchar Buffer[];
|
|
|
| private:
|
| JsonTok m_nodes[]; // pool de nodos (arena contigua)
|
| int m_ncount; // nodos usados
|
| int m_ncap; // capacidad del pool
|
| int m_end; // longitud valida del buffer
|
| int m_maxdepth; // limite de anidamiento
|
|
|
| ENUM_JSON_ERROR m_error;
|
| int m_errpos;
|
|
|
| uchar m_tmp[]; // buffer reutilizable de decodificacion
|
| uchar m_keybuf[]; // clave de busqueda en UTF-8
|
|
|
| int m_stk_last[]; // pila: ultimo hermano por profundidad
|
| int m_stk_cnt[]; // pila: numero de hijos por profundidad
|
|
|
| //--- memoizacion de acceso secuencial a arrays
|
| int m_memo_parent;
|
| int m_memo_index;
|
| int m_memo_node;
|
|
|
| //--- tablas estaticas compartidas
|
| static bool s_tables;
|
| static uchar s_hex[256]; // valor hexadecimal (0xFF = invalido)
|
| static double s_pow10[309]; // potencias de 10
|
|
|
| public:
|
| CJson(void)
|
| {
|
| m_ncount = 0;
|
| m_ncap = 0;
|
| m_end = 0;
|
| m_maxdepth = 512;
|
| m_error = JSON_NOT_PARSED;
|
| m_errpos = 0;
|
| m_memo_parent = -1;
|
| m_memo_index = 0;
|
| m_memo_node = -1;
|
| InitTables();
|
| }
|
|
|
| //+---------------------------------------------------------------+
|
| //| API principal |
|
| //+---------------------------------------------------------------+
|
| bool Parse(void) { return(Parse(ArraySize(Buffer))); }
|
| bool Parse(const int length);
|
|
|
| //--- Conveniencia: convierte una string (UTF-8) y parsea
|
| bool ParseText(const string text)
|
| {
|
| int n = StringToCharArray(text, Buffer, 0, WHOLE_ARRAY, CP_UTF8);
|
| if(n > 0)
|
| n--; // descartar el terminador '\0'
|
| return(Parse(n));
|
| }
|
|
|
| //--- Conveniencia: carga un archivo como BYTES CRUDOS (FILE_BIN)
|
| //--- y lo parsea. Via recomendada: sin conversiones de codepage.
|
| bool ParseFile(const string filename, const bool common = false)
|
| {
|
| const int h = FileOpen(filename,
|
| FILE_READ | FILE_BIN | FILE_SHARE_READ | FILE_SHARE_WRITE |
|
| (common ? FILE_COMMON : 0));
|
| if(h == INVALID_HANDLE)
|
| {
|
| m_ncount = 0;
|
| m_error = JSON_FILE_ERROR;
|
| m_errpos = 0;
|
| return(false);
|
| }
|
| const int size = (int)FileSize(h);
|
| ArrayResize(Buffer, size);
|
| const int read = (size > 0 ? (int)FileReadArray(h, Buffer, 0, size) : 0);
|
| FileClose(h);
|
| return(Parse(read));
|
| }
|
|
|
| //--- Reset: descarta el documento, CONSERVA toda la memoria
|
| void Reset(void)
|
| {
|
| m_ncount = 0;
|
| m_end = 0;
|
| m_error = JSON_NOT_PARSED;
|
| m_errpos = 0;
|
| m_memo_parent = -1;
|
| m_memo_node = -1;
|
| }
|
|
|
| //--- Clear: libera toda la memoria
|
| void Clear(void)
|
| {
|
| Reset();
|
| ArrayFree(m_nodes);
|
| ArrayFree(Buffer);
|
| ArrayFree(m_tmp);
|
| ArrayFree(m_keybuf);
|
| ArrayFree(m_stk_last);
|
| ArrayFree(m_stk_cnt);
|
| m_ncap = 0;
|
| }
|
|
|
| //--- Pre-reserva del pool de nodos (evita ArrayResize durante
|
| //--- el parseo si conoces el tamano tipico de tus documentos)
|
| void Reserve(const int nodes) { Grow(nodes); }
|
|
|
| void SetMaxDepth(const int depth) { m_maxdepth = (depth > 0 ? depth : 1); }
|
| int GetMaxDepth(void) const { return(m_maxdepth); }
|
|
|
| //+---------------------------------------------------------------+
|
| //| Errores |
|
| //+---------------------------------------------------------------+
|
| ENUM_JSON_ERROR GetError(void) const { return(m_error); }
|
| bool HasError(void) const { return(m_error != JSON_OK); }
|
| int GetErrorPosition(void) const { return(m_errpos); }
|
|
|
| string GetErrorMessage(void) const
|
| {
|
| switch(m_error)
|
| {
|
| case JSON_OK: return("sin errores");
|
| case JSON_NOT_PARSED: return("Parse() no se ha ejecutado");
|
| case JSON_EMPTY_DOCUMENT: return("documento vacio");
|
| case JSON_UNEXPECTED_CHARACTER: return("caracter inesperado");
|
| case JSON_UNEXPECTED_END: return("fin de datos inesperado");
|
| case JSON_INVALID_NUMBER: return("numero invalido");
|
| case JSON_INVALID_STRING: return("string invalida");
|
| case JSON_INVALID_ESCAPE: return("secuencia de escape invalida");
|
| case JSON_INVALID_UNICODE: return("secuencia \\uXXXX invalida");
|
| case JSON_EXPECTED_COLON: return("se esperaba ':'");
|
| case JSON_EXPECTED_COMMA: return("se esperaba ',' o cierre");
|
| case JSON_EXPECTED_KEY: return("se esperaba una clave entre comillas");
|
| case JSON_TRAILING_CHARACTERS: return("datos sobrantes tras el valor raiz");
|
| case JSON_MAX_DEPTH_EXCEEDED: return("anidamiento maximo superado");
|
| case JSON_FILE_ERROR: return("no se pudo abrir/leer el archivo");
|
| }
|
| return("error desconocido");
|
| }
|
|
|
| //--- Linea/columna del error (1-based; columna en bytes UTF-8).
|
| //--- Se calculan bajo demanda: cero coste durante el parseo.
|
| int GetLine(void) const
|
| {
|
| if(m_error == JSON_OK || m_error == JSON_NOT_PARSED)
|
| return(0);
|
| int line = 1;
|
| const int stop = (m_errpos < m_end ? m_errpos : m_end);
|
| for(int i = 0; i < stop; i++)
|
| if(Buffer[i] == 10)
|
| line++;
|
| return(line);
|
| }
|
|
|
| int GetColumn(void) const
|
| {
|
| if(m_error == JSON_OK || m_error == JSON_NOT_PARSED)
|
| return(0);
|
| int col = 1;
|
| const int stop = (m_errpos < m_end ? m_errpos : m_end);
|
| for(int i = 0; i < stop; i++)
|
| {
|
| if(Buffer[i] == 10)
|
| col = 1;
|
| else
|
| col++;
|
| }
|
| return(col);
|
| }
|
|
|
| //+---------------------------------------------------------------+
|
| //| Acceso al documento |
|
| //+---------------------------------------------------------------+
|
| int RootIndex(void) const { return(m_error == JSON_OK && m_ncount > 0 ? 0 : -1); }
|
|
|
| JsonNode Root(void)
|
| {
|
| JsonNode n;
|
| n.m_doc = GetPointer(this);
|
| n.m_idx = RootIndex();
|
| return(n);
|
| }
|
|
|
| JsonNode operator[](const string key)
|
| {
|
| JsonNode n;
|
| n.m_doc = GetPointer(this);
|
| n.m_idx = FindMember(RootIndex(), key);
|
| return(n);
|
| }
|
|
|
| JsonNode operator[](const int i)
|
| {
|
| JsonNode n;
|
| n.m_doc = GetPointer(this);
|
| n.m_idx = ChildAt(RootIndex(), i);
|
| return(n);
|
| }
|
|
|
| ENUM_JSON_TYPE GetType(void) const { return(NodeType(RootIndex())); }
|
| bool IsObject(void) const { return(GetType() == JSON_OBJECT); }
|
| bool IsArray(void) const { return(GetType() == JSON_ARRAY); }
|
| bool Exists(const string key) { return(FindMember(RootIndex(), key) >= 0); }
|
| int Size(void) const { return(NodeSize(RootIndex())); }
|
| int Count(void) const { return(Size()); }
|
|
|
| int NodesUsed(void) const { return(m_ncount); }
|
|
|
| //+---------------------------------------------------------------+
|
| //| API interna por indice de nodo (usada por JsonNode). |
|
| //| Publica por necesidad (MQL5 no tiene 'friend'). |
|
| //+---------------------------------------------------------------+
|
| ENUM_JSON_TYPE NodeType(const int i) const
|
| {
|
| if(i < 0 || i >= m_ncount)
|
| return(JSON_UNDEFINED);
|
| return((ENUM_JSON_TYPE)m_nodes[i].type);
|
| }
|
|
|
| //--- v2: child/count solo son validos en contenedores
|
| int NodeSize(const int i) const
|
| {
|
| if(i < 0 || i >= m_ncount)
|
| return(0);
|
| const uchar t = m_nodes[i].type;
|
| if(t != (uchar)JSON_ARRAY && t != (uchar)JSON_OBJECT)
|
| return(0);
|
| return(m_nodes[i].count);
|
| }
|
|
|
| int NodeFirst(const int i) const
|
| {
|
| if(i < 0 || i >= m_ncount)
|
| return(-1);
|
| const uchar t = m_nodes[i].type;
|
| if(t != (uchar)JSON_ARRAY && t != (uchar)JSON_OBJECT)
|
| return(-1);
|
| return(m_nodes[i].child);
|
| }
|
|
|
| int NodeNext(const int i) const
|
| {
|
| if(i < 0 || i >= m_ncount)
|
| return(-1);
|
| return(m_nodes[i].next);
|
| }
|
|
|
| string NodeKey(const int i)
|
| {
|
| if(i < 0 || i >= m_ncount || m_nodes[i].klen <= 0)
|
| return("");
|
| return(DecodeSpan(m_nodes[i].kstart, m_nodes[i].klen,
|
| (m_nodes[i].flags & JSON_F_KEY_ESC) != 0));
|
| }
|
|
|
| string NodeText(const int i, const string def)
|
| {
|
| if(i < 0 || i >= m_ncount)
|
| return(def);
|
| switch(m_nodes[i].type)
|
| {
|
| case JSON_STRING:
|
| return(DecodeSpan(m_nodes[i].start, m_nodes[i].len,
|
| (m_nodes[i].flags & JSON_F_VAL_ESC) != 0));
|
| case JSON_NUMBER:
|
| return(CharArrayToString(Buffer, m_nodes[i].start, m_nodes[i].len, CP_UTF8));
|
| case JSON_BOOL:
|
| return((m_nodes[i].flags & JSON_F_TRUE) != 0 ? "true" : "false");
|
| case JSON_NULL:
|
| return("null");
|
| }
|
| return(def);
|
| }
|
|
|
| double NodeDouble(const int i, const double def) const
|
| {
|
| if(i < 0 || i >= m_ncount)
|
| return(def);
|
| const uchar t = m_nodes[i].type;
|
| if(t == JSON_NUMBER)
|
| return(SpanToDouble(m_nodes[i].start, m_nodes[i].start + m_nodes[i].len));
|
| if(t == JSON_BOOL)
|
| return((m_nodes[i].flags & JSON_F_TRUE) != 0 ? 1.0 : 0.0);
|
| if(t == JSON_STRING && m_nodes[i].len > 0)
|
| {
|
| const uchar c = Buffer[m_nodes[i].start];
|
| if(c == '-' || (uchar)(c - '0') <= 9)
|
| return(SpanToDouble(m_nodes[i].start, m_nodes[i].start + m_nodes[i].len));
|
| }
|
| return(def);
|
| }
|
|
|
| long NodeLong(const int i, const long def) const
|
| {
|
| if(i < 0 || i >= m_ncount)
|
| return(def);
|
| const uchar t = m_nodes[i].type;
|
| if(t == JSON_NUMBER)
|
| {
|
| if((m_nodes[i].flags & JSON_F_INT) != 0)
|
| return(SpanToLong(m_nodes[i].start, m_nodes[i].start + m_nodes[i].len));
|
| return((long)SpanToDouble(m_nodes[i].start, m_nodes[i].start + m_nodes[i].len));
|
| }
|
| if(t == JSON_BOOL)
|
| return((m_nodes[i].flags & JSON_F_TRUE) != 0 ? 1 : 0);
|
| if(t == JSON_STRING && m_nodes[i].len > 0)
|
| {
|
| const uchar c = Buffer[m_nodes[i].start];
|
| if(c == '-' || (uchar)(c - '0') <= 9)
|
| return((long)SpanToDouble(m_nodes[i].start, m_nodes[i].start + m_nodes[i].len));
|
| }
|
| return(def);
|
| }
|
|
|
| bool NodeBool(const int i, const bool def)
|
| {
|
| if(i < 0 || i >= m_ncount)
|
| return(def);
|
| const uchar t = m_nodes[i].type;
|
| if(t == JSON_BOOL)
|
| return((m_nodes[i].flags & JSON_F_TRUE) != 0);
|
| if(t == JSON_NUMBER)
|
| return(SpanToDouble(m_nodes[i].start, m_nodes[i].start + m_nodes[i].len) != 0.0);
|
| if(t == JSON_STRING)
|
| {
|
| const string s = NodeText(i, "");
|
| if(s == "true")
|
| return(true);
|
| if(s == "false")
|
| return(false);
|
| }
|
| return(def);
|
| }
|
|
|
| //--- Busqueda de miembro: filtro hash + longitud -> bytes.
|
| int FindMember(const int obj, const string key)
|
| {
|
| if(obj < 0 || obj >= m_ncount || m_nodes[obj].type != (uchar)JSON_OBJECT)
|
| return(-1);
|
|
|
| int klen = StringToCharArray(key, m_keybuf, 0, WHOLE_ARRAY, CP_UTF8);
|
| if(klen > 0)
|
| klen--;
|
|
|
| uint h = 0x811C9DC5;
|
| for(int i = 0; i < klen; i++)
|
| h = (h ^ m_keybuf[i]) * 16777619;
|
|
|
| int n = m_nodes[obj].child;
|
| while(n >= 0)
|
| {
|
| if((m_nodes[n].flags & JSON_F_KEY_ESC) == 0)
|
| {
|
| if(m_nodes[n].khash == h && m_nodes[n].klen == klen)
|
| {
|
| const int ks = m_nodes[n].kstart;
|
| int j = 0;
|
| while(j < klen && Buffer[ks + j] == m_keybuf[j])
|
| j++;
|
| if(j == klen)
|
| return(n);
|
| }
|
| }
|
| else
|
| {
|
| if(DecodeSpan(m_nodes[n].kstart, m_nodes[n].klen, true) == key)
|
| return(n);
|
| }
|
| n = m_nodes[n].next;
|
| }
|
| return(-1);
|
| }
|
|
|
| //--- i-esimo hijo con memoizacion (acceso secuencial O(1) amort.)
|
| int ChildAt(const int cont, const int i)
|
| {
|
| if(cont < 0 || cont >= m_ncount || i < 0)
|
| return(-1);
|
| const uchar t = m_nodes[cont].type;
|
| if(t != (uchar)JSON_ARRAY && t != (uchar)JSON_OBJECT)
|
| return(-1);
|
| if(i >= m_nodes[cont].count)
|
| return(-1);
|
|
|
| int n, k;
|
| if(m_memo_parent == cont && m_memo_node >= 0 && m_memo_index <= i)
|
| {
|
| n = m_memo_node;
|
| k = m_memo_index;
|
| }
|
| else
|
| {
|
| n = m_nodes[cont].child;
|
| k = 0;
|
| }
|
| while(k < i)
|
| {
|
| n = m_nodes[n].next;
|
| k++;
|
| }
|
| m_memo_parent = cont;
|
| m_memo_index = i;
|
| m_memo_node = n;
|
| return(n);
|
| }
|
|
|
| private:
|
| //+---------------------------------------------------------------+
|
| //| Nucleo del parser |
|
| //+---------------------------------------------------------------+
|
| bool Fail(const ENUM_JSON_ERROR err, const int pos)
|
| {
|
| m_error = err;
|
| m_errpos = pos;
|
| return(false);
|
| }
|
|
|
| void Grow(const int need)
|
| {
|
| int cap = (need < 16 ? 16 : need);
|
| if(cap <= m_ncap)
|
| return;
|
| ArrayResize(m_nodes, cap);
|
| m_ncap = cap;
|
| }
|
|
|
| //--- v2: asignacion pura; la inicializacion minima y el enlace
|
| //--- de hermanos se hacen inline en Parse (menos escrituras).
|
| int NewNode(void)
|
| {
|
| if(m_ncount >= m_ncap)
|
| Grow(m_ncap * 2);
|
| return(m_ncount++);
|
| }
|
|
|
| int SkipWS(int pos) const
|
| {
|
| const int end = m_end;
|
| while(pos < end)
|
| {
|
| const uchar c = Buffer[pos];
|
| if(c > 32)
|
| return(pos);
|
| if(c != 32 && c != 9 && c != 10 && c != 13)
|
| return(pos);
|
| pos++;
|
| }
|
| return(pos);
|
| }
|
|
|
| //--- Escaneo de STRING DE VALOR (sin hash). Bucle v2:
|
| //--- * c >= 0x5D -> via ultrarapida, 1 comparacion
|
| //--- (minusculas a-z, ']','^','_','`','{','|','}','~' y TODO
|
| //--- byte UTF-8 multibyte >= 0x80: texto CJK, acentos...)
|
| bool ScanStr(int &pos, int &start, int &len, uchar &escaped)
|
| {
|
| const int end = m_end;
|
| pos++;
|
| start = pos;
|
| escaped = 0;
|
|
|
| while(pos < end)
|
| {
|
| const uchar c = Buffer[pos];
|
| if(c >= 0x5D)
|
| {
|
| pos++;
|
| continue;
|
| }
|
| if(c == '"')
|
| {
|
| len = pos - start;
|
| pos++;
|
| return(true);
|
| }
|
| if(c == '\\')
|
| {
|
| escaped = 1;
|
| pos++;
|
| if(pos >= end)
|
| return(Fail(JSON_UNEXPECTED_END, pos));
|
| pos++;
|
| continue;
|
| }
|
| if(c >= 0x20)
|
| {
|
| pos++;
|
| continue;
|
| }
|
| return(Fail(JSON_INVALID_STRING, pos));
|
| }
|
| return(Fail(JSON_UNEXPECTED_END, pos));
|
| }
|
|
|
| //--- Escaneo de CLAVE (identico + hash FNV-1a). Bucle separado
|
| //--- para que las strings de valor no paguen el hash.
|
| bool ScanKey(int &pos, int &start, int &len, uchar &escaped, uint &hash)
|
| {
|
| const int end = m_end;
|
| pos++;
|
| start = pos;
|
| escaped = 0;
|
| uint h = 0x811C9DC5;
|
|
|
| while(pos < end)
|
| {
|
| const uchar c = Buffer[pos];
|
| if(c >= 0x5D)
|
| {
|
| h = (h ^ c) * 16777619;
|
| pos++;
|
| continue;
|
| }
|
| if(c == '"')
|
| {
|
| len = pos - start;
|
| pos++;
|
| hash = h;
|
| return(true);
|
| }
|
| if(c == '\\')
|
| {
|
| escaped = 1;
|
| pos++;
|
| if(pos >= end)
|
| return(Fail(JSON_UNEXPECTED_END, pos));
|
| const uchar e = Buffer[pos];
|
| if(e == 'u')
|
| {
|
| if(pos + 4 >= end)
|
| return(Fail(JSON_UNEXPECTED_END, pos));
|
| if(s_hex[Buffer[pos + 1]] == 0xFF || s_hex[Buffer[pos + 2]] == 0xFF ||
|
| s_hex[Buffer[pos + 3]] == 0xFF || s_hex[Buffer[pos + 4]] == 0xFF)
|
| return(Fail(JSON_INVALID_UNICODE, pos - 1));
|
| pos += 5;
|
| continue;
|
| }
|
| if(e == '"' || e == '\\' || e == '/' || e == 'b' ||
|
| e == 'f' || e == 'n' || e == 'r' || e == 't')
|
| {
|
| pos++;
|
| continue;
|
| }
|
| return(Fail(JSON_INVALID_ESCAPE, pos - 1));
|
| }
|
| if(c >= 0x20)
|
| {
|
| h = (h ^ c) * 16777619;
|
| pos++;
|
| continue;
|
| }
|
| return(Fail(JSON_INVALID_STRING, pos));
|
| }
|
| return(Fail(JSON_UNEXPECTED_END, pos));
|
| }
|
|
|
| //--- Escaneo/validacion de numero. v2: UNA lectura de Buffer[pos]
|
| //--- por posicion y test de digito con una unica comparacion.
|
| bool ScanNumber(int &pos, const int node)
|
| {
|
| const int end = m_end;
|
| const int start = pos;
|
| uchar isint = 1;
|
|
|
| if(Buffer[pos] == '-')
|
| {
|
| pos++;
|
| if(pos >= end)
|
| return(Fail(JSON_INVALID_NUMBER, pos));
|
| }
|
|
|
| const uchar c = Buffer[pos];
|
| if(c == '0')
|
| pos++;
|
| else
|
| if((uchar)(c - '1') <= 8)
|
| {
|
| pos++;
|
| while(pos < end)
|
| {
|
| const uchar d = Buffer[pos];
|
| if((uchar)(d - '0') > 9)
|
| break;
|
| pos++;
|
| }
|
| }
|
| else
|
| return(Fail(JSON_INVALID_NUMBER, pos));
|
|
|
| if(pos < end && Buffer[pos] == '.')
|
| {
|
| isint = 0;
|
| pos++;
|
| if(pos >= end || (uchar)(Buffer[pos] - '0') > 9)
|
| return(Fail(JSON_INVALID_NUMBER, pos));
|
| pos++;
|
| while(pos < end)
|
| {
|
| const uchar d = Buffer[pos];
|
| if((uchar)(d - '0') > 9)
|
| break;
|
| pos++;
|
| }
|
| }
|
|
|
| if(pos < end && (Buffer[pos] == 'e' || Buffer[pos] == 'E'))
|
| {
|
| isint = 0;
|
| pos++;
|
| if(pos < end && (Buffer[pos] == '+' || Buffer[pos] == '-'))
|
| pos++;
|
| if(pos >= end || (uchar)(Buffer[pos] - '0') > 9)
|
| return(Fail(JSON_INVALID_NUMBER, pos));
|
| pos++;
|
| while(pos < end)
|
| {
|
| const uchar d = Buffer[pos];
|
| if((uchar)(d - '0') > 9)
|
| break;
|
| pos++;
|
| }
|
| }
|
|
|
| m_nodes[node].type = (uchar)JSON_NUMBER;
|
| m_nodes[node].start = start;
|
| m_nodes[node].len = pos - start;
|
| if(isint != 0)
|
| m_nodes[node].flags |= JSON_F_INT;
|
| return(true);
|
| }
|
|
|
| //--- Conversion rapida bytes -> double (18 digitos significativos)
|
| double SpanToDouble(int p, const int end) const
|
| {
|
| bool neg = false;
|
| if(p < end && Buffer[p] == '-')
|
| {
|
| neg = true;
|
| p++;
|
| }
|
|
|
| long mant = 0;
|
| int ndig = 0;
|
| int dexp = 0;
|
|
|
| while(p < end)
|
| {
|
| const uchar c = Buffer[p];
|
| if((uchar)(c - '0') > 9)
|
| break;
|
| if(ndig < 18)
|
| {
|
| mant = mant * 10 + (c - '0');
|
| if(mant != 0)
|
| ndig++;
|
| }
|
| else
|
| dexp++;
|
| p++;
|
| }
|
|
|
| if(p < end && Buffer[p] == '.')
|
| {
|
| p++;
|
| while(p < end)
|
| {
|
| const uchar c = Buffer[p];
|
| if((uchar)(c - '0') > 9)
|
| break;
|
| if(ndig < 18)
|
| {
|
| mant = mant * 10 + (c - '0');
|
| if(mant != 0)
|
| ndig++;
|
| dexp--;
|
| }
|
| p++;
|
| }
|
| }
|
|
|
| if(p < end && (Buffer[p] == 'e' || Buffer[p] == 'E'))
|
| {
|
| p++;
|
| bool eneg = false;
|
| if(p < end && (Buffer[p] == '+' || Buffer[p] == '-'))
|
| {
|
| eneg = (Buffer[p] == '-');
|
| p++;
|
| }
|
| int ex = 0;
|
| while(p < end)
|
| {
|
| const uchar c = Buffer[p];
|
| if((uchar)(c - '0') > 9)
|
| break;
|
| if(ex < 100000)
|
| ex = ex * 10 + (c - '0');
|
| p++;
|
| }
|
| dexp += (eneg ? -ex : ex);
|
| }
|
|
|
| double r = (double)mant;
|
| if(dexp > 0)
|
| {
|
| while(dexp > 308)
|
| {
|
| r *= s_pow10[308];
|
| dexp -= 308;
|
| }
|
| r *= s_pow10[dexp];
|
| }
|
| else
|
| if(dexp < 0)
|
| {
|
| int e2 = -dexp;
|
| while(e2 > 308)
|
| {
|
| r /= s_pow10[308];
|
| e2 -= 308;
|
| }
|
| r /= s_pow10[e2];
|
| }
|
| return(neg ? -r : r);
|
| }
|
|
|
| long SpanToLong(int p, const int end) const
|
| {
|
| bool neg = false;
|
| if(p < end && Buffer[p] == '-')
|
| {
|
| neg = true;
|
| p++;
|
| }
|
| long v = 0;
|
| while(p < end)
|
| {
|
| const uchar c = Buffer[p];
|
| if((uchar)(c - '0') > 9)
|
| break;
|
| v = v * 10 + (c - '0');
|
| p++;
|
| }
|
| return(neg ? -v : v);
|
| }
|
|
|
| //--- Decodificacion perezosa de spans. Via rapida sin escapes;
|
| //--- via con escapes usa buffer reutilizable y genera UTF-8 real
|
| //--- para \uXXXX, incluidos pares sustitutos (> U+FFFF).
|
| string DecodeSpan(const int start, const int len, const bool escaped)
|
| {
|
| if(len <= 0)
|
| return("");
|
| if(!escaped)
|
| return(CharArrayToString(Buffer, start, len, CP_UTF8));
|
|
|
| if(ArraySize(m_tmp) < len)
|
| ArrayResize(m_tmp, len, 256);
|
|
|
| int o = 0;
|
| int p = start;
|
| const int end = start + len;
|
|
|
| while(p < end)
|
| {
|
| const uchar c = Buffer[p];
|
| if(c != '\\')
|
| {
|
| m_tmp[o++] = c;
|
| p++;
|
| continue;
|
| }
|
| p++;
|
| const uchar e = Buffer[p++];
|
| switch(e)
|
| {
|
| case '"': m_tmp[o++] = (uchar)'"'; break;
|
| case '\\': m_tmp[o++] = (uchar)'\\'; break;
|
| case '/': m_tmp[o++] = (uchar)'/'; break;
|
| case 'b': m_tmp[o++] = 8; break;
|
| case 'f': m_tmp[o++] = 12; break;
|
| case 'n': m_tmp[o++] = 10; break;
|
| case 'r': m_tmp[o++] = 13; break;
|
| case 't': m_tmp[o++] = 9; break;
|
| case 'u':
|
| {
|
| uint cp = ((uint)s_hex[Buffer[p]] << 12) |
|
| ((uint)s_hex[Buffer[p + 1]] << 8) |
|
| ((uint)s_hex[Buffer[p + 2]] << 4) |
|
| (uint)s_hex[Buffer[p + 3]];
|
| p += 4;
|
|
|
| if(cp >= 0xD800 && cp <= 0xDBFF && p + 6 <= end &&
|
| Buffer[p] == '\\' && Buffer[p + 1] == 'u')
|
| {
|
| const uint lo = ((uint)s_hex[Buffer[p + 2]] << 12) |
|
| ((uint)s_hex[Buffer[p + 3]] << 8) |
|
| ((uint)s_hex[Buffer[p + 4]] << 4) |
|
| (uint)s_hex[Buffer[p + 5]];
|
| if(lo >= 0xDC00 && lo <= 0xDFFF)
|
| {
|
| cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
|
| p += 6;
|
| }
|
| }
|
|
|
| if(cp < 0x80)
|
| m_tmp[o++] = (uchar)cp;
|
| else
|
| if(cp < 0x800)
|
| {
|
| m_tmp[o++] = (uchar)(0xC0 | (cp >> 6));
|
| m_tmp[o++] = (uchar)(0x80 | (cp & 0x3F));
|
| }
|
| else
|
| if(cp < 0x10000)
|
| {
|
| m_tmp[o++] = (uchar)(0xE0 | (cp >> 12));
|
| m_tmp[o++] = (uchar)(0x80 | ((cp >> 6) & 0x3F));
|
| m_tmp[o++] = (uchar)(0x80 | (cp & 0x3F));
|
| }
|
| else
|
| {
|
| m_tmp[o++] = (uchar)(0xF0 | (cp >> 18));
|
| m_tmp[o++] = (uchar)(0x80 | ((cp >> 12) & 0x3F));
|
| m_tmp[o++] = (uchar)(0x80 | ((cp >> 6) & 0x3F));
|
| m_tmp[o++] = (uchar)(0x80 | (cp & 0x3F));
|
| }
|
| break;
|
| }
|
| default:
|
| m_tmp[o++] = e;
|
| break;
|
| }
|
| }
|
| return(CharArrayToString(m_tmp, 0, o, CP_UTF8));
|
| }
|
|
|
| static void InitTables(void)
|
| {
|
| if(s_tables)
|
| return;
|
| for(int i = 0; i < 256; i++)
|
| s_hex[i] = 0xFF;
|
| for(int i = '0'; i <= '9'; i++)
|
| s_hex[i] = (uchar)(i - '0');
|
| for(int i = 'a'; i <= 'f'; i++)
|
| s_hex[i] = (uchar)(i - 'a' + 10);
|
| for(int i = 'A'; i <= 'F'; i++)
|
| s_hex[i] = (uchar)(i - 'A' + 10);
|
| s_pow10[0] = 1.0;
|
| for(int i = 1; i <= 308; i++)
|
| s_pow10[i] = s_pow10[i - 1] * 10.0;
|
| s_tables = true;
|
| }
|
| };
|
|
|
| //--- Salto de espacios INLINE para la ruta caliente de Parse.
|
| //--- Evita la llamada a SkipWS: la mayoria de las veces el primer
|
| //--- byte ya no es espacio y sale con una comparacion.
|
| #define JSON_WS(p) while(p < end) { const uchar w_ = Buffer[p]; if(w_ > 32) break; if(w_ != 32 && w_ != 9 && w_ != 10 && w_ != 13) break; p++; }
|
|
|
| //+------------------------------------------------------------------+
|
| //| CJson::Parse - nucleo iterativo de una sola pasada (v2). |
|
| //| Jerarquia via enlaces parent (sin recursion). 'ultimo hermano' y |
|
| //| 'numero de hijos' del contenedor actual viven en registros; se |
|
| //| salvan/restauran en una pila por profundidad al abrir/cerrar. |
|
| //+------------------------------------------------------------------+
|
| bool CJson::Parse(const int length)
|
| {
|
| m_ncount = 0;
|
| m_error = JSON_OK;
|
| m_errpos = 0;
|
| m_memo_parent = -1;
|
| m_memo_node = -1;
|
|
|
| m_end = length;
|
| const int bs = ArraySize(Buffer);
|
| if(m_end > bs)
|
| m_end = bs;
|
|
|
| //--- recortar NULs finales (StringToCharArray suele anadir '\0')
|
| while(m_end > 0 && Buffer[m_end - 1] == 0)
|
| m_end--;
|
|
|
| //--- pre-reserva del pool (Reserve() permite ajustarla a mano)
|
| if(m_ncap < m_end / 8 + 16)
|
| Grow(m_end / 8 + 16);
|
|
|
| //--- pilas por profundidad (se conservan entre Reset)
|
| if(ArraySize(m_stk_last) < m_maxdepth + 2)
|
| {
|
| ArrayResize(m_stk_last, m_maxdepth + 2);
|
| ArrayResize(m_stk_cnt, m_maxdepth + 2);
|
| }
|
|
|
| const int end = m_end;
|
| int pos = 0;
|
|
|
| //--- saltar el BOM UTF-8 (EF BB BF) si el documento lo trae
|
| if(end >= 3 && Buffer[0] == 0xEF && Buffer[1] == 0xBB && Buffer[2] == 0xBF)
|
| pos = 3;
|
|
|
| JSON_WS(pos);
|
| if(pos >= end)
|
| return(Fail(JSON_EMPTY_DOCUMENT, pos));
|
|
|
| int parent = -1; // contenedor actual (-1 = nivel raiz)
|
| int depth = 0;
|
| int lastChild = -1; // ultimo hijo del contenedor actual (registro)
|
| int childCount = 0; // hijos del contenedor actual (registro)
|
| bool haskey = false;
|
| int kstart = 0, klen = 0;
|
| uint khash = 0;
|
| uchar kesc = 0;
|
|
|
| while(true)
|
| {
|
| //================================================================
|
| // FASE 1: parsear un valor en 'pos'
|
| //================================================================
|
| if(pos >= end)
|
| return(Fail(JSON_UNEXPECTED_END, pos));
|
|
|
| uchar c = Buffer[pos];
|
| const int node = NewNode();
|
|
|
| //--- v2: inicializacion minima + enlace de hermanos inline.
|
| //--- child/count solo se escriben en las ramas de contenedor.
|
| m_nodes[node].parent = parent;
|
| m_nodes[node].next = -1;
|
| m_nodes[node].klen = 0;
|
| m_nodes[node].flags = 0;
|
| if(parent >= 0)
|
| {
|
| if(lastChild < 0)
|
| m_nodes[parent].child = node;
|
| else
|
| m_nodes[lastChild].next = node;
|
| lastChild = node;
|
| childCount++;
|
| }
|
| if(haskey)
|
| {
|
| m_nodes[node].kstart = kstart;
|
| m_nodes[node].klen = klen;
|
| m_nodes[node].khash = khash;
|
| if(kesc != 0)
|
| m_nodes[node].flags |= JSON_F_KEY_ESC;
|
| haskey = false;
|
| }
|
|
|
| //--- dispatcher ordenado por frecuencia tipica
|
| if(c == '"')
|
| {
|
| int s = 0, l = 0;
|
| uchar esc = 0;
|
| if(!ScanStr(pos, s, l, esc))
|
| return(false);
|
| m_nodes[node].type = (uchar)JSON_STRING;
|
| m_nodes[node].start = s;
|
| m_nodes[node].len = l;
|
| if(esc != 0)
|
| m_nodes[node].flags |= JSON_F_VAL_ESC;
|
| }
|
| else
|
| if((uchar)(c - '0') <= 9 || c == '-')
|
| {
|
| if(!ScanNumber(pos, node))
|
| return(false);
|
| }
|
| else
|
| if(c == '{')
|
| {
|
| m_nodes[node].type = (uchar)JSON_OBJECT;
|
| m_nodes[node].start = pos;
|
| m_nodes[node].child = -1;
|
| m_nodes[node].count = 0;
|
| pos++;
|
| JSON_WS(pos);
|
| if(pos >= end)
|
| return(Fail(JSON_UNEXPECTED_END, pos));
|
| if(Buffer[pos] == '}')
|
| {
|
| pos++; // objeto vacio
|
| m_nodes[node].len = pos - m_nodes[node].start;
|
| }
|
| else
|
| {
|
| depth++;
|
| if(depth > m_maxdepth)
|
| return(Fail(JSON_MAX_DEPTH_EXCEEDED, pos));
|
| m_stk_last[depth] = lastChild; // "push" O(1)
|
| m_stk_cnt[depth] = childCount;
|
| lastChild = -1;
|
| childCount = 0;
|
| parent = node;
|
| if(Buffer[pos] != '"')
|
| return(Fail(JSON_EXPECTED_KEY, pos));
|
| if(!ScanKey(pos, kstart, klen, kesc, khash))
|
| return(false);
|
| JSON_WS(pos);
|
| if(pos >= end || Buffer[pos] != ':')
|
| return(Fail(JSON_EXPECTED_COLON, pos));
|
| pos++;
|
| JSON_WS(pos);
|
| haskey = true;
|
| continue; // parsear el valor
|
| }
|
| }
|
| else
|
| if(c == '[')
|
| {
|
| m_nodes[node].type = (uchar)JSON_ARRAY;
|
| m_nodes[node].start = pos;
|
| m_nodes[node].child = -1;
|
| m_nodes[node].count = 0;
|
| pos++;
|
| JSON_WS(pos);
|
| if(pos >= end)
|
| return(Fail(JSON_UNEXPECTED_END, pos));
|
| if(Buffer[pos] == ']')
|
| {
|
| pos++; // array vacio
|
| m_nodes[node].len = pos - m_nodes[node].start;
|
| }
|
| else
|
| {
|
| depth++;
|
| if(depth > m_maxdepth)
|
| return(Fail(JSON_MAX_DEPTH_EXCEEDED, pos));
|
| m_stk_last[depth] = lastChild;
|
| m_stk_cnt[depth] = childCount;
|
| lastChild = -1;
|
| childCount = 0;
|
| parent = node;
|
| continue; // parsear 1er elemento
|
| }
|
| }
|
| else
|
| if(c == 't')
|
| {
|
| if(pos + 4 <= end && Buffer[pos + 1] == 'r' &&
|
| Buffer[pos + 2] == 'u' && Buffer[pos + 3] == 'e')
|
| {
|
| m_nodes[node].type = (uchar)JSON_BOOL;
|
| m_nodes[node].flags |= JSON_F_TRUE;
|
| m_nodes[node].start = pos;
|
| m_nodes[node].len = 4;
|
| pos += 4;
|
| }
|
| else
|
| return(Fail(JSON_UNEXPECTED_CHARACTER, pos));
|
| }
|
| else
|
| if(c == 'f')
|
| {
|
| if(pos + 5 <= end && Buffer[pos + 1] == 'a' && Buffer[pos + 2] == 'l' &&
|
| Buffer[pos + 3] == 's' && Buffer[pos + 4] == 'e')
|
| {
|
| m_nodes[node].type = (uchar)JSON_BOOL;
|
| m_nodes[node].start = pos;
|
| m_nodes[node].len = 5;
|
| pos += 5;
|
| }
|
| else
|
| return(Fail(JSON_UNEXPECTED_CHARACTER, pos));
|
| }
|
| else
|
| if(c == 'n')
|
| {
|
| if(pos + 4 <= end && Buffer[pos + 1] == 'u' &&
|
| Buffer[pos + 2] == 'l' && Buffer[pos + 3] == 'l')
|
| {
|
| m_nodes[node].type = (uchar)JSON_NULL;
|
| m_nodes[node].start = pos;
|
| m_nodes[node].len = 4;
|
| pos += 4;
|
| }
|
| else
|
| return(Fail(JSON_UNEXPECTED_CHARACTER, pos));
|
| }
|
| else
|
| return(Fail(JSON_UNEXPECTED_CHARACTER, pos));
|
|
|
| //================================================================
|
| // FASE 2: valor completado -> resolver comas y cierres
|
| //================================================================
|
| bool needValue = false;
|
| while(!needValue)
|
| {
|
| if(parent < 0)
|
| {
|
| JSON_WS(pos);
|
| if(pos < end)
|
| return(Fail(JSON_TRAILING_CHARACTERS, pos));
|
| return(true);
|
| }
|
|
|
| JSON_WS(pos);
|
| if(pos >= end)
|
| return(Fail(JSON_UNEXPECTED_END, pos));
|
| c = Buffer[pos];
|
|
|
| if(m_nodes[parent].type == (uchar)JSON_OBJECT)
|
| {
|
| if(c == ',')
|
| {
|
| pos++;
|
| JSON_WS(pos);
|
| if(pos >= end || Buffer[pos] != '"')
|
| return(Fail(JSON_EXPECTED_KEY, pos));
|
| if(!ScanKey(pos, kstart, klen, kesc, khash))
|
| return(false);
|
| JSON_WS(pos);
|
| if(pos >= end || Buffer[pos] != ':')
|
| return(Fail(JSON_EXPECTED_COLON, pos));
|
| pos++;
|
| JSON_WS(pos);
|
| haskey = true;
|
| needValue = true;
|
| }
|
| else
|
| if(c == '}')
|
| {
|
| pos++;
|
| m_nodes[parent].len = pos - m_nodes[parent].start;
|
| m_nodes[parent].count = childCount; // count al cerrar
|
| parent = m_nodes[parent].parent; // "pop" O(1)
|
| lastChild = m_stk_last[depth];
|
| childCount = m_stk_cnt[depth];
|
| depth--;
|
| }
|
| else
|
| return(Fail(JSON_EXPECTED_COMMA, pos));
|
| }
|
| else // JSON_ARRAY
|
| {
|
| if(c == ',')
|
| {
|
| pos++;
|
| JSON_WS(pos);
|
| needValue = true;
|
| }
|
| else
|
| if(c == ']')
|
| {
|
| pos++;
|
| m_nodes[parent].len = pos - m_nodes[parent].start;
|
| m_nodes[parent].count = childCount;
|
| parent = m_nodes[parent].parent;
|
| lastChild = m_stk_last[depth];
|
| childCount = m_stk_cnt[depth];
|
| depth--;
|
| }
|
| else
|
| return(Fail(JSON_EXPECTED_COMMA, pos));
|
| }
|
| }
|
| }
|
|
|
| return(false); // inalcanzable
|
| }
|
|
|
| #undef JSON_WS
|
|
|
| //+------------------------------------------------------------------+
|
| //| Implementacion de JsonNode (requiere CJson completo) |
|
| //+------------------------------------------------------------------+
|
| ENUM_JSON_TYPE JsonNode::GetType(void) const
|
| {
|
| if(m_doc == NULL || m_idx < 0)
|
| return(JSON_UNDEFINED);
|
| return(m_doc.NodeType(m_idx));
|
| }
|
|
|
| int JsonNode::Size(void) const
|
| {
|
| if(m_doc == NULL)
|
| return(0);
|
| return(m_doc.NodeSize(m_idx));
|
| }
|
|
|
| JsonNode JsonNode::operator[](const string key)
|
| {
|
| JsonNode n;
|
| n.m_doc = m_doc;
|
| n.m_idx = (m_doc != NULL ? m_doc.FindMember(m_idx, key) : -1);
|
| return(n);
|
| }
|
|
|
| JsonNode JsonNode::operator[](const int i)
|
| {
|
| JsonNode n;
|
| n.m_doc = m_doc;
|
| n.m_idx = (m_doc != NULL ? m_doc.ChildAt(m_idx, i) : -1);
|
| return(n);
|
| }
|
|
|
| JsonNode JsonNode::Get(const string key)
|
| {
|
| JsonNode n;
|
| n.m_doc = m_doc;
|
| n.m_idx = (m_doc != NULL ? m_doc.FindMember(m_idx, key) : -1);
|
| return(n);
|
| }
|
|
|
| JsonNode JsonNode::At(const int i)
|
| {
|
| JsonNode n;
|
| n.m_doc = m_doc;
|
| n.m_idx = (m_doc != NULL ? m_doc.ChildAt(m_idx, i) : -1);
|
| return(n);
|
| }
|
|
|
| bool JsonNode::Exists(const string key)
|
| {
|
| return(m_doc != NULL && m_doc.FindMember(m_idx, key) >= 0);
|
| }
|
|
|
| JsonNode JsonNode::First(void)
|
| {
|
| JsonNode n;
|
| n.m_doc = m_doc;
|
| n.m_idx = (m_doc != NULL ? m_doc.NodeFirst(m_idx) : -1);
|
| return(n);
|
| }
|
|
|
| JsonNode JsonNode::Next(void)
|
| {
|
| JsonNode n;
|
| n.m_doc = m_doc;
|
| n.m_idx = (m_doc != NULL ? m_doc.NodeNext(m_idx) : -1);
|
| return(n);
|
| }
|
|
|
| string JsonNode::Key(void)
|
| {
|
| if(m_doc == NULL)
|
| return("");
|
| return(m_doc.NodeKey(m_idx));
|
| }
|
|
|
| string JsonNode::ToString(const string def)
|
| {
|
| if(m_doc == NULL)
|
| return(def);
|
| return(m_doc.NodeText(m_idx, def));
|
| }
|
|
|
| double JsonNode::ToDouble(const double def)
|
| {
|
| if(m_doc == NULL)
|
| return(def);
|
| return(m_doc.NodeDouble(m_idx, def));
|
| }
|
|
|
| long JsonNode::ToInteger(const long def)
|
| {
|
| if(m_doc == NULL)
|
| return(def);
|
| return(m_doc.NodeLong(m_idx, def));
|
| }
|
|
|
| bool JsonNode::ToBool(const bool def)
|
| {
|
| if(m_doc == NULL)
|
| return(def);
|
| return(m_doc.NodeBool(m_idx, def));
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Definicion de miembros estaticos |
|
| //+------------------------------------------------------------------+
|
| bool CJson::s_tables = false;
|
| uchar CJson::s_hex[256];
|
| double CJson::s_pow10[309];
|
|
|
| #endif // __JSON_MQH__
|
| //+------------------------------------------------------------------+ |