Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

Commit

Permalink
Parser and Unparser
Browse files Browse the repository at this point in the history
  • Loading branch information
recmo committed Oct 10, 2018
1 parent f156ebf commit 02382c1
Show file tree
Hide file tree
Showing 10 changed files with 357 additions and 0 deletions.
2 changes: 2 additions & 0 deletions packages/sol-meta/bin/sol-meta.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/usr/bin/env node
require('../lib/src/cli.js')
6 changes: 6 additions & 0 deletions packages/sol-meta/src/astToMock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

import { ASTNode } from 'solidity-parser-antlr';

export function astToMock(ast: ASTNode): ASTNode {
return ast;
}
40 changes: 40 additions & 0 deletions packages/sol-meta/src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env node
// We need the above pragma since this script will be run as a command-line tool.

import { logUtils } from '@0xproject/utils';
import * as _ from 'lodash';
import 'source-map-support/register';
import * as yargs from 'yargs';

import { Compiler } from './compiler';

const DEFAULT_CONTRACTS_LIST = '*';
const SEPARATOR = ',';

(async () => {
const argv = yargs
.option('contracts-dir', {
type: 'string',
description: 'path of contracts directory to compile',
})
.option('contracts', {
type: 'string',
description: 'comma separated list of contracts to compile',
})
.help().argv;
const contracts = _.isUndefined(argv.contracts)
? undefined
: argv.contracts === DEFAULT_CONTRACTS_LIST
? DEFAULT_CONTRACTS_LIST
: argv.contracts.split(SEPARATOR);
const opts = {
contractsDir: argv.contractsDir,
artifactsDir: argv.artifactsDir,
contracts,
};
const compiler = new Compiler(opts);
await compiler.compileAsync();
})().catch(err => {
logUtils.log(err);
process.exit(1);
});
32 changes: 32 additions & 0 deletions packages/sol-meta/src/compiler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import fs = require('fs');
import { parse } from './parser';
import { unparse } from './unparser';

export interface CompilerOptions {
contractsDir: string;
contracts: string;
}

export class Compiler {

private readonly opts: CompilerOptions;

constructor(opts?: CompilerOptions) {
this.opts = opts || {
contractsDir: '',
contracts: '',
} ;
}

public async compileAsync() {
console.log(this.opts.contracts[0]);
const source = fs.readFileSync(this.opts.contracts[0], 'utf8');
try {
const ast = parse(source);
console.log(unparse(ast));
} catch (e) {
console.log(e);
}
console.log('Compiling');
}
}
Empty file added packages/sol-meta/src/index.ts
Empty file.
3 changes: 3 additions & 0 deletions packages/sol-meta/src/parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import * as parser from 'solidity-parser-antlr';

export const parse = (source) => parser.parse(source, {});
13 changes: 13 additions & 0 deletions packages/sol-meta/src/transform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ASTNode } from 'solidity-parser-antlr';

const visitor = {

// If a function is not public, add a wrapper to make it public
FunctionDefinition: func =>
func.visbility,

}

export function transform(ast: ASTNode): ASTNode {
return (visitor[ast.type] || (a => a))(ast);
}
213 changes: 213 additions & 0 deletions packages/sol-meta/src/unparser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import { ASTNode } from 'solidity-parser-antlr';

const stresc = (s: string) => `\"${s}\"`;
const indent = (s: string) => `\t${s.replace(/\n/g, '\n\t')}`;
const block = (s: string) => `{\n${indent(s)}\n}`;
const unparen = (s: string) => s.replace(/^\((.*)\)$/, '$1');

