Parse circom code and highlight error

pull/3996/head
ioedeveloper 1 year ago
parent 75b011964f
commit e61ba8f808
  1. 6
      apps/circuit-compiler/src/app/actions/dispatch.ts
  2. 1
      apps/circuit-compiler/src/app/actions/index.ts
  3. 23
      apps/circuit-compiler/src/app/app.tsx
  4. 30
      apps/circuit-compiler/src/app/components/compiler.ts
  5. 4
      apps/circuit-compiler/src/app/contexts/index.ts
  6. 18
      apps/circuit-compiler/src/app/reducers/index.ts
  7. 217
      apps/circuit-compiler/src/app/services/circomPluginClient.ts
  8. 22
      apps/circuit-compiler/src/app/types/index.ts
  9. 1
      apps/circuit-compiler/src/logo.svg
  10. 2
      apps/circuit-compiler/src/profile.json
  11. 8
      apps/remix-ide/src/app/editor/editor.js
  12. 13
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  13. 4
      libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx
  14. 1
      package.json
  15. 9
      yarn.lock

@ -1,6 +0,0 @@
import { Dispatch } from "react"
import { Action } from "../types"
export const dispatchCheckRemixd = (status: boolean) => (dispatch: Dispatch<Action<'SET_REMIXD_CONNECTION_STATUS'>>) => {
dispatch({ type: 'SET_REMIXD_CONNECTION_STATUS', payload: status })
}

@ -1 +0,0 @@
import { Dispatch } from 'react'

@ -1,31 +1,16 @@
import React, { useEffect, useReducer, useState } from 'react'
import { RenderIf, RenderIfNot } from '@remix-ui/helper'
import { Alert, Button, Tabs, Tab } from 'react-bootstrap'
import React, { useEffect } from 'react'
import { AppContext } from './contexts'
import { appInitialState, appReducer } from './reducers'
import { CircomPluginClient } from './services/circomPluginClient'
function App() {
const [appState, dispatch] = useReducer(appReducer, appInitialState)
const [plugin, setPlugin] = useState<CircomPluginClient>(null)
useEffect(() => {
const plugin = new CircomPluginClient()
setPlugin(plugin)
new CircomPluginClient()
}, [])
const value = {
appState,
dispatch
}
return (
<AppContext.Provider value={value}>
<div className="App">
</div>
</AppContext.Provider>
<div className="App">
</div>
)
}

@ -1,30 +0,0 @@
// const compile = () => {
// const currentFile = api.currentFile
// if (currentFile.endsWith('.circom')) return compileCircuit()
// if (!isSolFileSelected()) return
// _setCompilerVersionFromPragma(currentFile)
// let externalCompType
// if (hhCompilation) externalCompType = 'hardhat'
// else if (truffleCompilation) externalCompType = 'truffle'
// compileTabLogic.runCompiler(externalCompType)
// }
// const compileAndRun = () => {
// const currentFile = api.currentFile
// if (currentFile.endsWith('.circom')) return compileCircuit()
// if (!isSolFileSelected()) return
// _setCompilerVersionFromPragma(currentFile)
// let externalCompType
// if (hhCompilation) externalCompType = 'hardhat'
// else if (truffleCompilation) externalCompType = 'truffle'
// api.runScriptAfterCompilation(currentFile)
// compileTabLogic.runCompiler(externalCompType)
// }
// const compileCircuit = () => {
// const currentFile = api.currentFile
// console.log('Compiling circuit ' + currentFile)
// }

@ -1,4 +0,0 @@
import { createContext } from 'react'
import { IAppContext } from '../types'
export const AppContext = createContext<IAppContext>({} as IAppContext)

@ -1,18 +0,0 @@
import { Actions, AppState } from "../types"
export const appInitialState: AppState = {
isRemixdConnected: null
}
export const appReducer = (state = appInitialState, action: Actions): AppState => {
switch (action.type) {
case 'SET_REMIXD_CONNECTION_STATUS':
return {
...state,
isRemixdConnected: action.payload
}
default:
throw new Error()
}
}

