PEBL 2.2
Psychology Experiment Building Language - Cross-platform psychological experiment development system
PEBLValidator.cpp
Go to the documentation of this file.
1//* -*- mode:C++; tab-width:4; c-basic-offset:4; indent-tabs-mode:nil -*- */
3// Name: src/apps/PEBLValidator.cpp
4// Purpose: PEBL syntax validator (minimal version)
5// Author: Shane T. Mueller, Ph.D.
6// Copyright: (c) 2025 Shane T. Mueller <smueller@obereed.net>
7// License: GPL 2
8//
9// This tool validates PEBL code syntax and function calls without executing it.
10//
11// Features:
12// - Syntax validation (parse tree generation)
13// - Function validation (detects undefined function calls)
14// - Standard library loading (same as runtime)
15//
17
18#include "../base/Variant.h" // Defines pInt, pDouble
19#include "../base/PNode.h" // Defines PNode
20#include "../base/grammar.tab.hpp" // Parser
21
22#include "../utility/PEBLPath.h"
23#include "../utility/PEBLUtility.h"
24#include "../utility/PError.h" // Defines PCallStack
25
26#include <iostream>
27#include <string>
28#include <vector>
29
30using std::cerr;
31using std::cout;
32using std::endl;
33using std::string;
34using std::vector;
35
36// Prototype for parser function defined in grammar.y
37extern PNode * parse(const char* filename);
38
39// Include headers for static member definitions
40#include "../base/FunctionMap.h"
41#include "../base/VariableMap.h"
42#include "../base/Evaluator.h"
43#include "../base/Loader.h"
44#include "../devices/PEventLoop.h"
45
46// Global variables normally defined in PEBL.cpp
47// These are required by various PEBL subsystems
48void * myEnv = NULL;
50
51// Define static members of Evaluator class
58
59// List of standard PEBL library files
60// This matches PEBL.cpp lines 290-297 (Transfer.pbl uses HTTP stub functions when PEBL_HTTP disabled)
61static const char* STANDARD_LIBRARIES[] = {
62 "Design.pbl",
63 "Utility.pbl",
64 "Math.pbl",
65 "Graphics.pbl",
66 "UI.pbl",
67 "HTML.pbl",
68 "Layout.pbl",
69 "Transfer.pbl",
70 NULL // Null terminator
71};
72
73// Global error tracking
74bool gSyntaxValid = true;
75vector<string> gErrors;
76vector<string> gWarnings;
77
78void PrintUsage() {
79 cout << "PEBL Validator - Syntax and Function Checker\n";
80 cout << "Usage: pebl-validator <file.pbl> [options]\n\n";
81 cout << "Options:\n";
82 cout << " --json Output results as JSON\n";
83 cout << " --help Show this help message\n\n";
84 cout << "Exit codes:\n";
85 cout << " 0 = Valid syntax and all functions defined\n";
86 cout << " 1 = Invalid syntax or undefined functions\n";
87 cout << " 2 = Usage error\n\n";
88 cout << "Features:\n";
89 cout << " - Parses PEBL syntax without execution\n";
90 cout << " - Validates all function calls against standard libraries\n";
91 cout << " - Detects undefined functions\n";
92}
93
94void PrintJSONResults(const string& filename) {
95 cout << "{\n";
96 cout << " \"file\": \"" << filename << "\",\n";
97 cout << " \"syntax_valid\": " << (gSyntaxValid ? "true" : "false") << ",\n";
98 cout << " \"errors\": [\n";
99 for (size_t i = 0; i < gErrors.size(); i++) {
100 // Escape quotes in error messages
101 string msg = gErrors[i];
102 size_t pos = 0;
103 while ((pos = msg.find('"', pos)) != string::npos) {
104 msg.replace(pos, 1, "\\\"");
105 pos += 2;
106 }
107 cout << " \"" << msg << "\"";
108 if (i < gErrors.size() - 1) cout << ",";
109 cout << "\n";
110 }
111 cout << " ],\n";
112 cout << " \"warnings\": [\n";
113 for (size_t i = 0; i < gWarnings.size(); i++) {
114 // Escape quotes in warning messages
115 string msg = gWarnings[i];
116 size_t pos = 0;
117 while ((pos = msg.find('\"', pos)) != string::npos) {
118 msg.replace(pos, 1, "\\\"");
119 pos += 2;
120 }
121 cout << " \"" << msg << "\"";
122 if (i < gWarnings.size() - 1) cout << ",";
123 cout << "\n";
124 }
125 cout << " ]\n";
126 cout << "}\n";
127}
128
129void PrintTextResults(const string& filename) {
130 cout << "PEBL Validator Results\n";
131 cout << "======================\n";
132 cout << "File: " << filename << "\n\n";
133
134 if (gSyntaxValid) {
135 cout << "✓ Syntax: VALID\n";
136 } else {
137 cout << "✗ Syntax: INVALID\n";
138 }
139
140 if (!gErrors.empty()) {
141 cout << "\nErrors:\n";
142 for (const auto& err : gErrors) {
143 cout << " ✗ " << err << "\n";
144 }
145 }
146
147 if (!gWarnings.empty()) {
148 cout << "\nWarnings:\n";
149 for (const auto& warn : gWarnings) {
150 cout << " ⚠ " << warn << "\n";
151 }
152 }
153
154}
155
156int main(int argc, char *argv[]) {
157 // Disable GUI error dialogs for validator (errors still go to stderr)
159
160 // Enable validator mode (throw exceptions instead of exiting on errors)
162
163 if (argc < 2) {
164 PrintUsage();
165 return 2;
166 }
167
168 string filename;
169 bool json_output = false;
170
171 // Parse command-line arguments
172 for (int i = 1; i < argc; i++) {
173 string arg = argv[i];
174 if (arg == "--help") {
175 PrintUsage();
176 return 0;
177 } else if (arg == "--json") {
178 json_output = true;
179 } else if (filename.empty()) {
180 filename = arg;
181 } else {
182 cerr << "Error: Unknown argument: " << arg << "\n";
183 PrintUsage();
184 return 2;
185 }
186 }
187
188 if (filename.empty()) {
189 cerr << "Error: No file specified\n";
190 PrintUsage();
191 return 2;
192 }
193
194 // Check if file exists using PEBLUtility
195 if (!PEBLUtility::FileExists(filename)) {
196 gSyntaxValid = false;
197 gErrors.push_back("File not found: " + filename);
198 if (json_output) {
199 PrintJSONResults(filename);
200 } else {
201 PrintTextResults(filename);
202 }
203 return 1;
204 }
205
206 // Initialize path system (needed to find library files)
207 std::list<std::string> files;
208 files.push_back("pebl-validator"); // Executable name
209 files.push_back(filename); // User's file
211
212 // Parse the user's file
213 PNode * head = NULL;
214 PNode * tmp = NULL;
215 Loader * myLoader = NULL;
216
217 try {
218 cerr << "Parsing user file: " << filename << endl;
219 head = parse(filename.c_str());
220
221 if (!head) {
222 gSyntaxValid = false;
223 gErrors.push_back("Parse failed - syntax error in file");
224 } else {
225 // Parse and combine standard library files (like PEBL.cpp lines 341-370)
226 for (int i = 0; STANDARD_LIBRARIES[i] != NULL; i++) {
227 string libpath = Evaluator::gPath.FindFile(STANDARD_LIBRARIES[i]);
228 if (libpath != "" && !Evaluator::gPath.IsDirectory(libpath)) {
229 cerr << "Loading library: " << STANDARD_LIBRARIES[i] << endl;
230 tmp = parse(libpath.c_str());
231 if (tmp) {
232 // Combine library with existing parse tree
233 head = new OpNode(PEBL_FUNCTIONS, head, tmp, "INTERNAL PEBL STRUCTURE", -1);
234 }
235 }
236 }
237
238 // Now validate functions using Loader (like PEBL.cpp lines 179-188)
239 cerr << "Validating functions..." << endl;
240 myLoader = new Loader();
242
243 // Validate Start() function has exactly one parameter
244 PNode* startFunc = myLoader->GetMainPEBLFunction();
245 if (startFunc) {
246 OpNode* startOpNode = dynamic_cast<OpNode*>(startFunc);
247 if (startOpNode) {
248 // For PEBL_FUNCTION nodes, left child is parameter list
249 PNode* paramList = startOpNode->GetLeft();
250 if (!paramList) {
251 // Start() defined with no parameters: define Start()
252 gErrors.push_back("Start() function must have exactly one parameter to receive command-line arguments (e.g., define Start(p))");
253 gSyntaxValid = false;
254 }
255 }
256 } else {
257 gWarnings.push_back("No Start() function found - PEBL scripts should define a Start() function as the entry point");
258 }
259
261
262 // LoadLibraryFunctions() will throw exception on undefined functions in validator mode
263 try {
265 } catch (const std::runtime_error& e) {
266 // Catch validation errors (undefined functions, etc.)
267 gSyntaxValid = false;
268 gErrors.push_back(e.what());
269 }
270
271 // Clean up
272 ((OpNode*)head)->DestroyFunctionTree();
273 delete head;
274 delete myLoader;
275 }
276 } catch (const std::exception& e) {
277 gSyntaxValid = false;
278 gErrors.push_back(string("Parse exception: ") + e.what());
279 if (head) {
280 ((OpNode*)head)->DestroyFunctionTree();
281 delete head;
282 }
283 if (myLoader) delete myLoader;
284 } catch (...) {
285 gSyntaxValid = false;
286 gErrors.push_back("Validation failed - unknown error");
287 if (head) {
288 ((OpNode*)head)->DestroyFunctionTree();
289 delete head;
290 }
291 if (myLoader) delete myLoader;
292 }
293
294 // Output results
295 if (json_output) {
296 PrintJSONResults(filename);
297 } else {
298 PrintTextResults(filename);
299 }
300
301 // Return exit code
302 return gSyntaxValid ? 0 : 1;
303}
#define NULL
Definition BinReloc.cpp:317
int main(int argc, char *argv[])
bool gSyntaxValid
void PrintJSONResults(const string &filename)
vector< string > gErrors
void PrintTextResults(const string &filename)
void PrintUsage()
Evaluator * myEval
vector< string > gWarnings
void * myEnv
PNode * head
Definition PEBL.cpp:220
Loader * myLoader
Definition PEBL.cpp:219
PNode * parse()
This class has got everything you need to evaluate stuff.
static const PNode * gEvalNode
static PEBLPath gPath
static PCallStack gCallStack
static PEventLoop * mEventLoop
static VariableMap gGlobalVariableMap
static FunctionMap mFunctionMap
Initiate some static member data.
void LoadUserFunctions(OpNode *node)
Definition Loader.cpp:147
void LoadLibraryFunctions()
Definition Loader.cpp:195
void FindFunctions(const PNode *Node)
Definition Loader.cpp:88
PNode * GetMainPEBLFunction()
Definition Loader.cpp:372
PNode * GetLeft() const
Definition PNode.h:114
std::string FindFile(const string &filename)
Definition PEBLPath.cpp:368
void Initialize(std::list< std::string >)
Definition PEBLPath.cpp:60
Definition PNode.h:45
Variant FileExists(std::string path)
bool gValidatorMode
Definition PError.cpp:59
bool gShowErrorDialogs
Definition PError.cpp:56