const visitor = {
// Source level

SourceUnit: ({children}) =>
children.map(unparse).join('\n'),

PragmaDirective: ({ name, value }) =>
`pragma ${name} ${value};`,

ImportDirective: ({ path, symbolAliases }) =>
`import `+
(symbolAliases ? `{${symbolAliases.map(([from, to]) =>
from + (to ? ` as ${to}` : '')
).join(', ')}} from `: '') +
`${stresc(path)};`,

ContractDefinition: ({name, kind, baseContracts, subNodes}) =>
`${kind} ${name} ${(baseContracts.length > 0 ? 'is ' : '')}` +
baseContracts.map(unparse).join(', ') +
block('\n' + subNodes.map(unparse).join('\n\n')),

InheritanceSpecifier: ({baseName: {namePath}}) =>
namePath,

// Contract level

UsingForDeclaration: ({typeName, libraryName}) =>
`using ${libraryName} for ${unparse(typeName)};`,

StateVariableDeclaration: ({variables}) =>
variables.map(unparse).join(', ') + ';',

StructDefinition: ({name, members}) =>
`struct ${name} ${block(members.map(unparse).join(';\n') + ';')}`,

EnumDefinition: ({name, members}) =>
`enum ${name} ${block(members.map(unparse).join(',\n'))}`,

EnumValue: ({name}) =>
name,

EventDefinition: ({ name, parameters }) =>
`event ${name}${unparse(parameters)};`,

ModifierDefinition: ({name, parameters, body}) =>
`modifier ${name}${Array.isArray(parameters) ? '' : unparse(parameters)} ${unparse(body)}`,
// Note: when there is no parameter block, instead of an ASTNode there is a []

FunctionDefinition: ({visibility, name, parameters, body, modifiers, isConstructor, stateMutability}) =>
(isConstructor ? 'constructor' : `function ${name}`) +
unparse(parameters) + ' ' +
(visibility && visibility != 'default' ? visibility + ' ' : '') + (stateMutability || '') + '\n' +
indent(modifiers.map(unparse).join('\n')) + '\n' +
(body ? unparse(body) : ';'),

ParameterList: ({parameters}) =>
`(${parameters.map(unparse).join(', ')})`,

Parameter: ({typeName, name, storageLocation}) =>
`${unparse(typeName)} ${storageLocation || ''} ${name || ''}`,

ModifierInvocation: ({name, arguments: args}) =>
`${name}(${args.map(unparse).join(', ')})`,

// Statements

Block: ({statements}) =>
block(statements.map(unparse).join('\n')),

VariableDeclarationStatement: ({variables, initialValue}) =>
variables.map(unparse) +
(initialValue ? ` = ${unparse(initialValue)};` : ';'),

ExpressionStatement: ({expression}) =>
`${unparse(expression)};`,

EmitStatement: ({eventCall}) =>
`emit ${unparen(unparse(eventCall))};`,

ReturnStatement: ({expression}) =>
`return ${expression ? unparse(expression) : ''};`,

BreakStatement: ({}) =>
`break;`,

ContinueStatement: ({}) =>
`continue;`,

ThrowStatement: ({}) =>
`throw;`,

IfStatement: ({condition, trueBody, falseBody}) =>
`if (${unparse(condition)})\n${unparse(trueBody)}` +
(falseBody ? `else\n${unparse(falseBody)}` : ''),

ForStatement: ({initExpression: i, conditionExpression: c, loopExpression: l, body}) =>
`for (${unparse(i).replace(';','')}; ${unparse(c)}; ${unparse(l).replace(';','')}) ${unparse(body)}`,

InlineAssemblyStatement: ({language, body}) => // TODO language
`assembly ${unparse(body)}`,

// Types

ElementaryTypeName: ({name}) =>
name,

UserDefinedTypeName: ({namePath}) =>
namePath,

ArrayTypeName: ({baseTypeName, length}) =>
`${unparse(baseTypeName)}[${length ? unparse(length) : ''}]`,

Mapping: ({keyType, valueType}) =>
`mapping (${unparse(keyType)} => ${unparse(valueType)})`,

// Expressions

Identifier: ({ name }) =>
name,

BooleanLiteral: ({ value }) =>
value ? 'true' : 'false',

NumberLiteral: ({number, subdenomination}) => // TODO subdenomination
number,

StringLiteral: ({value}) =>
stresc(value),

FunctionCall: ({expression, arguments: args, names}) => // TODO: names
`(${unparse(expression)}(${args.map(unparse).join(', ')}))`,

Conditional: ({condition, trueExpression, falseExpression}) =>
`(${unparse(condition)} ? ${unparse(trueExpression)} : ${unparse(falseExpression)})`,

UnaryOperation: ({operator, subExpression, isPrefix}) =>
`(${isPrefix ? operator : ''}${unparse(subExpression)}${isPrefix ? '' : operator})`,

BinaryOperation: ({operator, left, right}) =>
`(${unparse(left)} ${operator} ${unparse(right)})`,

MemberAccess: ({expression, memberName}) =>
`(${unparse(expression)}.${memberName})`,

IndexAccess: ({base, index}) =>
`(${unparse(base)}[${unparse(index)}])`,

ElementaryTypeNameExpression: ({typeName}) =>
`(${unparse(typeName)})`,

VariableDeclaration: ({typeName, name, visibility, isDeclaredConst, isIndexed, expression}) =>
`${unparse(typeName)} ` +
(isIndexed ? 'indexed ' : '') +
(visibility && visibility != 'default' ? visibility + ' ' : '') +
(isDeclaredConst ? 'constant ' : '') +
`${name}` +
(expression ? ` = ${unparse(expression)}` : ''),

NewExpression: ({typeName}) =>
`(new ${unparse(typeName)})`,

TupleExpression: ({components}) =>
`[${components.map(unparse).join(', ')}]`,

// Assembly

AssemblyBlock: ({operations}) =>
block(operations.map(unparse).join('\n')),

AssemblyAssignment: ({names, expression}) =>
`${names.map(unparse).join(', ')} := ${unparse(expression)}`,

AssemblyLocalDefinition: ({names, expression}) =>
`let ${names.map(unparse).join(', ')} := ${unparse(expression)}`,

AssemblyCall: ({functionName, arguments: args}) =>
args.length == 0 ?
functionName :
`${functionName}(${args.map(unparse).join(', ')})`,

AssemblyIf: ({condition, body}) =>
`if ${unparse(condition)} ${unparse(body)}`,

AssemblyFor: ({pre, condition, post, body}) =>
`for ${[pre, condition, post, body].map(unparse).join(' ')}`,

AssemblySwitch: ({expression, cases}) =>
`switch ${unparse(expression)}\n${cases.map(unparse).join('\n')}`,

AssemblyCase: ({value, block}) =>
`case ${unparse(value)} ${unparse(block)}`

DecimalNumber: ({ value }) =>
value,

HexNumber: ({ value }) =>
value,
}