@ -1,108 +1,137 @@
import { PluginClient } from '@remixproject/plugin'
import { createClient } from '@remixproject/plugin-webview'
import { parse_circuit_browser, main_browser } from 'apps/circuit-compiler/pkg/circom'
import { generate_witness } from 'apps/circuit-compiler/pkg/generate_witness'
import EventManager from 'events'
const pathModule = require('path')
import pathModule from 'path'
import { parse } from 'circom_wasm'
export class CircomPluginClient extends PluginClient {
private connected: boolean
public internalEvents: EventManager
constructor() {
super()
createClient(this)
this.internalEvents = new EventManager()
this.methods = ["init", "compile"]
this.onload()
}
init (): void {
console.log('initializing circom plugin...')
public internalEvents: EventManager
constructor() {
super()
createClient(this)
this.internalEvents = new EventManager()
this.methods = ["init", "parse"]
this.onload()
}
init (): void {
console.log('initializing circom plugin...')
}
onActivation(): void {
// @ts-ignore
this.on('editor', 'contentChanged', (path: string, fileContent) => {
if (path.endsWith('.circom')) {
this.parse(path, fileContent)
}
})
}
async parse (path: string, fileContent: string): Promise<void> {
let buildFiles = {
[path]: fileContent
}
async compile (path: string) {
const fileContent = await this.call('fileManager', 'readFile', path)
let buildFiles = {
[path]: fileContent
}
buildFiles = await this.resolveDependencies(path, fileContent, buildFiles)
const compilationResult = main_browser(path, buildFiles, { prime: "bn128" })
console.log('compilation result: ' + compilationResult)
generate_witness(compilationResult, '{ "a": "3", "b": "11" }')
generate_witness(compilationResult, '{ "a": "5", "b": "77" }')
}
async parse (path: string) {
const fileContent = await this.call('fileManager', 'readFile', path)
let buildFiles = {
[path]: fileContent
}
buildFiles = await this.resolveDependencies(path, fileContent, buildFiles)
const parsingResult = parse_circuit_browser(path, buildFiles, { prime: "bn128" })
buildFiles = await this.resolveDependencies(path, fileContent, buildFiles)
const parsedOutput = parse(path, buildFiles)
console.log('parsing result: ' + parsingResult)
}
async resolveDependencies (filePath: string, fileContent: string, output = {}, depPath: string = '', blackPath: string[] = []) {
const includes = (fileContent.match(/include ['"].*['"]/g) || []).map(include => include.replace(/include ['"]/g, '').replace(/['"]/g, ''))
try {
const result = JSON.parse(parsedOutput)
const markers = []
await Promise.all(includes.map(async include => {
// fix for endless recursive includes
if (blackPath.includes(include)) return
let dependencyContent = ''
let path = include
for (const report of result) {
for (const label in report.labels) {
if (report.labels[label].file_id === '0') {
// @ts-ignore
const pathExists = await this.call('fileManager', 'exists', path)
if (pathExists) {
dependencyContent = await this.call('fileManager', 'readFile', path)
} else {
let relativePath = pathModule.resolve(filePath.slice(0, filePath.lastIndexOf('/')), include)
if (relativePath.indexOf('/') === 0) relativePath = relativePath.slice(1)
// @ts-ignore
const relativePathExists = await this.call('fileManager', 'exists', relativePath)
if (relativePathExists) {
dependencyContent = await this.call('fileManager', 'readFile', relativePath)
} else {
if (depPath) {
path = pathModule.resolve(depPath.slice(0, depPath.lastIndexOf('/')), include)
if (path.indexOf('/') === 0) path = path.slice(1)
dependencyContent = await this.call('contentImport', 'resolveAndSave', path, null)
} else {
if (include.startsWith('circomlib')) {
const splitInclude = include.split('/')
const version = splitInclude[1].match(/v[0-9]+.[0-9]+.[0-9]+/g)
if (version && version[0]) {
path = `https://raw.githubusercontent.com/iden3/circomlib/${version[0]}/circuits/${splitInclude.slice(2).join('/')}`
dependencyContent = await this.call('contentImport', 'resolveAndSave', path, null)
} else {
path = `https://raw.githubusercontent.com/iden3/circomlib/master/circuits/${splitInclude.slice(1).join('/')}`
dependencyContent = await this.call('contentImport', 'resolveAndSave', path, null)
}
} else {
dependencyContent = await this.call('contentImport', 'resolveAndSave', path, null)
}
}
const startPosition: { lineNumber: number, column: number } = await this.call('editor', 'getPositionAt', report.labels[label].range.start)
// @ts-ignore
const endPosition: { lineNumber: number, column: number } = await this.call('editor', 'getPositionAt', report.labels[label].range.end)
console.log('startPosition: ', startPosition)
console.log('endPosition: ', endPosition)
markers.push({
message: report.message,
severity: report.type.toLowerCase(),
position: {
start: {
line: startPosition.lineNumber,
column: startPosition.column
},
end: {
line: endPosition.lineNumber,
column: endPosition.column
}
}
const dependencyIncludes = (dependencyContent.match(/include ['"].*['"]/g) || []).map(include => include.replace(/include ['"]/g, '').replace(/['"]/g, ''))
blackPath.push(include)
if (dependencyIncludes.length > 0) {
await this.resolveDependencies(filePath, dependencyContent, output, path, blackPath)
output[include] = dependencyContent
},
file: path
})
}
}
}
// @ts-ignore
await this.call('editor', 'addErrorMarker', markers)
} catch (e) {
// @ts-ignore
await this.call('editor', 'clearErrorMarkers', [path])
console.log(parsedOutput)
}
}
async resolveDependencies (filePath: string, fileContent: string, output = {}, depPath: string = '', blackPath: string[] = []): Promise<Record<string, string>> {
const includes = (fileContent.match(/include ['"].*['"]/g) || []).map(include => include.replace(/include ['"]/g, '').replace(/['"]/g, ''))
await Promise.all(includes.map(async include => {
// fix for endless recursive includes
if (blackPath.includes(include)) return
let dependencyContent = ''
let path = include
// @ts-ignore
const pathExists = await this.call('fileManager', 'exists', path)
if (pathExists) {
dependencyContent = await this.call('fileManager', 'readFile', path)
} else {
let relativePath = pathModule.resolve(filePath.slice(0, filePath.lastIndexOf('/')), include)
if (relativePath.indexOf('/') === 0) relativePath = relativePath.slice(1)
// @ts-ignore
const relativePathExists = await this.call('fileManager', 'exists', relativePath)
if (relativePathExists) {
dependencyContent = await this.call('fileManager', 'readFile', relativePath)
} else {
if (depPath) {
path = pathModule.resolve(depPath.slice(0, depPath.lastIndexOf('/')), include)
if (path.indexOf('/') === 0) path = path.slice(1)
dependencyContent = await this.call('contentImport', 'resolveAndSave', path, null)
} else {
if (include.startsWith('circomlib')) {
const splitInclude = include.split('/')
const version = splitInclude[1].match(/v[0-9]+.[0-9]+.[0-9]+/g)
if (version && version[0]) {
path = `https://raw.githubusercontent.com/iden3/circomlib/${version[0]}/circuits/${splitInclude.slice(2).join('/')}`
dependencyContent = await this.call('contentImport', 'resolveAndSave', path, null)
} else {
path = `https://raw.githubusercontent.com/iden3/circomlib/master/circuits/${splitInclude.slice(1).join('/')}`
dependencyContent = await this.call('contentImport', 'resolveAndSave', path, null)
}
} else {
output[include] = dependencyContent
dependencyContent = await this.call('contentImport', 'resolveAndSave', path, null)
}
}))
return output
}
}
}
}
const dependencyIncludes = (dependencyContent.match(/include ['"].*['"]/g) || []).map(include => include.replace(/include ['"]/g, '').replace(/['"]/g, ''))
blackPath.push(include)
if (dependencyIncludes.length > 0) {
await this.resolveDependencies(filePath, dependencyContent, output, path, blackPath)
output[include] = dependencyContent
} else {
output[include] = dependencyContent
}
}))
return output
}
}

@ -1,22 +0,0 @@
import { Dispatch } from "react"
export interface IAppContext {
appState: AppState,
dispatch: Dispatch<any>
}
export interface ActionPayloadTypes {
SET_REMIXD_CONNECTION_STATUS: boolean
}
export interface Action <T extends keyof ActionPayloadTypes> {
type: T
payload: ActionPayloadTypes[T]
}
export type Actions = { [A in keyof ActionPayloadTypes]: Action<A> }[keyof ActionPayloadTypes]
export interface AppState {
isRemixdConnected: boolean
}

@ -1 +0,0 @@
<svg id="Ebene_2" data-name="Ebene 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 105 100"><title>remix_logo1</title><path d="M91.84,35a.09.09,0,0,1-.1-.07,41,41,0,0,0-79.48,0,.09.09,0,0,1-.1.07C9.45,35,1,35.35,1,42.53c0,8.56,1,16,6,20.32,2.16,1.85,5.81,2.3,9.27,2.22a44.4,44.4,0,0,0,6.45-.68.09.09,0,0,0,.06-.15A34.81,34.81,0,0,1,17,45c0-.1,0-.21,0-.31a35,35,0,0,1,70,0c0,.1,0,.21,0,.31a34.81,34.81,0,0,1-5.78,19.24.09.09,0,0,0,.06.15,44.4,44.4,0,0,0,6.45.68c3.46.08,7.11-.37,9.27-2.22,5-4.27,6-11.76,6-20.32C103,35.35,94.55,35,91.84,35Z"/><path d="M52,74,25.4,65.13a.1.1,0,0,0-.1.17L51.93,91.93a.1.1,0,0,0,.14,0L78.7,65.3a.1.1,0,0,0-.1-.17L52,74A.06.06,0,0,1,52,74Z"/><path d="M75.68,46.9,82,45a.09.09,0,0,0,.08-.09,29.91,29.91,0,0,0-.87-6.94.11.11,0,0,0-.09-.08l-6.43-.58a.1.1,0,0,1-.06-.18l4.78-4.18a.13.13,0,0,0,0-.12,30.19,30.19,0,0,0-3.65-6.07.09.09,0,0,0-.11,0l-5.91,2a.1.1,0,0,1-.12-.14L72.19,23a.11.11,0,0,0,0-.12,29.86,29.86,0,0,0-5.84-4.13.09.09,0,0,0-.11,0l-4.47,4.13a.1.1,0,0,1-.17-.07l.09-6a.1.1,0,0,0-.07-.1,30.54,30.54,0,0,0-7-1.47.1.1,0,0,0-.1.07l-2.38,5.54a.1.1,0,0,1-.18,0l-2.37-5.54a.11.11,0,0,0-.11-.06,30,30,0,0,0-7,1.48.12.12,0,0,0-.07.1l.08,6.05a.09.09,0,0,1-.16.07L37.8,18.76a.11.11,0,0,0-.12,0,29.75,29.75,0,0,0-5.83,4.13.11.11,0,0,0,0,.12l2.59,5.6a.11.11,0,0,1-.13.14l-5.9-2a.11.11,0,0,0-.12,0,30.23,30.23,0,0,0-3.62,6.08.11.11,0,0,0,0,.12l4.79,4.19a.1.1,0,0,1-.06.17L23,37.91a.1.1,0,0,0-.09.07A29.9,29.9,0,0,0,22,44.92a.1.1,0,0,0,.07.1L28.4,47a.1.1,0,0,1,0,.18l-5.84,3.26a.16.16,0,0,0,0,.11,30.17,30.17,0,0,0,2.1,6.76c.32.71.67,1.4,1,2.08a.1.1,0,0,0,.06,0L52,68.16H52l26.34-8.78a.1.1,0,0,0,.06-.05,30.48,30.48,0,0,0,3.11-8.88.1.1,0,0,0-.05-.11l-5.83-3.26A.1.1,0,0,1,75.68,46.9Z"/></svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

@ -4,7 +4,7 @@
"displayName": "Circuit Compiler",
"events": [],
"version": "2.0.0",
"methods": ["init", "compile"],
"methods": ["init", "parse"],
"canActivate": [],
"url": "",
"description": "Enables circuit compilation and computing a witness for ZK proofs",

@ -13,7 +13,7 @@ const profile = {
name: 'editor',
description: 'service - editor',
version: packageJson.version,
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'open', 'addModel','addErrorMarker', 'clearErrorMarkers', 'getText'],
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'open', 'addModel','addErrorMarker', 'clearErrorMarkers', 'getText', 'getPositionAt'],
}
class Editor extends Plugin {
@ -175,7 +175,7 @@ class Editor extends Plugin {
}
this.saveTimeout = window.setTimeout(() => {
this.triggerEvent('contentChanged', [])
this.triggerEvent('contentChanged', [currentFile, input])
this.triggerEvent('requiringToSaveCurrentfile', [])
}, 500)
}
@ -579,6 +579,10 @@ class Editor extends Plugin {
this.clearDecorationsByPlugin(session, from, 'lineTextPerFile', this.registeredDecorations, this.currentDecorations)
}
}
getPositionAt(offset) {
return this.api.getPositionAt(offset)
}
}
module.exports = Editor

@ -17,7 +17,9 @@ import {RemixHighLightProvider} from './providers/highlightProvider'
import {RemixDefinitionProvider} from './providers/definitionProvider'
import {RemixCodeActionProvider} from './providers/codeActionProvider'
import './remix-ui-editor.css'
import {circomLanguageConfig, circomTokensProvider} from './syntaxes/circom'
import { circomLanguageConfig, circomTokensProvider } from './syntaxes/circom'
import { IPosition } from 'monaco-editor'
enum MarkerSeverity {
Hint = 1,
@ -107,7 +109,8 @@ export type EditorAPIType = {
clearDecorationsByPlugin: (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => DecorationsReturn
keepDecorationsFor: (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => DecorationsReturn
addErrorMarker: (errors: errorMarker[], from: string) => void
clearErrorMarkers: (sources: string[] | {[fileName: string]: any}, from: string) => void
clearErrorMarkers: (sources: string[] | {[fileName: string]: any}, from: string) => void,
getPositionAt: (offset: number) => monacoTypes.IPosition
}
/* eslint-disable-next-line */
@ -550,7 +553,11 @@ export const EditorUI = (props: EditorUIProps) => {
if (!editorRef.current) return
return editorRef.current.getOption(43).fontSize
}
;(window as any).addRemixBreakpoint = (position) => {
props.editorAPI.getPositionAt = (offset: number): IPosition => {
return editorRef.current.getModel().getPositionAt(offset);
}
;(window as any).addRemixBreakpoint = (position) => {
// make it available from e2e testing...
const model = editorRef.current.getModel()
if (model) {

@ -177,10 +177,6 @@ export const TabsUI = (props: TabsUIProps) => {
} else if (tabsState.currentExt === 'sol' || tabsState.currentExt === 'yul') {
await props.plugin.call('solidity', 'compile', path)
_paq.push(['trackEvent', 'editor', 'clickRunFromEditor', tabsState.currentExt])
} else if (tabsState.currentExt === 'circom') {
await props.plugin.call('circuit-compiler', 'compile', path)
console.log('called cricuit compiler: ', path)
_paq.push(['trackEvent', 'editor', 'clickRunFromEditor', tabsState.currentExt])
}
}}
>

@ -150,6 +150,7 @@
"brace": "^0.8.0",
"change-case": "^4.1.1",
"chokidar": "^2.1.8",
"circom_wasm": "circom_wasm",
"color-support": "^1.1.3",
"commander": "^9.4.1",
"core-js": "^3.6.5",

@ -9897,6 +9897,11 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
inherits "^2.0.1"
safe-buffer "^5.0.1"
circom_wasm@circom_wasm:
version "0.0.0-alpha.1"
resolved "https://registry.yarnpkg.com/circom_wasm/-/circom_wasm-0.0.0-alpha.1.tgz#9578c82d78c5e02527ae3d65c9a57d98c3e9a53d"
integrity sha512-z1QKPvqhdLdfUw1hy/WBmi4wDj2UHN7o9Mq9QptGZkpKC9PzB5HCRj3v3uTI1ZJPH6IR6L1ytbrptM7VV3O0fA==
circular-json@^0.3.0:
version "0.3.3"
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66"
@ -10309,7 +10314,7 @@ comma-separated-tokens@^2.0.0:
resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee"
integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==
command-exists@^1.2.7, command-exists@^1.2.8:
command-exists@^1.2.8:
version "1.2.9"
resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69"
integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==
@ -27536,7 +27541,7 @@ watchify@^3.9.0:
through2 "^2.0.0"
xtend "^4.0.0"
watchpack@^2.1.1, watchpack@^2.4.0:
watchpack@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==

Loading…
Cancel
Save