parent
1bd9907418
commit
6548616df0
@ -1,103 +1,240 @@ |
|||||||
// Noir Circuit Program Parser
|
|
||||||
// Detects syntax errors and warnings in .nr files
|
|
||||||
|
|
||||||
class NoirParser { |
class NoirParser { |
||||||
errors: any; |
errors: { |
||||||
currentLine: any; |
message: string; |
||||||
|
type: string; |
||||||
|
position: { |
||||||
|
start: { line: number; column: number }; |
||||||
|
end: { line: number; column: number }; |
||||||
|
}; |
||||||
|
}[]; |
||||||
|
currentLine: number; |
||||||
currentColumn: number; |
currentColumn: number; |
||||||
|
noirTypes: string[]; |
||||||
|
|
||||||
constructor() { |
constructor() { |
||||||
this.errors = []; |
this.errors = []; |
||||||
this.currentLine = 1; |
this.currentLine = 1; |
||||||
this.currentColumn = 1; |
this.currentColumn = 1; |
||||||
|
this.noirTypes = ['Field', 'bool', 'u8', 'u16', 'u32', 'u64', 'i8', 'i16', 'i32', 'i64']; |
||||||
} |
} |
||||||
|
|
||||||
parseNoirCode(code) { |
parseNoirCode(code) { |
||||||
this.errors = []; |
this.errors = []; |
||||||
this.currentLine = 1; |
|
||||||
this.currentColumn = 1; |
|
||||||
|
|
||||||
const lines = code.split('\n'); |
const lines = code.split('\n'); |
||||||
let inFunctionBody = false; |
const functions = this.analyzeFunctions(lines); |
||||||
|
|
||||||
for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) { |
for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) { |
||||||
const line = lines[lineIdx].trim(); |
const line = lines[lineIdx]; |
||||||
|
const trimmedLine = line.trim(); |
||||||
|
|
||||||
// Skip empty lines or comments
|
if (trimmedLine === '' || trimmedLine.startsWith('//')) continue; |
||||||
if (line === '' || line.startsWith('//')) { |
if (trimmedLine.startsWith('mod ')) { |
||||||
this.currentLine++; |
this.checkModuleImport(trimmedLine, lineIdx, line); |
||||||
continue; |
continue; |
||||||
} |
} |
||||||
|
const currentFunction = functions.find(f => lineIdx >= f.startLine && lineIdx <= f.endLine); |
||||||
|
|
||||||
// Track function body
|
if (currentFunction) { |
||||||
if (line.includes('{')) { |
if (lineIdx === currentFunction.startLine) this.checkFunctionReturnType(trimmedLine, lineIdx, line); |
||||||
inFunctionBody = true; |
else this.checkFunctionBodyStatement(trimmedLine, lineIdx, line, currentFunction, lines); |
||||||
} else if (line.includes('}')) { |
|
||||||
inFunctionBody = false; |
|
||||||
} |
} |
||||||
|
|
||||||
// Check for multiple semicolons
|
if (/[ \t]$/.test(line)) { |
||||||
const semicolonMatches = [...line.matchAll(/;/g)]; |
this.addError({ |
||||||
if (semicolonMatches.length > 1) { |
message: 'Trailing whitespace detected', |
||||||
this.addError( |
type: 'style', |
||||||
'Multiple semicolons in a single statement', |
position: this.calculatePosition(lineIdx, line.length - 1, line.length) |
||||||
lineIdx + 1, |
}); |
||||||
semicolonMatches[1].index + 1, |
|
||||||
[lineIdx + 1, line.length] |
|
||||||
); |
|
||||||
} |
} |
||||||
|
} |
||||||
|
|
||||||
|
return this.errors; |
||||||
|
} |
||||||
|
|
||||||
|
analyzeFunctions(lines) { |
||||||
|
const functions = []; |
||||||
|
let currentFunction = null; |
||||||
|
let bracketCount = 0; |
||||||
|
|
||||||
|
for (let i = 0; i < lines.length; i++) { |
||||||
|
const line = lines[i]; |
||||||
|
const codePart = line.split('//')[0].trim(); |
||||||
|
|
||||||
|
if (codePart.startsWith('fn ')) { |
||||||
|
if (currentFunction !== null) { |
||||||
|
this.addError({ |
||||||
|
message: 'Nested function definition not allowed', |
||||||
|
type: 'syntax', |
||||||
|
position: this.calculatePosition(i, 0, line.length) |
||||||
|
}); |
||||||
|
} |
||||||
|
const fnMatch = codePart.match(/fn\s+([a-zA-Z_][a-zA-Z0-9_]*)/); |
||||||
|
|
||||||
// Check module imports
|
if (!fnMatch) { |
||||||
if (line.startsWith('mod ')) { |
this.addError({ |
||||||
const modulePattern = /^mod\s+[a-zA-Z_][a-zA-Z0-9_]*\s*;?$/; |
message: 'Invalid function name', |
||||||
if (!modulePattern.test(line)) { |
type: 'syntax', |
||||||
this.addError( |
position: this.calculatePosition(i, 0, line.length) |
||||||
'Invalid module import syntax', |
}); |
||||||
lineIdx + 1, |
continue; |
||||||
1, |
|
||||||
[lineIdx + 1, line.length] |
|
||||||
); |
|
||||||
} |
} |
||||||
|
currentFunction = { |
||||||
|
startLine: i, |
||||||
|
name: fnMatch[1], |
||||||
|
returnType: this.extractReturnType(codePart), |
||||||
|
bracketCount: 0 |
||||||
|
}; |
||||||
} |
} |
||||||
|
|
||||||
// Check statement semicolons
|
if (currentFunction) { |
||||||
if (inFunctionBody && |
const open = (codePart.match(/{/g) || []).length; |
||||||
!line.endsWith('{') && |
const close = (codePart.match(/}/g) || []).length; |
||||||
!line.endsWith('}') && |
|
||||||
!line.startsWith('fn ') && |
bracketCount += open - close; |
||||||
!line.startsWith('//') && |
if (bracketCount === 0) { |
||||||
!line.endsWith(';') && |
currentFunction.endLine = i; |
||||||
line.length > 0) { |
functions.push({ ...currentFunction }); |
||||||
this.addError( |
currentFunction = null; |
||||||
'Missing semicolon at statement end', |
} |
||||||
lineIdx + 1, |
|
||||||
line.length, |
|
||||||
[lineIdx + 1, line.length] |
|
||||||
); |
|
||||||
} |
} |
||||||
|
} |
||||||
|
|
||||||
|
return functions; |
||||||
|
} |
||||||
|
|
||||||
|
checkFunctionBodyStatement(line, lineIdx, originalLine, currentFunction, allLines) { |
||||||
|
if (line === '' || line.startsWith('//') || line === '{' || line === '}') return; |
||||||
|
const codePart = line.split('//')[0].trimEnd(); |
||||||
|
const isLastStatement = this.isLastStatementInFunction(lineIdx, currentFunction, allLines); |
||||||
|
|
||||||
// Check for trailing whitespace
|
if (!isLastStatement && !codePart.endsWith(';') && !codePart.endsWith('{')) { |
||||||
if (lines[lineIdx].endsWith(' ')) { |
const nextNonEmptyLine = this.findNextNonEmptyLine(lineIdx + 1, allLines); |
||||||
this.addError( |
if (nextNonEmptyLine && !nextNonEmptyLine.trim().startsWith('//')) { |
||||||
'Trailing whitespace', |
this.addError({ |
||||||
lineIdx + 1, |
message: 'Missing semicolon at statement end', |
||||||
lines[lineIdx].length, |
type: 'syntax', |
||||||
[lineIdx + 1, lines[lineIdx].length] |
position: this.calculatePosition( |
||||||
); |
lineIdx, |
||||||
|
originalLine.length, |
||||||
|
originalLine.length |
||||||
|
) |
||||||
|
}); |
||||||
} |
} |
||||||
|
} |
||||||
|
const semicolonMatches = [...codePart.matchAll(/;/g)]; |
||||||
|
|
||||||
this.currentLine++; |
if (semicolonMatches.length > 1) { |
||||||
|
this.addError({ |
||||||
|
message: 'Multiple semicolons in a single statement', |
||||||
|
type: 'syntax', |
||||||
|
position: this.calculatePosition( |
||||||
|
lineIdx, |
||||||
|
semicolonMatches[1].index, |
||||||
|
originalLine.length |
||||||
|
) |
||||||
|
}); |
||||||
} |
} |
||||||
|
} |
||||||
|
|
||||||
return this.errors; |
extractReturnType(line) { |
||||||
|
const returnMatch = line.match(/->\s*([a-zA-Z_][a-zA-Z0-9_:<>, ]*)/); |
||||||
|
|
||||||
|
return returnMatch ? returnMatch[1].trim() : null; |
||||||
|
} |
||||||
|
|
||||||
|
checkFunctionReturnType(line, lineIdx, originalLine) { |
||||||
|
const returnMatch = line.match(/->\s*([a-zA-Z_][a-zA-Z0-9_:<>, ]*)/); |
||||||
|
|
||||||
|
if (returnMatch) { |
||||||
|
const returnType = returnMatch[1].trim(); |
||||||
|
|
||||||
|
// Check if it's a valid Noir type or a custom type
|
||||||
|
if (!this.isValidNoirType(returnType)) { |
||||||
|
this.addError({ |
||||||
|
message: `Potentially invalid return type: ${returnType}`, |
||||||
|
type: 'warning', |
||||||
|
position: this.calculatePosition( |
||||||
|
lineIdx, |
||||||
|
originalLine.indexOf(returnType), |
||||||
|
originalLine.indexOf(returnType) + returnType.length |
||||||
|
) |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
isLastStatementInFunction(currentLine, currentFunction, lines) { |
||||||
|
for (let i = currentLine + 1; i <= currentFunction.endLine; i++) { |
||||||
|
const line = lines[i].trim(); |
||||||
|
if (line && !line.startsWith('//') && line !== '}') { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
findNextNonEmptyLine(startIndex, lines) { |
||||||
|
for (let i = startIndex; i < lines.length; i++) { |
||||||
|
const line = lines[i].trim(); |
||||||
|
if (line && !line.startsWith('//')) { |
||||||
|
return line; |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
checkModuleImport(line, lineIdx, originalLine) { |
||||||
|
const modulePattern = /^mod\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*;?$/; |
||||||
|
const match = line.match(modulePattern); |
||||||
|
|
||||||
|
if (!match) { |
||||||
|
this.addError({ |
||||||
|
message: 'Invalid module import syntax', |
||||||
|
type: 'syntax', |
||||||
|
position: this.calculatePosition(lineIdx, 0, originalLine.length) |
||||||
|
}); |
||||||
|
} else if (!line.endsWith(';')) { |
||||||
|
this.addError({ |
||||||
|
message: 'Missing semicolon after module import', |
||||||
|
type: 'syntax', |
||||||
|
position: this.calculatePosition( |
||||||
|
lineIdx, |
||||||
|
originalLine.length, |
||||||
|
originalLine.length |
||||||
|
) |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
isValidNoirType(type) { |
||||||
|
// Basic types
|
||||||
|
if (this.noirTypes.includes(type)) return true; |
||||||
|
|
||||||
|
// Array types
|
||||||
|
if (type.includes('[') && type.includes(']')) { |
||||||
|
const baseType = type.match(/\[(.*?);/)?.[1]; |
||||||
|
return baseType && this.noirTypes.includes(baseType); |
||||||
|
} |
||||||
|
|
||||||
|
// Generic types or custom types (not supported for now)
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
calculatePosition(line, startColumn, endColumn) { |
||||||
|
return { |
||||||
|
start: { |
||||||
|
line: line + 1, |
||||||
|
column: startColumn + 1 |
||||||
|
}, |
||||||
|
end: { |
||||||
|
line: line + 1, |
||||||
|
column: endColumn + 1 |
||||||
|
} |
||||||
|
}; |
||||||
} |
} |
||||||
|
|
||||||
addError(message, line, column, range) { |
addError(error) { |
||||||
this.errors.push({ |
this.errors.push(error); |
||||||
message, |
|
||||||
line, |
|
||||||
column, |
|
||||||
range: range || [line, column] |
|
||||||
}); |
|
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
|
Loading…
Reference in new issue