export function unparse(ast: ASTNode): string {
return (visitor[ast.type] || (a => {
console.log(a);
console.trace();
return `<${a.type}>`;
}))(ast);
}
Empty file added packages/sol-meta/test/dump
Empty file.
48 changes: 48 additions & 0 deletions packages/sol-meta/test/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as chai from 'chai';
import 'mocha';
import * as glob from 'glob';
import * as fs from 'fs';
import * as path from 'path';
import { parse } from '../src/parser';
import { unparse } from '../src/unparser';

const expect = chai.expect;

const promisify = (func) => (...args) =>
new Promise((resolve, reject) =>
func(...args, (error, result) =>
error ? reject(error) : resolve(result)));

const findContracts = searchPath =>
glob.sync(searchPath).map(file => ({
name: path.basename(file, '.sol') + ` (${file})`,
source: fs.readFileSync(file, 'utf8')
}));

const contracts = findContracts('../contracts/src/**/*.sol');

describe('Parser', () => {

it('should have test contracts', () => {
expect(contracts).to.have.lengthOf.above(10);
});

contracts.forEach(({name, source}) =>
it(`should parse ${name}`, () => {
parse(source);
})
);

});

describe.only('Unparser', () => {

contracts.forEach(({name, source}) =>
it(`should unparse ${name}`, () => {
const ast = parse(source);
const src = unparse(ast) ;
const ast2 = parse(src);
//expect(ast2).to.deep.equal(ast);
})
);
})

0 comments on commit 02382c1

Please sign in to comment.