From 1bd990741857c310635d5edab21a6884e5ba7e04 Mon Sep 17 00:00:00 2001 From: ioedeveloper Date: Sat, 1 Feb 2025 14:36:26 +0000 Subject: [PATCH] Parse Noir files --- .../src/app/services/noirParser.ts | 104 ++++++++++++++++++ .../src/app/services/noirPluginClient.ts | 6 + package.json | 2 + yarn.lock | 10 ++ 4 files changed, 122 insertions(+) create mode 100644 apps/noir-compiler/src/app/services/noirParser.ts diff --git a/apps/noir-compiler/src/app/services/noirParser.ts b/apps/noir-compiler/src/app/services/noirParser.ts new file mode 100644 index 0000000000..323f86124a --- /dev/null +++ b/apps/noir-compiler/src/app/services/noirParser.ts @@ -0,0 +1,104 @@ +// Noir Circuit Program Parser +// Detects syntax errors and warnings in .nr files + +class NoirParser { + errors: any; + currentLine: any; + currentColumn: number; + constructor() { + this.errors = []; + this.currentLine = 1; + this.currentColumn = 1; + } + + parseNoirCode(code) { + this.errors = []; + this.currentLine = 1; + this.currentColumn = 1; + + const lines = code.split('\n'); + let inFunctionBody = false; + + for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) { + const line = lines[lineIdx].trim(); + + // Skip empty lines or comments + if (line === '' || line.startsWith('//')) { + this.currentLine++; + continue; + } + + // Track function body + if (line.includes('{')) { + inFunctionBody = true; + } else if (line.includes('}')) { + inFunctionBody = false; + } + + // Check for multiple semicolons + const semicolonMatches = [...line.matchAll(/;/g)]; + if (semicolonMatches.length > 1) { + this.addError( + 'Multiple semicolons in a single statement', + lineIdx + 1, + semicolonMatches[1].index + 1, + [lineIdx + 1, line.length] + ); + } + + // Check module imports + if (line.startsWith('mod ')) { + const modulePattern = /^mod\s+[a-zA-Z_][a-zA-Z0-9_]*\s*;?$/; + if (!modulePattern.test(line)) { + this.addError( + 'Invalid module import syntax', + lineIdx + 1, + 1, + [lineIdx + 1, line.length] + ); + } + } + + // Check statement semicolons + if (inFunctionBody && + !line.endsWith('{') && + !line.endsWith('}') && + !line.startsWith('fn ') && + !line.startsWith('//') && + !line.endsWith(';') && + line.length > 0) { + this.addError( + 'Missing semicolon at statement end', + lineIdx + 1, + line.length, + [lineIdx + 1, line.length] + ); + } + + // Check for trailing whitespace + if (lines[lineIdx].endsWith(' ')) { + this.addError( + 'Trailing whitespace', + lineIdx + 1, + lines[lineIdx].length, + [lineIdx + 1, lines[lineIdx].length] + ); + } + + this.currentLine++; + } + + return this.errors; + } + + addError(message, line, column, range) { + this.errors.push({ + message, + line, + column, + range: range || [line, column] + }); + } +} + +export default NoirParser; \ No newline at end of file diff --git a/apps/noir-compiler/src/app/services/noirPluginClient.ts b/apps/noir-compiler/src/app/services/noirPluginClient.ts index 58e72b7572..e27154d23e 100644 --- a/apps/noir-compiler/src/app/services/noirPluginClient.ts +++ b/apps/noir-compiler/src/app/services/noirPluginClient.ts @@ -6,9 +6,11 @@ import { compile_program, createFileManager } from '@noir-lang/noir_wasm/default import type { FileManager } from '@noir-lang/noir_wasm/dist/node/main' import pathModule from 'path' import { DEFAULT_TOML_CONFIG } from '../actions/constants' +import NoirParser from './noirParser' export class NoirPluginClient extends PluginClient { public internalEvents: EventManager public fm: FileManager + public parser: NoirParser constructor() { super() @@ -16,6 +18,7 @@ export class NoirPluginClient extends PluginClient { createClient(this) this.internalEvents = new EventManager() this.fm = createFileManager('/') + this.parser = new NoirParser() this.onload() } @@ -66,6 +69,9 @@ export class NoirPluginClient extends PluginClient { async parse(path: string, content?: string): Promise { if (!content) content = await this.call('fileManager', 'readFile', path) await this.resolveDependencies(path, content) + const result = this.parser.parseNoirCode(content) + + console.log('result: ', result) const fileBytes = new TextEncoder().encode(content) this.fm.writeFile(`${path}`, new Blob([fileBytes]).stream()) diff --git a/package.json b/package.json index 602ebf0edf..59af5c147d 100644 --- a/package.json +++ b/package.json @@ -142,6 +142,7 @@ "core-js": "^3.6.5", "cors": "^2.8.5", "create-hash": "^1.2.0", + "dedent": "^1.5.3", "deep-equal": "^1.0.1", "document-register-element": "1.13.1", "electron-squirrel-startup": "^1.0.0", @@ -176,6 +177,7 @@ "merge": "^2.1.1", "npm-install-version": "^6.0.2", "octokit": "^3.1.2", + "ohm-js": "^17.1.0", "path-browserify": "^1.0.1", "prettier": "^2.8.4", "prettier-plugin-solidity": "^1.0.0-beta.24", diff --git a/yarn.lock b/yarn.lock index ba78df074c..5bfa602d3e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13536,6 +13536,11 @@ dedent@^0.7.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= +dedent@^1.5.3: + version "1.5.3" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a" + integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ== + deep-eql@4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.0.1.tgz#2b65bc89491d193780c452edee2144a91bb0a445" @@ -24075,6 +24080,11 @@ ofetch@^1.3.3: node-fetch-native "^1.4.0" ufo "^1.3.0" +ohm-js@^17.1.0: + version "17.1.0" + resolved "https://registry.yarnpkg.com/ohm-js/-/ohm-js-17.1.0.tgz#50d8e08f69d7909931998d75202d35e2a90c8885" + integrity sha512-xc3B5dgAjTBQGHaH7B58M2Pmv6WvzrJ/3/7LeUzXNg0/sY3jQPdSd/S2SstppaleO77rifR1tyhdfFGNIwxf2Q== + on-exit-leak-free@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz#b39c9e3bf7690d890f4861558b0d7b90a442d209"