@solidity-parser/parser
Advanced tools
Comparing version 0.12.1 to 0.13.0-rc.0
{ | ||
"name": "@solidity-parser/parser", | ||
"version": "0.12.1", | ||
"version": "0.13.0-rc.0", | ||
"description": "A Solidity parser built from a robust ANTLR 4 grammar", | ||
"main": "dist/index.cjs.js", | ||
"browser": "dist/index.iife.js", | ||
"browser": { | ||
"fs": false, | ||
"path": false | ||
}, | ||
"files": [ | ||
@@ -11,12 +14,17 @@ "dist/**/*", | ||
], | ||
"types": "dist/index.d.ts", | ||
"types": "dist/src/index.d.ts", | ||
"scripts": { | ||
"antlr": "sh scripts/antlr.sh", | ||
"build": "rollup -c rollup.config.js", | ||
"antlr": "antlr4ts -visitor antlr/Solidity.g4 -o src", | ||
"build:browser": "esbuild src/index.ts --outfile=dist/index.iife.js --bundle --loader:.tokens=file --sourcemap --format=iife --global-name=SolidityParser --define:__dirname=true --define:BROWSER=true --inject:./process-shim.js", | ||
"build:node": "esbuild src/index.ts --outfile=dist/index.cjs.js --bundle --loader:.tokens=file --sourcemap --format=cjs --platform=node", | ||
"build": "npm run build:node && npm run build:browser && npm run generate-types && npm run copy-files", | ||
"generate-types": "tsc", | ||
"copy-files": "shx mkdir -p dist/antlr && shx cp './src/antlr/*tokens' dist/antlr", | ||
"prettier": "prettier --write 'src/**/*' 'test/**/*'", | ||
"prepack": "npm run build && npm run generate-types", | ||
"eslint": "eslint src", | ||
"lint": "eslint src", | ||
"test": "npm run build && nyc mocha" | ||
"prepack": "npm run build", | ||
"lint": "eslint src test", | ||
"test": "npm run test:node && npm run test:browser", | ||
"test:node": "mocha", | ||
"test:browser": "karma start karma.conf.js", | ||
"test:coverage": "nyc mocha" | ||
}, | ||
@@ -36,18 +44,14 @@ "authors": [ | ||
"devDependencies": { | ||
"@babel/cli": "^7.12.1", | ||
"@babel/core": "^7.9.0", | ||
"@babel/plugin-proposal-class-properties": "^7.12.1", | ||
"@babel/plugin-transform-typescript": "^7.12.1", | ||
"@babel/preset-env": "^7.12.1", | ||
"@babel/preset-typescript": "^7.12.7", | ||
"@babel/register": "^7.12.1", | ||
"@rollup/plugin-babel": "^5.2.2", | ||
"@rollup/plugin-commonjs": "^17.0.0", | ||
"@rollup/plugin-node-resolve": "^11.0.0", | ||
"@types/chai": "^4.2.16", | ||
"@types/mocha": "^8.2.2", | ||
"@types/node": "^14.14.41", | ||
"@typescript-eslint/eslint-plugin": "^4.9.0", | ||
"@typescript-eslint/parser": "^4.9.0", | ||
"antlr4": "^4.9.0", | ||
"babel-loader": "^8.1.0", | ||
"babel-plugin-inline-import": "^3.0.0", | ||
"antlr4ts": "^0.5.0-alpha.4", | ||
"antlr4ts-cli": "^0.5.0-alpha.4", | ||
"assert": "^2.0.0", | ||
"chai": "^4.2.0", | ||
"esbuild": "^0.11.13", | ||
"esbuild-register": "^2.5.0", | ||
"eslint": "^7.15.0", | ||
@@ -58,16 +62,23 @@ "eslint-plugin-import": "^2.18.2", | ||
"eslint-plugin-standard": "^4.0.1", | ||
"karma": "^6.3.2", | ||
"karma-chrome-launcher": "^3.1.0", | ||
"karma-mocha": "^2.0.1", | ||
"mocha": "^6.2.0", | ||
"nyc": "^14.1.1", | ||
"prettier": "^2.2.1", | ||
"raw-loader": "^4.0.0", | ||
"rollup": "^2.34.2", | ||
"rollup-plugin-node-polyfills": "^0.2.1", | ||
"puppeteer": "^9.0.0", | ||
"shx": "^0.3.3", | ||
"ts-node": "^9.1.1", | ||
"typescript": "^4.1.2", | ||
"util": "^0.12.3", | ||
"yarn": "^1.17.3" | ||
}, | ||
"nyc": { | ||
"include": [ | ||
"src/*.js" | ||
"extension": [ | ||
".ts" | ||
] | ||
}, | ||
"volta": { | ||
"node": "14.16.1" | ||
} | ||
} |
@@ -6,9 +6,13 @@ // Base on the original type definitions for solidity-parser-antlr 0.2 | ||
import { Token } from './types' | ||
interface Location { | ||
start: { | ||
line: number | ||
column: number | ||
} | ||
end: { | ||
line: number | ||
column: number | ||
} | ||
} | ||
export type AST = { | ||
errors?: any[] | ||
tokens?: Token[] | ||
} & ASTNode | ||
export interface BaseASTNode { | ||
@@ -20,98 +24,7 @@ type: ASTNodeTypeString | ||
export type ASTNodeTypeString = | ||
| 'SourceUnit' | ||
| 'PragmaDirective' | ||
| 'PragmaName' | ||
| 'PragmaValue' | ||
| 'ImportDirective' | ||
| 'ContractDefinition' | ||
| 'InheritanceSpecifier' | ||
| 'StateVariableDeclaration' | ||
| 'UsingForDeclaration' | ||
| 'StructDefinition' | ||
| 'ModifierDefinition' | ||
| 'ModifierInvocation' | ||
| 'FunctionDefinition' | ||
| 'EventDefinition' | ||
| 'EnumValue' | ||
| 'EnumDefinition' | ||
| 'VariableDeclaration' | ||
| 'UserDefinedTypeName' | ||
| 'Mapping' | ||
| 'ArrayTypeName' | ||
| 'FunctionTypeName' | ||
| 'StorageLocation' | ||
| 'StateMutability' | ||
| 'Block' | ||
| 'ExpressionStatement' | ||
| 'IfStatement' | ||
| 'WhileStatement' | ||
| 'ForStatement' | ||
| 'InlineAssemblyStatement' | ||
| 'DoWhileStatement' | ||
| 'ContinueStatement' | ||
| 'Break' | ||
| 'Continue' | ||
| 'BreakStatement' | ||
| 'ReturnStatement' | ||
| 'EmitStatement' | ||
| 'ThrowStatement' | ||
| 'VariableDeclarationStatement' | ||
| 'IdentifierList' | ||
| 'ElementaryTypeName' | ||
| 'FunctionCall' | ||
| 'AssemblyBlock' | ||
| 'AssemblyItem' | ||
| 'AssemblyCall' | ||
| 'AssemblyLocalDefinition' | ||
| 'AssemblyAssignment' | ||
| 'AssemblyStackAssignment' | ||
| 'LabelDefinition' | ||
| 'AssemblySwitch' | ||
| 'AssemblyCase' | ||
| 'AssemblyFunctionDefinition' | ||
| 'AssemblyFunctionReturns' | ||
| 'AssemblyFor' | ||
| 'AssemblyIf' | ||
| 'AssemblyLiteral' | ||
| 'SubAssembly' | ||
| 'TupleExpression' | ||
| 'TypeNameExpression' | ||
| 'NameValueExpression' | ||
| 'BooleanLiteral' | ||
| 'NumberLiteral' | ||
| 'Identifier' | ||
| 'BinaryOperation' | ||
| 'UnaryOperation' | ||
| 'NewExpression' | ||
| 'Conditional' | ||
| 'StringLiteral' | ||
| 'HexLiteral' | ||
| 'HexNumber' | ||
| 'DecimalNumber' | ||
| 'MemberAccess' | ||
| 'IndexAccess' | ||
| 'IndexRangeAccess' | ||
| 'NameValueList' | ||
| 'UncheckedStatement' | ||
export interface BaseASTNode { | ||
type: ASTNodeTypeString | ||
range?: [number, number] | ||
loc?: Location | ||
} | ||
export interface SourceUnit extends BaseASTNode { | ||
type: 'SourceUnit' | ||
children: ASTNode[] // TODO: Can be more precise | ||
} // tslint:disable-line:no-empty-interface | ||
export interface PragmaDirective extends BaseASTNode { | ||
type: 'PragmaDirective' | ||
name: string | ||
value: string | ||
children: ASTNode[] | ||
} | ||
export interface ImportDirective extends BaseASTNode { | ||
type: 'ImportDirective' | ||
path: string | ||
unitAlias: string | ||
symbolAliases: Array<[string, string]> | ||
} | ||
export interface ContractDefinition extends BaseASTNode { | ||
@@ -122,4 +35,5 @@ type: 'ContractDefinition' | ||
kind: string | ||
subNodes: ASTNode[] // TODO: Can be more precise | ||
subNodes: BaseASTNode[] | ||
} | ||
export interface InheritanceSpecifier extends BaseASTNode { | ||
@@ -130,10 +44,110 @@ type: 'InheritanceSpecifier' | ||
} | ||
export interface UserDefinedTypeName extends BaseASTNode { | ||
type: 'UserDefinedTypeName' | ||
namePath: string | ||
} | ||
export const astNodeTypes = [ | ||
'SourceUnit', | ||
'PragmaDirective', | ||
'ImportDirective', | ||
'ContractDefinition', | ||
'InheritanceSpecifier', | ||
'StateVariableDeclaration', | ||
'UsingForDeclaration', | ||
'StructDefinition', | ||
'ModifierDefinition', | ||
'ModifierInvocation', | ||
'FunctionDefinition', | ||
'EventDefinition', | ||
'EnumValue', | ||
'EnumDefinition', | ||
'VariableDeclaration', | ||
'UserDefinedTypeName', | ||
'Mapping', | ||
'ArrayTypeName', | ||
'FunctionTypeName', | ||
'Block', | ||
'ExpressionStatement', | ||
'IfStatement', | ||
'WhileStatement', | ||
'ForStatement', | ||
'InlineAssemblyStatement', | ||
'DoWhileStatement', | ||
'ContinueStatement', | ||
'Break', | ||
'Continue', | ||
'BreakStatement', | ||
'ReturnStatement', | ||
'EmitStatement', | ||
'ThrowStatement', | ||
'VariableDeclarationStatement', | ||
'ElementaryTypeName', | ||
'FunctionCall', | ||
'AssemblyBlock', | ||
'AssemblyCall', | ||
'AssemblyLocalDefinition', | ||
'AssemblyAssignment', | ||
'AssemblyStackAssignment', | ||
'LabelDefinition', | ||
'AssemblySwitch', | ||
'AssemblyCase', | ||
'AssemblyFunctionDefinition', | ||
'AssemblyFunctionReturns', | ||
'AssemblyFor', | ||
'AssemblyIf', | ||
'SubAssembly', | ||
'TupleExpression', | ||
'TypeNameExpression', | ||
'NameValueExpression', | ||
'BooleanLiteral', | ||
'NumberLiteral', | ||
'Identifier', | ||
'BinaryOperation', | ||
'UnaryOperation', | ||
'NewExpression', | ||
'Conditional', | ||
'StringLiteral', | ||
'HexLiteral', | ||
'HexNumber', | ||
'DecimalNumber', | ||
'MemberAccess', | ||
'IndexAccess', | ||
'IndexRangeAccess', | ||
'NameValueList', | ||
'UncheckedStatement', | ||
'TryStatement', | ||
'CatchClause', | ||
'FileLevelConstant', | ||
'AssemblyMemberAccess', | ||
] as const | ||
export type ASTNodeTypeString = typeof astNodeTypes[number] | ||
export interface PragmaDirective extends BaseASTNode { | ||
type: 'PragmaDirective' | ||
name: string | ||
value: string | ||
} | ||
export interface ImportDirective extends BaseASTNode { | ||
type: 'ImportDirective' | ||
path: string | ||
unitAlias: string | null | ||
symbolAliases: Array<[string, string | null]> | null | ||
} | ||
export interface StateVariableDeclaration extends BaseASTNode { | ||
type: 'StateVariableDeclaration' | ||
variables: StateVariableDeclarationVariable[] | ||
initialValue?: Expression | ||
initialValue: Expression | null | ||
} | ||
export interface FileLevelConstant extends BaseASTNode { | ||
type: 'FileLevelConstant' | ||
typeName: TypeName | ||
name: string | ||
initialValue: Expression | ||
} | ||
export interface UsingForDeclaration extends BaseASTNode { | ||
type: 'UsingForDeclaration' | ||
typeName: TypeName | ||
typeName: TypeName | null | ||
libraryName: string | ||
@@ -161,10 +175,10 @@ } | ||
type: 'FunctionDefinition' | ||
name?: string | ||
name: string | null | ||
parameters: VariableDeclaration[] | ||
modifiers: ModifierInvocation[] | ||
stateMutability?: 'pure' | 'constant' | 'payable' | 'view' | ||
stateMutability: 'pure' | 'constant' | 'payable' | 'view' | null | ||
visibility: 'default' | 'external' | 'internal' | 'public' | 'private' | ||
returnParameters?: VariableDeclaration[] | ||
body?: Block | ||
override: null | UserDefinedTypeName[] | ||
returnParameters: VariableDeclaration[] | null | ||
body: Block | null | ||
override: UserDefinedTypeName[] | null | ||
isConstructor: boolean | ||
@@ -179,2 +193,3 @@ isReceiveEther: boolean | ||
parameters: VariableDeclaration[] | ||
isAnonymous: boolean | ||
} | ||
@@ -194,7 +209,7 @@ export interface EnumValue extends BaseASTNode { | ||
isStateVar: boolean | ||
typeName: TypeName | ||
name: string | ||
typeName: TypeName | null | ||
name: string | null | ||
isDeclaredConst?: boolean | ||
storageLocation?: string | ||
expression?: Expression | ||
storageLocation: string | null | ||
expression: Expression | null | ||
visibility?: 'public' | 'private' | 'internal' | 'default' | ||
@@ -206,6 +221,2 @@ } | ||
} | ||
export interface UserDefinedTypeName extends BaseASTNode { | ||
type: 'UserDefinedTypeName' | ||
namePath: string | ||
} | ||
export interface ArrayTypeName extends BaseASTNode { | ||
@@ -218,3 +229,3 @@ type: 'ArrayTypeName' | ||
type: 'Mapping' | ||
keyType: ElementaryTypeName | ||
keyType: ElementaryTypeName | UserDefinedTypeName | ||
valueType: TypeName | ||
@@ -224,14 +235,15 @@ } | ||
type: 'FunctionTypeName' | ||
parameterTypes: TypeName[] | ||
returnTypes: TypeName[] | ||
parameterTypes: VariableDeclaration[] | ||
returnTypes: VariableDeclaration[] | ||
visibility: string | ||
stateMutability: string | ||
stateMutability: string | null | ||
} | ||
export interface Block extends BaseASTNode { | ||
type: 'Block' | ||
statements: Statement[] | ||
statements: BaseASTNode[] | ||
} | ||
export interface ExpressionStatement extends BaseASTNode { | ||
type: 'ExpressionStatement' | ||
expression: Expression | ||
expression: Expression | null | ||
} | ||
@@ -242,3 +254,3 @@ export interface IfStatement extends BaseASTNode { | ||
trueBody: Statement | ||
falseBody?: Statement | ||
falseBody: Statement | null | ||
} | ||
@@ -249,4 +261,19 @@ export interface UncheckedStatement extends BaseASTNode { | ||
} | ||
export interface TryStatement extends BaseASTNode { | ||
type: 'TryStatement' | ||
expression: Expression | ||
returnParameters: VariableDeclaration[] | null | ||
body: Block | ||
catchClauses: CatchClause[] | ||
} | ||
export interface CatchClause extends BaseASTNode { | ||
type: 'CatchClause' | ||
isReasonStringType: boolean | ||
kind: string | null | ||
parameters: VariableDeclaration[] | null | ||
body: Block | ||
} | ||
export interface WhileStatement extends BaseASTNode { | ||
type: 'WhileStatement' | ||
condition: Expression | ||
body: Statement | ||
@@ -256,5 +283,5 @@ } | ||
type: 'ForStatement' | ||
initExpression?: SimpleStatement | ||
initExpression: SimpleStatement | null | ||
conditionExpression?: Expression | ||
loopExpression?: ExpressionStatement | ||
loopExpression: ExpressionStatement | ||
body: Statement | ||
@@ -264,3 +291,3 @@ } | ||
type: 'InlineAssemblyStatement' | ||
language: string | ||
language: string | null | ||
body: AssemblyBlock | ||
@@ -298,4 +325,4 @@ } | ||
type: 'VariableDeclarationStatement' | ||
variables: ASTNode[] | ||
initialValue?: Expression | ||
variables: Array<BaseASTNode | null> | ||
initialValue: Expression | null | ||
} | ||
@@ -305,2 +332,3 @@ export interface ElementaryTypeName extends BaseASTNode { | ||
name: string | ||
stateMutability: string | null | ||
} | ||
@@ -324,22 +352,35 @@ export interface FunctionCall extends BaseASTNode { | ||
type: 'AssemblyLocalDefinition' | ||
names: Identifier[] | AssemblyMemberAccess[] | ||
expression: AssemblyExpression | ||
} | ||
export interface AssemblyAssignment extends BaseASTNode { | ||
type: 'AssemblyAssignment' | ||
names: Identifier[] | AssemblyMemberAccess[] | ||
expression: AssemblyExpression | ||
names: Identifier[] | ||
} | ||
export interface AssemblyStackAssignment extends BaseASTNode { | ||
type: 'AssemblyStackAssignment' | ||
name: string | ||
} | ||
export interface LabelDefinition extends BaseASTNode { | ||
type: 'LabelDefinition' | ||
name: string | ||
} | ||
export interface AssemblySwitch extends BaseASTNode { | ||
type: 'AssemblySwitch' | ||
expression: AssemblyExpression | ||
cases: AssemblyCase[] | ||
} | ||
export interface AssemblyCase extends BaseASTNode { | ||
type: 'AssemblyCase' | ||
value: AssemblyLiteral | null | ||
block: AssemblyBlock | ||
default: boolean | ||
} | ||
export interface AssemblyFunctionDefinition extends BaseASTNode { | ||
type: 'AssemblyFunctionDefinition' | ||
name: string | ||
arguments: Identifier[] | ||
returnArguments: Identifier[] | ||
body: AssemblyBlock | ||
} | ||
@@ -351,12 +392,25 @@ export interface AssemblyFunctionReturns extends BaseASTNode { | ||
type: 'AssemblyFor' | ||
pre: AssemblyBlock | AssemblyExpression | ||
condition: AssemblyExpression | ||
post: AssemblyBlock | AssemblyExpression | ||
body: AssemblyBlock | ||
} | ||
export interface AssemblyIf extends BaseASTNode { | ||
type: 'AssemblyIf' | ||
condition: AssemblyExpression | ||
body: AssemblyBlock | ||
} | ||
export interface AssemblyLiteral extends BaseASTNode { | ||
type: 'AssemblyLiteral' | ||
} | ||
export type AssemblyLiteral = | ||
| StringLiteral | ||
| DecimalNumber | ||
| HexNumber | ||
| HexLiteral | ||
export interface SubAssembly extends BaseASTNode { | ||
type: 'SubAssembly' | ||
} | ||
export interface AssemblyMemberAccess extends BaseASTNode { | ||
type: 'AssemblyMemberAccess' | ||
expression: Identifier | ||
memberName: Identifier | ||
} | ||
export interface NewExpression extends BaseASTNode { | ||
@@ -368,3 +422,3 @@ type: 'NewExpression' | ||
type: 'TupleExpression' | ||
components: Expression[] | ||
components: Array<BaseASTNode | null> | ||
isArray: boolean | ||
@@ -379,3 +433,3 @@ } | ||
expression: Expression | ||
arguments: { [name: string]: Expression } | ||
arguments: NameValueList | ||
} | ||
@@ -417,34 +471,49 @@ export interface NumberLiteral extends BaseASTNode { | ||
} | ||
export type BinOp = | ||
| '+' | ||
| '-' | ||
| '*' | ||
| '/' | ||
| '**' | ||
| '%' | ||
| '<<' | ||
| '>>' | ||
| '&&' | ||
| '||' | ||
| '&' | ||
| '|' | ||
| '^' | ||
| '<' | ||
| '>' | ||
| '<=' | ||
| '>=' | ||
| '==' | ||
| '!=' | ||
| '=' | ||
| '|=' | ||
| '^=' | ||
| '&=' | ||
| '<<=' | ||
| '>>=' | ||
| '+=' | ||
| '-=' | ||
| '*=' | ||
| '/=' | ||
| '%=' | ||
export type UnaryOp = '-' | '+' | '++' | '~' | 'after' | 'delete' | '!' | ||
export const binaryOpValues = [ | ||
'+', | ||
'-', | ||
'*', | ||
'/', | ||
'**', | ||
'%', | ||
'<<', | ||
'>>', | ||
'&&', | ||
'||', | ||
',,', | ||
'&', | ||
',', | ||
'^', | ||
'<', | ||
'>', | ||
'<=', | ||
'>=', | ||
'==', | ||
'!=', | ||
'=', | ||
',=', | ||
'^=', | ||
'&=', | ||
'<<=', | ||
'>>=', | ||
'+=', | ||
'-=', | ||
'*=', | ||
'/=', | ||
'%=', | ||
] as const | ||
export type BinOp = typeof binaryOpValues[number] | ||
export const unaryOpValues = [ | ||
'-', | ||
'+', | ||
'++', | ||
'~', | ||
'after', | ||
'delete', | ||
'!', | ||
] as const | ||
export type UnaryOp = typeof unaryOpValues[number] | ||
export interface BinaryOperation extends BaseASTNode { | ||
@@ -465,4 +534,4 @@ type: 'BinaryOperation' | ||
condition: Expression | ||
trueExpression: ASTNode | ||
falseExpression: ASTNode | ||
trueExpression: Expression | ||
falseExpression: Expression | ||
} | ||
@@ -496,3 +565,3 @@ export interface IndexAccess extends BaseASTNode { | ||
names: string[] | ||
args: Expression[] | ||
arguments: Expression[] | ||
} | ||
@@ -544,2 +613,7 @@ export type ASTNode = | ||
| Expression | ||
| NameValueList | ||
| AssemblyMemberAccess | ||
| CatchClause | ||
| FileLevelConstant | ||
export type AssemblyItem = | ||
@@ -580,2 +654,4 @@ | Identifier | ||
| BooleanLiteral | ||
| HexLiteral | ||
| StringLiteral | ||
| NumberLiteral | ||
@@ -607,1 +683,204 @@ | Identifier | ||
| UncheckedStatement | ||
| TryStatement | ||
interface ASTVisitorEnter { | ||
SourceUnit?: (node: SourceUnit) => any | ||
PragmaDirective?: (node: PragmaDirective) => any | ||
ImportDirective?: (node: ImportDirective) => any | ||
ContractDefinition?: (node: ContractDefinition) => any | ||
InheritanceSpecifier?: (node: InheritanceSpecifier) => any | ||
StateVariableDeclaration?: (node: StateVariableDeclaration) => any | ||
UsingForDeclaration?: (node: UsingForDeclaration) => any | ||
StructDefinition?: (node: StructDefinition) => any | ||
ModifierDefinition?: (node: ModifierDefinition) => any | ||
ModifierInvocation?: (node: ModifierInvocation) => any | ||
FunctionDefinition?: (node: FunctionDefinition) => any | ||
EventDefinition?: (node: EventDefinition) => any | ||
EnumValue?: (node: EnumValue) => any | ||
EnumDefinition?: (node: EnumDefinition) => any | ||
VariableDeclaration?: (node: VariableDeclaration) => any | ||
UserDefinedTypeName?: (node: UserDefinedTypeName) => any | ||
Mapping?: (node: Mapping) => any | ||
ArrayTypeName?: (node: ArrayTypeName) => any | ||
FunctionTypeName?: (node: FunctionTypeName) => any | ||
Block?: (node: Block) => any | ||
ExpressionStatement?: (node: ExpressionStatement) => any | ||
IfStatement?: (node: IfStatement) => any | ||
WhileStatement?: (node: WhileStatement) => any | ||
ForStatement?: (node: ForStatement) => any | ||
InlineAssemblyStatement?: (node: InlineAssemblyStatement) => any | ||
DoWhileStatement?: (node: DoWhileStatement) => any | ||
ContinueStatement?: (node: ContinueStatement) => any | ||
Break?: (node: Break) => any | ||
Continue?: (node: Continue) => any | ||
BreakStatement?: (node: BreakStatement) => any | ||
ReturnStatement?: (node: ReturnStatement) => any | ||
EmitStatement?: (node: EmitStatement) => any | ||
ThrowStatement?: (node: ThrowStatement) => any | ||
VariableDeclarationStatement?: (node: VariableDeclarationStatement) => any | ||
ElementaryTypeName?: (node: ElementaryTypeName) => any | ||
FunctionCall?: (node: FunctionCall) => any | ||
AssemblyBlock?: (node: AssemblyBlock) => any | ||
AssemblyCall?: (node: AssemblyCall) => any | ||
AssemblyLocalDefinition?: (node: AssemblyLocalDefinition) => any | ||
AssemblyAssignment?: (node: AssemblyAssignment) => any | ||
AssemblyStackAssignment?: (node: AssemblyStackAssignment) => any | ||
LabelDefinition?: (node: LabelDefinition) => any | ||
AssemblySwitch?: (node: AssemblySwitch) => any | ||
AssemblyCase?: (node: AssemblyCase) => any | ||
AssemblyFunctionDefinition?: (node: AssemblyFunctionDefinition) => any | ||
AssemblyFunctionReturns?: (node: AssemblyFunctionReturns) => any | ||
AssemblyFor?: (node: AssemblyFor) => any | ||
AssemblyIf?: (node: AssemblyIf) => any | ||
SubAssembly?: (node: SubAssembly) => any | ||
TupleExpression?: (node: TupleExpression) => any | ||
TypeNameExpression?: (node: TypeNameExpression) => any | ||
NameValueExpression?: (node: NameValueExpression) => any | ||
BooleanLiteral?: (node: BooleanLiteral) => any | ||
NumberLiteral?: (node: NumberLiteral) => any | ||
Identifier?: (node: Identifier) => any | ||
BinaryOperation?: (node: BinaryOperation) => any | ||
UnaryOperation?: (node: UnaryOperation) => any | ||
NewExpression?: (node: NewExpression) => any | ||
Conditional?: (node: Conditional) => any | ||
StringLiteral?: (node: StringLiteral) => any | ||
HexLiteral?: (node: HexLiteral) => any | ||
HexNumber?: (node: HexNumber) => any | ||
DecimalNumber?: (node: DecimalNumber) => any | ||
MemberAccess?: (node: MemberAccess) => any | ||
IndexAccess?: (node: IndexAccess) => any | ||
IndexRangeAccess?: (node: IndexRangeAccess) => any | ||
NameValueList?: (node: NameValueList) => any | ||
UncheckedStatement?: (node: UncheckedStatement) => any | ||
TryStatement?: (node: TryStatement) => any | ||
CatchClause?: (node: CatchClause) => any | ||
FileLevelConstant?: (node: FileLevelConstant) => any | ||
AssemblyMemberAccess?: (node: AssemblyMemberAccess) => any | ||
} | ||
interface ASTVisitorExit { | ||
'SourceUnit:exit'?: (node: SourceUnit) => any | ||
'PragmaDirective:exit'?: (node: PragmaDirective) => any | ||
'ImportDirective:exit'?: (node: ImportDirective) => any | ||
'ContractDefinition:exit'?: (node: ContractDefinition) => any | ||
'InheritanceSpecifier:exit'?: (node: InheritanceSpecifier) => any | ||
'StateVariableDeclaration:exit'?: (node: StateVariableDeclaration) => any | ||
'UsingForDeclaration:exit'?: (node: UsingForDeclaration) => any | ||
'StructDefinition:exit'?: (node: StructDefinition) => any | ||
'ModifierDefinition:exit'?: (node: ModifierDefinition) => any | ||
'ModifierInvocation:exit'?: (node: ModifierInvocation) => any | ||
'FunctionDefinition:exit'?: (node: FunctionDefinition) => any | ||
'EventDefinition:exit'?: (node: EventDefinition) => any | ||
'EnumValue:exit'?: (node: EnumValue) => any | ||
'EnumDefinition:exit'?: (node: EnumDefinition) => any | ||
'VariableDeclaration:exit'?: (node: VariableDeclaration) => any | ||
'UserDefinedTypeName:exit'?: (node: UserDefinedTypeName) => any | ||
'Mapping:exit'?: (node: Mapping) => any | ||
'ArrayTypeName:exit'?: (node: ArrayTypeName) => any | ||
'FunctionTypeName:exit'?: (node: FunctionTypeName) => any | ||
'Block:exit'?: (node: Block) => any | ||
'ExpressionStatement:exit'?: (node: ExpressionStatement) => any | ||
'IfStatement:exit'?: (node: IfStatement) => any | ||
'WhileStatement:exit'?: (node: WhileStatement) => any | ||
'ForStatement:exit'?: (node: ForStatement) => any | ||
'InlineAssemblyStatement:exit'?: (node: InlineAssemblyStatement) => any | ||
'DoWhileStatement:exit'?: (node: DoWhileStatement) => any | ||
'ContinueStatement:exit'?: (node: ContinueStatement) => any | ||
'Break:exit'?: (node: Break) => any | ||
'Continue:exit'?: (node: Continue) => any | ||
'BreakStatement:exit'?: (node: BreakStatement) => any | ||
'ReturnStatement:exit'?: (node: ReturnStatement) => any | ||
'EmitStatement:exit'?: (node: EmitStatement) => any | ||
'ThrowStatement:exit'?: (node: ThrowStatement) => any | ||
'VariableDeclarationStatement:exit'?: ( | ||
node: VariableDeclarationStatement | ||
) => any | ||
'ElementaryTypeName:exit'?: (node: ElementaryTypeName) => any | ||
'FunctionCall:exit'?: (node: FunctionCall) => any | ||
'AssemblyBlock:exit'?: (node: AssemblyBlock) => any | ||
'AssemblyCall:exit'?: (node: AssemblyCall) => any | ||
'AssemblyLocalDefinition:exit'?: (node: AssemblyLocalDefinition) => any | ||
'AssemblyAssignment:exit'?: (node: AssemblyAssignment) => any | ||
'AssemblyStackAssignment:exit'?: (node: AssemblyStackAssignment) => any | ||
'LabelDefinition:exit'?: (node: LabelDefinition) => any | ||
'AssemblySwitch:exit'?: (node: AssemblySwitch) => any | ||
'AssemblyCase:exit'?: (node: AssemblyCase) => any | ||
'AssemblyFunctionDefinition:exit'?: (node: AssemblyFunctionDefinition) => any | ||
'AssemblyFunctionReturns:exit'?: (node: AssemblyFunctionReturns) => any | ||
'AssemblyFor:exit'?: (node: AssemblyFor) => any | ||
'AssemblyIf:exit'?: (node: AssemblyIf) => any | ||
'SubAssembly:exit'?: (node: SubAssembly) => any | ||
'TupleExpression:exit'?: (node: TupleExpression) => any | ||
'TypeNameExpression:exit'?: (node: TypeNameExpression) => any | ||
'NameValueExpression:exit'?: (node: NameValueExpression) => any | ||
'BooleanLiteral:exit'?: (node: BooleanLiteral) => any | ||
'NumberLiteral:exit'?: (node: NumberLiteral) => any | ||
'Identifier:exit'?: (node: Identifier) => any | ||
'BinaryOperation:exit'?: (node: BinaryOperation) => any | ||
'UnaryOperation:exit'?: (node: UnaryOperation) => any | ||
'NewExpression:exit'?: (node: NewExpression) => any | ||
'Conditional:exit'?: (node: Conditional) => any | ||
'StringLiteral:exit'?: (node: StringLiteral) => any | ||
'HexLiteral:exit'?: (node: HexLiteral) => any | ||
'HexNumber:exit'?: (node: HexNumber) => any | ||
'DecimalNumber:exit'?: (node: DecimalNumber) => any | ||
'MemberAccess:exit'?: (node: MemberAccess) => any | ||
'IndexAccess:exit'?: (node: IndexAccess) => any | ||
'IndexRangeAccess:exit'?: (node: IndexRangeAccess) => any | ||
'NameValueList:exit'?: (node: NameValueList) => any | ||
'UncheckedStatement:exit'?: (node: UncheckedStatement) => any | ||
'TryStatement:exit'?: (node: TryStatement) => any | ||
'CatchClause:exit'?: (node: CatchClause) => any | ||
'FileLevelConstant:exit'?: (node: FileLevelConstant) => any | ||
'AssemblyMemberAccess:exit'?: (node: AssemblyMemberAccess) => any | ||
} | ||
export type ASTVisitor = ASTVisitorEnter & ASTVisitorExit | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
/** | ||
* This monstrosity is here to check that there are no ASTNodeTypeString without | ||
* a corresponding ASTNode, no ASTNode without a corresponding ASTNodeTypeString, | ||
* no ASTVisitorEnter callback without a corresponding ASTNode, | ||
* no ASTVisitorExit callback without a corresponding ASTVisitorEnter callback, | ||
* and so on, and so on. | ||
* | ||
* There are probably some ways to simplify this by deriving some types | ||
* from others. | ||
*/ | ||
function checkTypes() { | ||
const astNodeType: ASTNode['type'] = '' as any | ||
const astNodeTypeString: ASTNodeTypeString = '' as any | ||
const astVisitorEnterKey: keyof ASTVisitorEnter = '' as any | ||
let assignAstNodeType: ASTNode['type'] = astNodeTypeString | ||
assignAstNodeType = astVisitorEnterKey | ||
let assignAstNodeTyeString: ASTNodeTypeString = astNodeType | ||
assignAstNodeTyeString = astVisitorEnterKey | ||
let assignAstVisitorEnterKey: keyof ASTVisitorEnter = astNodeType | ||
assignAstVisitorEnterKey = astNodeTypeString | ||
const astNodeTypeExit: `${ASTNode['type']}:exit` = '' as any | ||
const astNodeTypeStringExit: `${ASTNodeTypeString}:exit` = '' as any | ||
const astVisitorEnterKeyExit: `${keyof ASTVisitorEnter}:exit` = '' as any | ||
const astVisitorExitKey: keyof ASTVisitorExit = '' as any | ||
let letAstNodeTypeExit: `${ASTNode['type']}:exit` = astNodeTypeStringExit | ||
letAstNodeTypeExit = astVisitorEnterKeyExit | ||
letAstNodeTypeExit = astVisitorExitKey | ||
let assignAstNodeTypeStringExit: `${ASTNodeTypeString}:exit` = astNodeTypeExit | ||
assignAstNodeTypeStringExit = astVisitorEnterKeyExit | ||
assignAstNodeTypeStringExit = astVisitorExitKey | ||
let assignAstVisitorEnterKeyExit: `${keyof ASTVisitorEnter}:exit` = astNodeTypeExit | ||
assignAstVisitorEnterKeyExit = astNodeTypeStringExit | ||
assignAstVisitorEnterKeyExit = astVisitorExitKey | ||
let assignAstVisitorExitKey: keyof ASTVisitorExit = astNodeTypeExit | ||
assignAstVisitorExitKey = astNodeTypeStringExit | ||
assignAstVisitorExitKey = astVisitorEnterKeyExit | ||
} | ||
/* eslint-enable @typescript-eslint/no-unused-vars */ |
@@ -1,161 +0,273 @@ | ||
import antlr4 from 'antlr4' | ||
import { ParserRuleContext } from 'antlr4ts' | ||
import { AbstractParseTreeVisitor } from 'antlr4ts/tree/AbstractParseTreeVisitor' | ||
import { ParseTree } from 'antlr4ts/tree/ParseTree' | ||
import * as SP from './antlr/SolidityParser' | ||
import { SolidityVisitor } from './antlr/SolidityVisitor' | ||
import { ParseOptions } from './types' | ||
import * as ASTTypes from './ast-types' | ||
import { BaseASTNode } from './ast-types' | ||
import * as AST from './ast-types' | ||
type Ctx = any | ||
function toText(ctx: Ctx | null) { | ||
if (ctx !== null) { | ||
return ctx.getText() | ||
interface SourceLocation { | ||
start: { | ||
line: number | ||
column: number | ||
} | ||
return null | ||
end: { | ||
line: number | ||
column: number | ||
} | ||
} | ||
function mapCommasToNulls(children: Ctx[]) { | ||
if (children.length === 0) { | ||
return [] | ||
export class ASTBuilder | ||
extends AbstractParseTreeVisitor<AST.ASTNode> | ||
implements SolidityVisitor<AST.ASTNode | AST.ASTNode[]> { | ||
public result: AST.SourceUnit | null = null | ||
private _currentContract?: string | ||
constructor(public options: ParseOptions) { | ||
super() | ||
} | ||
const values = [] | ||
let comma = true | ||
defaultResult() { | ||
return ({ type: '' } as unknown) as AST.ASTNode | ||
} | ||
for (const el of children) { | ||
if (comma) { | ||
if (toText(el) === ',') { | ||
values.push(null) | ||
} else { | ||
values.push(el) | ||
comma = false | ||
} | ||
} else { | ||
if (toText(el) !== ',') { | ||
throw new Error('expected comma') | ||
} | ||
comma = true | ||
aggregateResult() { | ||
return ({ type: '' } as unknown) as AST.ASTNode | ||
} | ||
public visitSourceUnit(ctx: SP.SourceUnitContext): AST.SourceUnit { | ||
const children = ctx.children ?? [] | ||
const node: AST.SourceUnit = { | ||
type: 'SourceUnit', | ||
children: children.slice(0, -1).map((child) => this.visit(child)), | ||
} | ||
this.result = this._addMeta(node, ctx) | ||
return this.result | ||
} | ||
if (comma) { | ||
values.push(null) | ||
public visitContractPart(ctx: SP.ContractPartContext) { | ||
return this.visit(ctx.getChild(0)) | ||
} | ||
return values | ||
} | ||
public visitContractDefinition( | ||
ctx: SP.ContractDefinitionContext | ||
): AST.ContractDefinition { | ||
const name = this._toText(ctx.identifier()) | ||
const kind = this._toText(ctx.getChild(0)) | ||
function isBinOp(op: string): boolean { | ||
const binOps = [ | ||
'+', | ||
'-', | ||
'*', | ||
'/', | ||
'**', | ||
'%', | ||
'<<', | ||
'>>', | ||
'&&', | ||
'||', | ||
'&', | ||
'|', | ||
'^', | ||
'<', | ||
'>', | ||
'<=', | ||
'>=', | ||
'==', | ||
'!=', | ||
'=', | ||
'|=', | ||
'^=', | ||
'&=', | ||
'<<=', | ||
'>>=', | ||
'+=', | ||
'-=', | ||
'*=', | ||
'/=', | ||
'%=', | ||
] | ||
return binOps.includes(op) | ||
} | ||
this._currentContract = name | ||
const transformAST = { | ||
SourceUnit(ctx: Ctx): ASTTypes.SourceUnit { | ||
// last element is EOF terminal node | ||
return { | ||
type: 'SourceUnit', | ||
children: (this as any).visit(ctx.children.slice(0, -1)), | ||
const node: AST.ContractDefinition = { | ||
type: 'ContractDefinition', | ||
name, | ||
baseContracts: ctx | ||
.inheritanceSpecifier() | ||
.map((x) => this.visitInheritanceSpecifier(x)), | ||
subNodes: ctx.contractPart().map((x) => this.visit(x)), | ||
kind, | ||
} | ||
}, | ||
EnumDefinition(ctx: Ctx): ASTTypes.EnumDefinition { | ||
return { | ||
type: 'EnumDefinition', | ||
name: toText(ctx.identifier()), | ||
members: (this as any).visit(ctx.enumValue()), | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitStateVariableDeclaration( | ||
ctx: SP.StateVariableDeclarationContext | ||
) { | ||
const type = this.visitTypeName(ctx.typeName()) | ||
const iden = ctx.identifier() | ||
const name = this._toText(iden) | ||
let expression: AST.Expression | null = null | ||
const ctxExpression = ctx.expression() | ||
if (ctxExpression) { | ||
expression = this.visitExpression(ctxExpression) | ||
} | ||
}, | ||
EnumValue(ctx: Ctx) { | ||
return { | ||
name: toText(ctx.identifier()), | ||
let visibility: AST.VariableDeclaration['visibility'] = 'default' | ||
if (ctx.InternalKeyword().length > 0) { | ||
visibility = 'internal' | ||
} else if (ctx.PublicKeyword().length > 0) { | ||
visibility = 'public' | ||
} else if (ctx.PrivateKeyword().length > 0) { | ||
visibility = 'private' | ||
} | ||
}, | ||
UsingForDeclaration(ctx: Ctx) { | ||
let typeName = null | ||
if (toText(ctx.getChild(3)) !== '*') { | ||
typeName = (this as any).visit(ctx.getChild(3)) | ||
let isDeclaredConst = false | ||
if (ctx.ConstantKeyword().length > 0) { | ||
isDeclaredConst = true | ||
} | ||
return { | ||
typeName, | ||
libraryName: toText(ctx.identifier()), | ||
let override | ||
const overrideSpecifier = ctx.overrideSpecifier() | ||
if (overrideSpecifier.length === 0) { | ||
override = null | ||
} else { | ||
override = overrideSpecifier[0] | ||
.userDefinedTypeName() | ||
.map((x) => this.visitUserDefinedTypeName(x)) | ||
} | ||
}, | ||
PragmaDirective(ctx: Ctx) { | ||
// this converts something like >= 0.5.0 <0.7.0 | ||
// in >=0.5.0 <0.7.0 | ||
const value = ctx | ||
.pragmaValue() | ||
.children[0].children.map((x: any) => toText(x)) | ||
.join(' ') | ||
let isImmutable = false | ||
if (ctx.ImmutableKeyword().length > 0) { | ||
isImmutable = true | ||
} | ||
return { | ||
name: toText(ctx.pragmaName()), | ||
value, | ||
const decl: AST.StateVariableDeclarationVariable = { | ||
type: 'VariableDeclaration', | ||
typeName: type, | ||
name, | ||
expression, | ||
visibility, | ||
isStateVar: true, | ||
isDeclaredConst, | ||
isIndexed: false, | ||
isImmutable, | ||
override, | ||
storageLocation: null, | ||
} | ||
}, | ||
ContractDefinition(ctx: Ctx) { | ||
const name = toText(ctx.identifier()) | ||
const kind = toText(ctx.getChild(0)) | ||
const node: AST.StateVariableDeclaration = { | ||
type: 'StateVariableDeclaration', | ||
variables: [this._addMeta(decl, ctx)], | ||
initialValue: expression, | ||
} | ||
;(this as any)._currentContract = name | ||
return this._addMeta(node, ctx) | ||
} | ||
return { | ||
name, | ||
baseContracts: (this as any).visit(ctx.inheritanceSpecifier()), | ||
subNodes: (this as any).visit(ctx.contractPart()), | ||
kind, | ||
public visitVariableDeclaration( | ||
ctx: SP.VariableDeclarationContext | ||
): AST.VariableDeclaration { | ||
let storageLocation: string | null = null | ||
const ctxStorageLocation = ctx.storageLocation() | ||
if (ctxStorageLocation) { | ||
storageLocation = this._toText(ctxStorageLocation) | ||
} | ||
}, | ||
InheritanceSpecifier(ctx: Ctx) { | ||
const exprList = ctx.expressionList() | ||
const args = | ||
exprList != null ? (this as any).visit(exprList.expression()) : [] | ||
const node: AST.VariableDeclaration = { | ||
type: 'VariableDeclaration', | ||
typeName: this.visitTypeName(ctx.typeName()), | ||
name: this._toText(ctx.identifier()), | ||
storageLocation, | ||
isStateVar: false, | ||
isIndexed: false, | ||
expression: null, | ||
} | ||
return { | ||
baseName: (this as any).visit(ctx.userDefinedTypeName()), | ||
arguments: args, | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitVariableDeclarationStatement( | ||
ctx: SP.VariableDeclarationStatementContext | ||
): AST.VariableDeclarationStatement { | ||
let variables: Array<AST.BaseASTNode | null> = [] | ||
const ctxVariableDeclaration = ctx.variableDeclaration() | ||
const ctxIdentifierList = ctx.identifierList() | ||
const ctxVariableDeclarationList = ctx.variableDeclarationList() | ||
if (ctxVariableDeclaration !== undefined) { | ||
variables = [this.visitVariableDeclaration(ctxVariableDeclaration)] | ||
} else if (ctxIdentifierList !== undefined) { | ||
variables = this.buildIdentifierList(ctxIdentifierList) | ||
} else if (ctxVariableDeclarationList) { | ||
variables = this.buildVariableDeclarationList(ctxVariableDeclarationList) | ||
} | ||
}, | ||
ContractPart(ctx: Ctx) { | ||
return (this as any).visit(ctx.children[0]) | ||
}, | ||
let initialValue: AST.Expression | null = null | ||
const ctxExpression = ctx.expression() | ||
if (ctxExpression) { | ||
initialValue = this.visitExpression(ctxExpression) | ||
} | ||
FunctionDefinition(ctx: Ctx) { | ||
const node: AST.VariableDeclarationStatement = { | ||
type: 'VariableDeclarationStatement', | ||
variables, | ||
initialValue, | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitStatement(ctx: SP.StatementContext) { | ||
return this.visit(ctx.getChild(0)) as AST.Statement | ||
} | ||
public visitSimpleStatement(ctx: SP.SimpleStatementContext) { | ||
return this.visit(ctx.getChild(0)) as AST.SimpleStatement | ||
} | ||
public visitEventDefinition(ctx: SP.EventDefinitionContext) { | ||
const parameters = ctx | ||
.eventParameterList() | ||
.eventParameter() | ||
.map((paramCtx) => { | ||
const type = this.visitTypeName(paramCtx.typeName()) | ||
let name: string | null = null | ||
const paramCtxIdentifier = paramCtx.identifier() | ||
if (paramCtxIdentifier) { | ||
name = this._toText(paramCtxIdentifier) | ||
} | ||
const node: AST.VariableDeclaration = { | ||
type: 'VariableDeclaration', | ||
typeName: type, | ||
name, | ||
isStateVar: false, | ||
isIndexed: paramCtx.IndexedKeyword() !== undefined, | ||
storageLocation: null, | ||
expression: null, | ||
} | ||
return this._addMeta(node, paramCtx) | ||
}) | ||
const node: AST.EventDefinition = { | ||
type: 'EventDefinition', | ||
name: this._toText(ctx.identifier()), | ||
parameters, | ||
isAnonymous: ctx.AnonymousKeyword() !== undefined, | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitBlock(ctx: SP.BlockContext): AST.Block { | ||
const node: AST.Block = { | ||
type: 'Block', | ||
statements: ctx.statement().map((x) => this.visitStatement(x)), | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitParameter(ctx: SP.ParameterContext) { | ||
let storageLocation: string | null = null | ||
const ctxStorageLocation = ctx.storageLocation() | ||
if (ctxStorageLocation !== undefined) { | ||
storageLocation = this._toText(ctxStorageLocation) | ||
} | ||
let name: string | null = null | ||
const ctxIdentifier = ctx.identifier() | ||
if (ctxIdentifier !== undefined) { | ||
name = this._toText(ctxIdentifier) | ||
} | ||
const node: AST.VariableDeclaration = { | ||
type: 'VariableDeclaration', | ||
typeName: this.visitTypeName(ctx.typeName()), | ||
name, | ||
storageLocation, | ||
isStateVar: false, | ||
isIndexed: false, | ||
expression: null, | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitFunctionDefinition( | ||
ctx: SP.FunctionDefinitionContext | ||
): AST.FunctionDefinition { | ||
let isConstructor = false | ||
@@ -165,10 +277,11 @@ let isFallback = false | ||
let isVirtual = false | ||
let name = null | ||
let parameters = [] | ||
let returnParameters = null | ||
let visibility = 'default' | ||
let name: string | null = null | ||
let parameters: any = [] | ||
let returnParameters: AST.VariableDeclaration[] | null = null | ||
let visibility: AST.FunctionDefinition['visibility'] = 'default' | ||
let block = null | ||
if (ctx.block()) { | ||
block = (this as any).visit(ctx.block()) | ||
let block: AST.Block | null = null | ||
const ctxBlock = ctx.block() | ||
if (ctxBlock !== undefined) { | ||
block = this.visitBlock(ctxBlock) | ||
} | ||
@@ -179,25 +292,24 @@ | ||
.modifierInvocation() | ||
.map((mod: any) => (this as any).visit(mod)) | ||
.map((mod) => this.visitModifierInvocation(mod)) | ||
let stateMutability = null | ||
if (ctx.modifierList().stateMutability(0)) { | ||
stateMutability = toText(ctx.modifierList().stateMutability(0)) | ||
if (ctx.modifierList().stateMutability().length > 0) { | ||
stateMutability = this._stateMutabilityToText( | ||
ctx.modifierList().stateMutability(0) | ||
) | ||
} | ||
// see what type of function we're dealing with | ||
switch (toText(ctx.functionDescriptor().getChild(0))) { | ||
const ctxReturnParameters = ctx.returnParameters() | ||
switch (this._toText(ctx.functionDescriptor().getChild(0))) { | ||
case 'constructor': | ||
parameters = (this as any).visit(ctx.parameterList()) | ||
parameters = ctx | ||
.parameterList() | ||
.parameter() | ||
.map((x) => this.visit(x)) | ||
if ( | ||
ctx.returnParameters() && | ||
ctx.returnParameters().parameterList().parameter().length > 0 | ||
) { | ||
throw new Error('Constructors cannot have return parameters') | ||
} | ||
// error out on incorrect function visibility | ||
if (ctx.modifierList().InternalKeyword(0)) { | ||
if (ctx.modifierList().InternalKeyword().length > 0) { | ||
visibility = 'internal' | ||
} else if (ctx.modifierList().PublicKeyword(0)) { | ||
} else if (ctx.modifierList().PublicKeyword().length > 0) { | ||
visibility = 'public' | ||
@@ -211,71 +323,30 @@ } else { | ||
case 'fallback': | ||
if (ctx.parameterList().parameter().length > 0) { | ||
throw new Error('Fallback functions cannot have parameters') | ||
} | ||
if ( | ||
ctx.returnParameters() && | ||
ctx.returnParameters().parameterList().parameter().length > 0 | ||
) { | ||
throw new Error('Fallback functions cannot have return parameters') | ||
} | ||
// error out on incorrect function visibility | ||
if (!ctx.modifierList().ExternalKeyword(0)) { | ||
throw new Error('Fallback functions have to be declared "external"') | ||
} | ||
visibility = 'external' | ||
isFallback = true | ||
break | ||
case 'receive': | ||
if (ctx.parameterList().parameter().length > 0) { | ||
throw new Error('Receive Ether functions cannot have parameters') | ||
} | ||
if ( | ||
ctx.returnParameters() && | ||
ctx.returnParameters().parameterList().parameter().length > 0 | ||
) { | ||
throw new Error( | ||
'Receive Ether functions cannot have return parameters' | ||
) | ||
} | ||
// error out on incorrect function visibility | ||
if (!ctx.modifierList().ExternalKeyword(0)) { | ||
throw new Error( | ||
'Receive Ether functions have to be declared "external"' | ||
) | ||
} | ||
visibility = 'external' | ||
// error out on incorrect function payability | ||
if ( | ||
!ctx.modifierList().stateMutability(0) || | ||
!ctx.modifierList().stateMutability(0).PayableKeyword(0) | ||
) { | ||
throw new Error( | ||
'Receive Ether functions have to be declared "payable"' | ||
) | ||
} | ||
isReceiveEther = true | ||
break | ||
case 'function': | ||
name = ctx.functionDescriptor().identifier(0) | ||
? toText(ctx.functionDescriptor().identifier(0)) | ||
: '' | ||
case 'function': { | ||
const identifier = ctx.functionDescriptor().identifier() | ||
name = identifier !== undefined ? this._toText(identifier) : '' | ||
parameters = (this as any).visit(ctx.parameterList()) | ||
returnParameters = (this as any).visit(ctx.returnParameters()) | ||
parameters = ctx | ||
.parameterList() | ||
.parameter() | ||
.map((x) => this.visit(x)) | ||
returnParameters = | ||
ctxReturnParameters !== undefined | ||
? this.visitReturnParameters(ctxReturnParameters) | ||
: null | ||
// parse function visibility | ||
if (ctx.modifierList().ExternalKeyword(0)) { | ||
if (ctx.modifierList().ExternalKeyword().length > 0) { | ||
visibility = 'external' | ||
} else if (ctx.modifierList().InternalKeyword(0)) { | ||
} else if (ctx.modifierList().InternalKeyword().length > 0) { | ||
visibility = 'internal' | ||
} else if (ctx.modifierList().PublicKeyword(0)) { | ||
} else if (ctx.modifierList().PublicKeyword().length > 0) { | ||
visibility = 'public' | ||
} else if (ctx.modifierList().PrivateKeyword(0)) { | ||
} else if (ctx.modifierList().PrivateKeyword().length > 0) { | ||
visibility = 'private' | ||
@@ -285,12 +356,13 @@ } | ||
// check if function is virtual | ||
if (ctx.modifierList().VirtualKeyword(0)) { | ||
if (ctx.modifierList().VirtualKeyword().length > 0) { | ||
isVirtual = true | ||
} | ||
isConstructor = name === (this as any)._currentContract | ||
isConstructor = name === this._currentContract | ||
isFallback = name === '' | ||
break | ||
} | ||
} | ||
let override | ||
let override: AST.UserDefinedTypeName[] | null | ||
const overrideSpecifier = ctx.modifierList().overrideSpecifier() | ||
@@ -300,6 +372,9 @@ if (overrideSpecifier.length === 0) { | ||
} else { | ||
override = (this as any).visit(overrideSpecifier[0].userDefinedTypeName()) | ||
override = overrideSpecifier[0] | ||
.userDefinedTypeName() | ||
.map((x) => this.visitUserDefinedTypeName(x)) | ||
} | ||
return { | ||
const node: AST.FunctionDefinition = { | ||
type: 'FunctionDefinition', | ||
name, | ||
@@ -318,62 +393,223 @@ parameters, | ||
} | ||
}, | ||
ModifierInvocation(ctx: Ctx) { | ||
const exprList = ctx.expressionList() | ||
return this._addMeta(node, ctx) | ||
} | ||
let args | ||
if (exprList != null) { | ||
args = (this as any).visit(exprList.expression()) | ||
} else if (ctx.children.length > 1) { | ||
args = [] | ||
} else { | ||
args = null | ||
public visitEnumDefinition( | ||
ctx: SP.EnumDefinitionContext | ||
): AST.EnumDefinition { | ||
const node: AST.EnumDefinition = { | ||
type: 'EnumDefinition', | ||
name: this._toText(ctx.identifier()), | ||
members: ctx.enumValue().map((x) => this.visitEnumValue(x)), | ||
} | ||
return { | ||
name: toText(ctx.identifier()), | ||
arguments: args, | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitEnumValue(ctx: SP.EnumValueContext): AST.EnumValue { | ||
const node: AST.EnumValue = { | ||
type: 'EnumValue', | ||
name: this._toText(ctx.identifier()), | ||
} | ||
}, | ||
return this._addMeta(node, ctx) | ||
} | ||
TypeNameExpression(ctx: Ctx) { | ||
let typeName = ctx.elementaryTypeName() | ||
if (!typeName) { | ||
typeName = ctx.userDefinedTypeName() | ||
public visitElementaryTypeName( | ||
ctx: SP.ElementaryTypeNameContext | ||
): AST.ElementaryTypeName { | ||
const node: AST.ElementaryTypeName = { | ||
type: 'ElementaryTypeName', | ||
name: this._toText(ctx), | ||
stateMutability: null, | ||
} | ||
return { | ||
typeName: (this as any).visit(typeName), | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitIdentifier(ctx: SP.IdentifierContext): AST.Identifier { | ||
const node: AST.Identifier = { | ||
type: 'Identifier', | ||
name: this._toText(ctx), | ||
} | ||
}, | ||
return this._addMeta(node, ctx) | ||
} | ||
TypeName(ctx: Ctx) { | ||
if (ctx.children.length > 2) { | ||
public visitTypeName(ctx: SP.TypeNameContext): AST.TypeName { | ||
if (ctx.children !== undefined && ctx.children.length > 2) { | ||
let length = null | ||
if (ctx.children.length === 4) { | ||
length = (this as any).visit(ctx.getChild(2)) | ||
const expression = ctx.expression() | ||
if (expression === undefined) { | ||
throw new Error( | ||
'Assertion error: a typeName with 4 children should have an expression' | ||
) | ||
} | ||
length = this.visitExpression(expression) | ||
} | ||
return { | ||
const ctxTypeName = ctx.typeName() | ||
const node: AST.ArrayTypeName = { | ||
type: 'ArrayTypeName', | ||
baseTypeName: (this as any).visit(ctx.typeName()), | ||
baseTypeName: this.visitTypeName(ctxTypeName!), | ||
length, | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
if (ctx.children.length === 2) { | ||
return { | ||
if (ctx.children?.length === 2) { | ||
const node: AST.ElementaryTypeName = { | ||
type: 'ElementaryTypeName', | ||
name: toText(ctx.getChild(0)), | ||
stateMutability: toText(ctx.getChild(1)), | ||
name: this._toText(ctx.getChild(0)), | ||
stateMutability: this._toText(ctx.getChild(1)), | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
return (this as any).visit(ctx.getChild(0)) | ||
}, | ||
FunctionTypeName(ctx: Ctx) { | ||
if (ctx.elementaryTypeName() !== undefined) { | ||
return this.visitElementaryTypeName(ctx.elementaryTypeName()!) | ||
} | ||
if (ctx.userDefinedTypeName() !== undefined) { | ||
return this.visitUserDefinedTypeName(ctx.userDefinedTypeName()!) | ||
} | ||
if (ctx.mapping() !== undefined) { | ||
return this.visitMapping(ctx.mapping()!) | ||
} | ||
if (ctx.functionTypeName() !== undefined) { | ||
return this.visitFunctionTypeName(ctx.functionTypeName()!) | ||
} | ||
throw new Error('Assertion error: unhandled type name case') | ||
} | ||
public visitUserDefinedTypeName( | ||
ctx: SP.UserDefinedTypeNameContext | ||
): AST.UserDefinedTypeName { | ||
const node: AST.UserDefinedTypeName = { | ||
type: 'UserDefinedTypeName', | ||
namePath: this._toText(ctx), | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitUsingForDeclaration( | ||
ctx: SP.UsingForDeclarationContext | ||
): AST.UsingForDeclaration { | ||
let typeName = null | ||
const ctxTypeName = ctx.typeName() | ||
if (ctxTypeName !== undefined) { | ||
typeName = this.visitTypeName(ctxTypeName) | ||
} | ||
const node: AST.UsingForDeclaration = { | ||
type: 'UsingForDeclaration', | ||
typeName, | ||
libraryName: this._toText(ctx.identifier()), | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitPragmaDirective( | ||
ctx: SP.PragmaDirectiveContext | ||
): AST.PragmaDirective { | ||
// this converts something like >= 0.5.0 <0.7.0 | ||
// in >=0.5.0 <0.7.0 | ||
const versionContext = ctx.pragmaValue().version() | ||
let value = '' | ||
if (versionContext?.children !== undefined) { | ||
value = versionContext.children.map((x) => this._toText(x)).join(' ') | ||
} | ||
const node: AST.PragmaDirective = { | ||
type: 'PragmaDirective', | ||
name: this._toText(ctx.pragmaName()), | ||
value, | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitInheritanceSpecifier( | ||
ctx: SP.InheritanceSpecifierContext | ||
): AST.InheritanceSpecifier { | ||
const exprList = ctx.expressionList() | ||
const args = | ||
exprList !== undefined | ||
? exprList.expression().map((x) => this.visitExpression(x)) | ||
: [] | ||
const node: AST.InheritanceSpecifier = { | ||
type: 'InheritanceSpecifier', | ||
baseName: this.visitUserDefinedTypeName(ctx.userDefinedTypeName()), | ||
arguments: args, | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitModifierInvocation( | ||
ctx: SP.ModifierInvocationContext | ||
): AST.ModifierInvocation { | ||
const exprList = ctx.expressionList() | ||
let args | ||
if (exprList != null) { | ||
args = exprList.expression().map((x) => this.visit(x)) | ||
} else if (ctx.children !== undefined && ctx.children.length > 1) { | ||
args = [] | ||
} else { | ||
args = null | ||
} | ||
const node: AST.ModifierInvocation = { | ||
type: 'ModifierInvocation', | ||
name: this._toText(ctx.identifier()), | ||
arguments: args, | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitTypeNameExpression( | ||
ctx: SP.TypeNameExpressionContext | ||
): AST.TypeNameExpression { | ||
const ctxElementaryTypeName = ctx.elementaryTypeName() | ||
const ctxUserDefinedTypeName = ctx.userDefinedTypeName() | ||
let typeName | ||
if (ctxElementaryTypeName !== undefined) { | ||
typeName = this.visitElementaryTypeName(ctxElementaryTypeName) | ||
} else if (ctxUserDefinedTypeName !== undefined) { | ||
typeName = this.visitUserDefinedTypeName(ctxUserDefinedTypeName) | ||
} else { | ||
throw new Error( | ||
'Assertion error: either elementaryTypeName or userDefinedTypeName should be defined' | ||
) | ||
} | ||
const node: AST.TypeNameExpression = { | ||
type: 'TypeNameExpression', | ||
typeName, | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitFunctionTypeName( | ||
ctx: SP.FunctionTypeNameContext | ||
): AST.FunctionTypeName { | ||
const parameterTypes = ctx | ||
.functionTypeParameterList(0) | ||
.functionTypeParameter() | ||
.map((typeCtx: any) => (this as any).visit(typeCtx)) | ||
.map((typeCtx) => this.visitFunctionTypeParameter(typeCtx)) | ||
let returnTypes = [] | ||
let returnTypes: AST.VariableDeclaration[] = [] | ||
if (ctx.functionTypeParameterList(1)) { | ||
@@ -383,9 +619,9 @@ returnTypes = ctx | ||
.functionTypeParameter() | ||
.map((typeCtx: any) => (this as any).visit(typeCtx)) | ||
.map((typeCtx) => this.visitFunctionTypeParameter(typeCtx)) | ||
} | ||
let visibility = 'default' | ||
if (ctx.InternalKeyword(0)) { | ||
if (ctx.InternalKeyword().length > 0) { | ||
visibility = 'internal' | ||
} else if (ctx.ExternalKeyword(0)) { | ||
} else if (ctx.ExternalKeyword().length > 0) { | ||
visibility = 'external' | ||
@@ -395,7 +631,8 @@ } | ||
let stateMutability = null | ||
if (ctx.stateMutability(0)) { | ||
stateMutability = toText(ctx.stateMutability(0)) | ||
if (ctx.stateMutability().length > 0) { | ||
stateMutability = this._toText(ctx.stateMutability(0)) | ||
} | ||
return { | ||
const node: AST.FunctionTypeName = { | ||
type: 'FunctionTypeName', | ||
parameterTypes, | ||
@@ -406,130 +643,152 @@ returnTypes, | ||
} | ||
}, | ||
ReturnStatement(ctx: Ctx) { | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitFunctionTypeParameter( | ||
ctx: SP.FunctionTypeParameterContext | ||
): AST.VariableDeclaration { | ||
let storageLocation = null | ||
if (ctx.storageLocation()) { | ||
storageLocation = this._toText(ctx.storageLocation()!) | ||
} | ||
const node: AST.VariableDeclaration = { | ||
type: 'VariableDeclaration', | ||
typeName: this.visitTypeName(ctx.typeName()), | ||
name: null, | ||
storageLocation, | ||
isStateVar: false, | ||
isIndexed: false, | ||
expression: null, | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitThrowStatement( | ||
ctx: SP.ThrowStatementContext | ||
): AST.ThrowStatement { | ||
const node: AST.ThrowStatement = { | ||
type: 'ThrowStatement', | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitReturnStatement( | ||
ctx: SP.ReturnStatementContext | ||
): AST.ReturnStatement { | ||
let expression = null | ||
if (ctx.expression()) { | ||
expression = (this as any).visit(ctx.expression()) | ||
const ctxExpression = ctx.expression() | ||
if (ctxExpression) { | ||
expression = this.visitExpression(ctxExpression) | ||
} | ||
return { expression } | ||
}, | ||
const node: AST.ReturnStatement = { | ||
type: 'ReturnStatement', | ||
expression, | ||
} | ||
EmitStatement(ctx: Ctx) { | ||
return { | ||
eventCall: (this as any).visit(ctx.functionCall()), | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitEmitStatement(ctx: SP.EmitStatementContext): AST.EmitStatement { | ||
const node: AST.EmitStatement = { | ||
type: 'EmitStatement', | ||
eventCall: this.visitFunctionCall(ctx.functionCall()), | ||
} | ||
}, | ||
FunctionCall(ctx: Ctx) { | ||
let args = [] | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitFunctionCall(ctx: SP.FunctionCallContext): AST.FunctionCall { | ||
let args: AST.Expression[] = [] | ||
const names = [] | ||
const ctxArgs = ctx.functionCallArguments() | ||
if (ctxArgs.expressionList()) { | ||
args = ctxArgs | ||
.expressionList() | ||
const ctxArgsExpressionList = ctxArgs.expressionList() | ||
const ctxArgsNameValueList = ctxArgs.nameValueList() | ||
if (ctxArgsExpressionList) { | ||
args = ctxArgsExpressionList | ||
.expression() | ||
.map((exprCtx: any) => (this as any).visit(exprCtx)) | ||
} else if (ctxArgs.nameValueList()) { | ||
for (const nameValue of ctxArgs.nameValueList().nameValue()) { | ||
args.push((this as any).visit(nameValue.expression())) | ||
names.push(toText(nameValue.identifier())) | ||
.map((exprCtx) => this.visitExpression(exprCtx)) | ||
} else if (ctxArgsNameValueList) { | ||
for (const nameValue of ctxArgsNameValueList.nameValue()) { | ||
args.push(this.visitExpression(nameValue.expression())) | ||
names.push(this._toText(nameValue.identifier())) | ||
} | ||
} | ||
return { | ||
expression: (this as any).visit(ctx.expression()), | ||
const node: AST.FunctionCall = { | ||
type: 'FunctionCall', | ||
expression: this.visitExpression(ctx.expression()), | ||
arguments: args, | ||
names, | ||
} | ||
}, | ||
StructDefinition(ctx: Ctx) { | ||
return { | ||
name: toText(ctx.identifier()), | ||
members: (this as any).visit(ctx.variableDeclaration()), | ||
} | ||
}, | ||
return this._addMeta(node, ctx) | ||
} | ||
VariableDeclaration(ctx: Ctx) { | ||
let storageLocation = null | ||
if (ctx.storageLocation()) { | ||
storageLocation = toText(ctx.storageLocation()) | ||
public visitStructDefinition( | ||
ctx: SP.StructDefinitionContext | ||
): AST.StructDefinition { | ||
const node: AST.StructDefinition = { | ||
type: 'StructDefinition', | ||
name: this._toText(ctx.identifier()), | ||
members: ctx | ||
.variableDeclaration() | ||
.map((x) => this.visitVariableDeclaration(x)), | ||
} | ||
return { | ||
typeName: (this as any).visit(ctx.typeName()), | ||
name: toText(ctx.identifier()), | ||
storageLocation, | ||
isStateVar: false, | ||
isIndexed: false, | ||
} | ||
}, | ||
return this._addMeta(node, ctx) | ||
} | ||
EventParameter(ctx: Ctx) { | ||
let storageLocation = null | ||
if (ctx.storageLocation(0)) { | ||
storageLocation = toText(ctx.storageLocation(0)) | ||
public visitWhileStatement( | ||
ctx: SP.WhileStatementContext | ||
): AST.WhileStatement { | ||
const node: AST.WhileStatement = { | ||
type: 'WhileStatement', | ||
condition: this.visitExpression(ctx.expression()), | ||
body: this.visitStatement(ctx.statement()), | ||
} | ||
return { | ||
type: 'VariableDeclaration', | ||
typeName: (this as any).visit(ctx.typeName()), | ||
name: toText(ctx.identifier()), | ||
storageLocation, | ||
isStateVar: false, | ||
isIndexed: !!ctx.IndexedKeyword(0), | ||
} | ||
}, | ||
return this._addMeta(node, ctx) | ||
} | ||
FunctionTypeParameter(ctx: Ctx) { | ||
let storageLocation = null | ||
if (ctx.storageLocation()) { | ||
storageLocation = toText(ctx.storageLocation()) | ||
public visitDoWhileStatement( | ||
ctx: SP.DoWhileStatementContext | ||
): AST.DoWhileStatement { | ||
const node: AST.DoWhileStatement = { | ||
type: 'DoWhileStatement', | ||
condition: this.visitExpression(ctx.expression()), | ||
body: this.visitStatement(ctx.statement()), | ||
} | ||
return { | ||
type: 'VariableDeclaration', | ||
typeName: (this as any).visit(ctx.typeName()), | ||
name: null, | ||
storageLocation, | ||
isStateVar: false, | ||
isIndexed: false, | ||
} | ||
}, | ||
return this._addMeta(node, ctx) | ||
} | ||
WhileStatement(ctx: Ctx) { | ||
return { | ||
condition: (this as any).visit(ctx.expression()), | ||
body: (this as any).visit(ctx.statement()), | ||
} | ||
}, | ||
public visitIfStatement(ctx: SP.IfStatementContext): AST.IfStatement { | ||
const trueBody = this.visitStatement(ctx.statement(0)) | ||
DoWhileStatement(ctx: Ctx) { | ||
return { | ||
condition: (this as any).visit(ctx.expression()), | ||
body: (this as any).visit(ctx.statement()), | ||
} | ||
}, | ||
IfStatement(ctx: Ctx) { | ||
const trueBody = (this as any).visit(ctx.statement(0)) | ||
let falseBody = null | ||
if (ctx.statement().length > 1) { | ||
falseBody = (this as any).visit(ctx.statement(1)) | ||
falseBody = this.visitStatement(ctx.statement(1)) | ||
} | ||
return { | ||
condition: (this as any).visit(ctx.expression()), | ||
const node: AST.IfStatement = { | ||
type: 'IfStatement', | ||
condition: this.visitExpression(ctx.expression()), | ||
trueBody, | ||
falseBody, | ||
} | ||
}, | ||
TryStatement(ctx: Ctx) { | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitTryStatement(ctx: SP.TryStatementContext): AST.TryStatement { | ||
let returnParameters = null | ||
if (ctx.returnParameters()) { | ||
returnParameters = (this as any).visit(ctx.returnParameters()) | ||
const ctxReturnParameters = ctx.returnParameters() | ||
if (ctxReturnParameters !== undefined) { | ||
returnParameters = this.visitReturnParameters(ctxReturnParameters) | ||
} | ||
@@ -539,16 +798,19 @@ | ||
.catchClause() | ||
.map((exprCtx: any) => (this as any).visit(exprCtx)) | ||
.map((exprCtx) => this.visitCatchClause(exprCtx)) | ||
return { | ||
expression: (this as any).visit(ctx.expression()), | ||
const node: AST.TryStatement = { | ||
type: 'TryStatement', | ||
expression: this.visitExpression(ctx.expression()), | ||
returnParameters, | ||
body: (this as any).visit(ctx.block()), | ||
body: this.visitBlock(ctx.block()), | ||
catchClauses, | ||
} | ||
}, | ||
CatchClause(ctx: Ctx) { | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitCatchClause(ctx: SP.CatchClauseContext): AST.CatchClause { | ||
let parameters = null | ||
if (ctx.parameterList()) { | ||
parameters = (this as any).visit(ctx.parameterList()) | ||
parameters = this.visitParameterList(ctx.parameterList()!) | ||
} | ||
@@ -558,4 +820,4 @@ | ||
ctx.identifier() && | ||
toText(ctx.identifier()) !== 'Error' && | ||
toText(ctx.identifier()) !== 'Panic' | ||
this._toText(ctx.identifier()!) !== 'Error' && | ||
this._toText(ctx.identifier()!) !== 'Panic' | ||
) { | ||
@@ -566,59 +828,57 @@ throw new Error('Expected "Error" or "Panic" identifier in catch clause') | ||
let kind = null | ||
if (ctx.identifier()) { | ||
kind = toText(ctx.identifier()) | ||
const ctxIdentifier = ctx.identifier() | ||
if (ctxIdentifier !== undefined) { | ||
kind = this._toText(ctxIdentifier) | ||
} | ||
return { | ||
const node: AST.CatchClause = { | ||
type: 'CatchClause', | ||
// deprecated, use the `kind` property instead, | ||
isReasonStringType: | ||
!!ctx.identifier() && toText(ctx.identifier()) === 'Error', | ||
isReasonStringType: kind === 'Error', | ||
kind, | ||
parameters, | ||
body: (this as any).visit(ctx.block()), | ||
body: this.visitBlock(ctx.block()), | ||
} | ||
}, | ||
UserDefinedTypeName(ctx: Ctx) { | ||
return { | ||
namePath: toText(ctx), | ||
} | ||
}, | ||
return this._addMeta(node, ctx) | ||
} | ||
ElementaryTypeName(ctx: Ctx) { | ||
return { | ||
name: toText(ctx), | ||
public visitExpressionStatement( | ||
ctx: SP.ExpressionStatementContext | ||
): AST.ExpressionStatement { | ||
if (!ctx) { | ||
return null as any | ||
} | ||
}, | ||
Block(ctx: Ctx) { | ||
return { | ||
statements: (this as any).visit(ctx.statement()), | ||
const node: AST.ExpressionStatement = { | ||
type: 'ExpressionStatement', | ||
expression: this.visitExpression(ctx.expression()), | ||
} | ||
}, | ||
ExpressionStatement(ctx: Ctx) { | ||
return { | ||
expression: (this as any).visit(ctx.expression()), | ||
} | ||
}, | ||
return this._addMeta(node, ctx) | ||
} | ||
NumberLiteral(ctx: Ctx) { | ||
const number = toText(ctx.getChild(0)) | ||
public visitNumberLiteral(ctx: SP.NumberLiteralContext): AST.NumberLiteral { | ||
const number = this._toText(ctx.getChild(0)) | ||
let subdenomination = null | ||
if (ctx.children.length === 2) { | ||
subdenomination = toText(ctx.getChild(1)) | ||
if (ctx.children?.length === 2) { | ||
subdenomination = this._toText(ctx.getChild(1)) | ||
} | ||
return { | ||
const node: AST.NumberLiteral = { | ||
type: 'NumberLiteral', | ||
number, | ||
subdenomination, | ||
subdenomination: subdenomination as AST.NumberLiteral['subdenomination'], | ||
} | ||
}, | ||
MappingKey(ctx: Ctx) { | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitMappingKey( | ||
ctx: SP.MappingKeyContext | ||
): AST.ElementaryTypeName | AST.UserDefinedTypeName { | ||
if (ctx.elementaryTypeName()) { | ||
return (this as any).visit(ctx.elementaryTypeName()) | ||
return this.visitElementaryTypeName(ctx.elementaryTypeName()!) | ||
} else if (ctx.userDefinedTypeName()) { | ||
return (this as any).visit(ctx.userDefinedTypeName()) | ||
return this.visitUserDefinedTypeName(ctx.userDefinedTypeName()!) | ||
} else { | ||
@@ -630,19 +890,24 @@ throw new Error( | ||
} | ||
}, | ||
} | ||
Mapping(ctx: Ctx) { | ||
return { | ||
keyType: (this as any).visit(ctx.mappingKey()), | ||
valueType: (this as any).visit(ctx.typeName()), | ||
public visitMapping(ctx: SP.MappingContext): AST.Mapping { | ||
const node: AST.Mapping = { | ||
type: 'Mapping', | ||
keyType: this.visitMappingKey(ctx.mappingKey()), | ||
valueType: this.visitTypeName(ctx.typeName()), | ||
} | ||
}, | ||
ModifierDefinition(ctx: Ctx) { | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitModifierDefinition( | ||
ctx: SP.ModifierDefinitionContext | ||
): AST.ModifierDefinition { | ||
let parameters = null | ||
if (ctx.parameterList()) { | ||
parameters = (this as any).visit(ctx.parameterList()) | ||
parameters = this.visitParameterList(ctx.parameterList()!) | ||
} | ||
let isVirtual = false | ||
if (ctx.VirtualKeyword(0)) { | ||
if (ctx.VirtualKeyword().length > 0) { | ||
isVirtual = true | ||
@@ -656,67 +921,85 @@ } | ||
} else { | ||
override = (this as any).visit(overrideSpecifier[0].userDefinedTypeName()) | ||
override = overrideSpecifier[0] | ||
.userDefinedTypeName() | ||
.map((x) => this.visitUserDefinedTypeName(x)) | ||
} | ||
return { | ||
name: toText(ctx.identifier()), | ||
const node: AST.ModifierDefinition = { | ||
type: 'ModifierDefinition', | ||
name: this._toText(ctx.identifier()), | ||
parameters, | ||
body: (this as any).visit(ctx.block()), | ||
body: this.visitBlock(ctx.block()), | ||
isVirtual, | ||
override, | ||
} | ||
}, | ||
Statement(ctx: Ctx) { | ||
return (this as any).visit(ctx.getChild(0)) | ||
}, | ||
return this._addMeta(node, ctx) | ||
} | ||
SimpleStatement(ctx: Ctx) { | ||
return (this as any).visit(ctx.getChild(0)) | ||
}, | ||
UncheckedStatement(ctx: Ctx) { | ||
return { | ||
block: (this as any).visit(ctx.block()), | ||
public visitUncheckedStatement( | ||
ctx: SP.UncheckedStatementContext | ||
): AST.UncheckedStatement { | ||
const node: AST.UncheckedStatement = { | ||
type: 'UncheckedStatement', | ||
block: this.visitBlock(ctx.block()), | ||
} | ||
}, | ||
Expression(ctx: Ctx): ASTTypes.Expression { | ||
let op | ||
return this._addMeta(node, ctx) | ||
} | ||
switch (ctx.children.length) { | ||
case 1: | ||
public visitExpression(ctx: SP.ExpressionContext): AST.Expression { | ||
let op: string | ||
switch (ctx.children!.length) { | ||
case 1: { | ||
// primary expression | ||
return (this as any).visit(ctx.getChild(0)) | ||
const primaryExpressionCtx = ctx.tryGetRuleContext( | ||
0, | ||
SP.PrimaryExpressionContext | ||
) | ||
if (primaryExpressionCtx === undefined) { | ||
throw new Error( | ||
'Assertion error: primary expression should exist when children length is 1' | ||
) | ||
} | ||
return this.visitPrimaryExpression(primaryExpressionCtx) | ||
} | ||
case 2: | ||
op = toText(ctx.getChild(0)) | ||
op = this._toText(ctx.getChild(0)) | ||
// new expression | ||
if (op === 'new') { | ||
return { | ||
const node: AST.NewExpression = { | ||
type: 'NewExpression', | ||
typeName: (this as any).visit(ctx.typeName()), | ||
typeName: this.visitTypeName(ctx.typeName()!), | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
// prefix operators | ||
if (['+', '-', '++', '--', '!', '~', 'after', 'delete'].includes(op)) { | ||
return { | ||
if (AST.unaryOpValues.includes(op as AST.UnaryOp)) { | ||
const node: AST.UnaryOperation = { | ||
type: 'UnaryOperation', | ||
operator: op, | ||
subExpression: (this as any).visit(ctx.getChild(1)), | ||
operator: op as AST.UnaryOp, | ||
subExpression: this.visitExpression( | ||
ctx.getRuleContext(0, SP.ExpressionContext) | ||
), | ||
isPrefix: true, | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
op = toText(ctx.getChild(1)) | ||
op = this._toText(ctx.getChild(1))! | ||
// postfix operators | ||
if (['++', '--'].includes(op)) { | ||
return { | ||
const node: AST.UnaryOperation = { | ||
type: 'UnaryOperation', | ||
operator: op, | ||
subExpression: (this as any).visit(ctx.getChild(0)), | ||
operator: op as AST.UnaryOp, | ||
subExpression: this.visitExpression( | ||
ctx.getRuleContext(0, SP.ExpressionContext) | ||
), | ||
isPrefix: false, | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
@@ -728,58 +1011,35 @@ break | ||
if ( | ||
toText(ctx.getChild(0)) === '(' && | ||
toText(ctx.getChild(2)) === ')' | ||
this._toText(ctx.getChild(0)) === '(' && | ||
this._toText(ctx.getChild(2)) === ')' | ||
) { | ||
return { | ||
const node: AST.TupleExpression = { | ||
type: 'TupleExpression', | ||
components: [(this as any).visit(ctx.getChild(1))], | ||
isArray: false, | ||
} | ||
} | ||
// if square parenthesis are present it can only be | ||
// a typename expression | ||
if ( | ||
toText(ctx.getChild(1)) === '[' && | ||
toText(ctx.getChild(2)) === ']' | ||
) { | ||
return { | ||
type: 'TypeNameExpression', | ||
typeName: { | ||
type: 'ArrayTypeName', | ||
baseTypeName: (this as any).visit(ctx.getChild(0)), | ||
length: null, | ||
}, | ||
} | ||
} | ||
op = toText(ctx.getChild(1)) | ||
// tuple separator | ||
if (op === ',') { | ||
return { | ||
type: 'TupleExpression', | ||
components: [ | ||
(this as any).visit(ctx.getChild(0)), | ||
(this as any).visit(ctx.getChild(2)), | ||
this.visitExpression(ctx.getRuleContext(0, SP.ExpressionContext)), | ||
], | ||
isArray: false, | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
op = this._toText(ctx.getChild(1))! | ||
// member access | ||
if (op === '.') { | ||
return { | ||
const node: AST.MemberAccess = { | ||
type: 'MemberAccess', | ||
expression: (this as any).visit(ctx.getChild(0)), | ||
memberName: toText(ctx.getChild(2)), | ||
expression: this.visitExpression(ctx.expression(0)), | ||
memberName: this._toText(ctx.identifier()!), | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
if (isBinOp(op)) { | ||
return { | ||
const node: AST.BinaryOperation = { | ||
type: 'BinaryOperation', | ||
operator: op, | ||
left: (this as any).visit(ctx.getChild(0)), | ||
right: (this as any).visit(ctx.getChild(2)), | ||
left: this.visitExpression(ctx.expression(0)), | ||
right: this.visitExpression(ctx.expression(1)), | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
@@ -791,27 +1051,29 @@ break | ||
if ( | ||
toText(ctx.getChild(1)) === '(' && | ||
toText(ctx.getChild(3)) === ')' | ||
this._toText(ctx.getChild(1)) === '(' && | ||
this._toText(ctx.getChild(3)) === ')' | ||
) { | ||
let args = [] | ||
let args: AST.Expression[] = [] | ||
const names = [] | ||
const ctxArgs = ctx.functionCallArguments() | ||
const ctxArgs = ctx.functionCallArguments()! | ||
if (ctxArgs.expressionList()) { | ||
args = ctxArgs | ||
.expressionList() | ||
.expressionList()! | ||
.expression() | ||
.map((exprCtx: any) => (this as any).visit(exprCtx)) | ||
.map((exprCtx) => this.visitExpression(exprCtx)) | ||
} else if (ctxArgs.nameValueList()) { | ||
for (const nameValue of ctxArgs.nameValueList().nameValue()) { | ||
args.push((this as any).visit(nameValue.expression())) | ||
names.push(toText(nameValue.identifier())) | ||
for (const nameValue of ctxArgs.nameValueList()!.nameValue()) { | ||
args.push(this.visitExpression(nameValue.expression())) | ||
names.push(this._toText(nameValue.identifier())) | ||
} | ||
} | ||
return { | ||
const node: AST.FunctionCall = { | ||
type: 'FunctionCall', | ||
expression: (this as any).visit(ctx.getChild(0)), | ||
expression: this.visitExpression(ctx.expression(0)), | ||
arguments: args, | ||
names, | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
@@ -821,17 +1083,21 @@ | ||
if ( | ||
toText(ctx.getChild(1)) === '[' && | ||
toText(ctx.getChild(3)) === ']' | ||
this._toText(ctx.getChild(1)) === '[' && | ||
this._toText(ctx.getChild(3)) === ']' | ||
) { | ||
if (ctx.getChild(2).getText() === ':') { | ||
return { | ||
if (ctx.getChild(2).text === ':') { | ||
const node: AST.IndexRangeAccess = { | ||
type: 'IndexRangeAccess', | ||
base: (this as any).visit(ctx.getChild(0)), | ||
base: this.visitExpression(ctx.expression(0)), | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
return { | ||
const node: AST.IndexAccess = { | ||
type: 'IndexAccess', | ||
base: (this as any).visit(ctx.getChild(0)), | ||
index: (this as any).visit(ctx.getChild(2)), | ||
base: this.visitExpression(ctx.expression(0)), | ||
index: this.visitExpression(ctx.expression(1)), | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
@@ -841,10 +1107,12 @@ | ||
if ( | ||
toText(ctx.getChild(1)) === '{' && | ||
toText(ctx.getChild(3)) === '}' | ||
this._toText(ctx.getChild(1)) === '{' && | ||
this._toText(ctx.getChild(3)) === '}' | ||
) { | ||
return { | ||
const node: AST.NameValueExpression = { | ||
type: 'NameValueExpression', | ||
expression: (this as any).visit(ctx.getChild(0)), | ||
arguments: (this as any).visit(ctx.getChild(2)), | ||
expression: this.visitExpression(ctx.expression(0)), | ||
arguments: this.visitNameValueList(ctx.nameValueList()!), | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
@@ -857,11 +1125,13 @@ | ||
if ( | ||
toText(ctx.getChild(1)) === '?' && | ||
toText(ctx.getChild(3)) === ':' | ||
this._toText(ctx.getChild(1)) === '?' && | ||
this._toText(ctx.getChild(3)) === ':' | ||
) { | ||
return { | ||
const node: AST.Conditional = { | ||
type: 'Conditional', | ||
condition: (this as any).visit(ctx.getChild(0)), | ||
trueExpression: (this as any).visit(ctx.getChild(2)), | ||
falseExpression: (this as any).visit(ctx.getChild(4)), | ||
condition: this.visitExpression(ctx.expression(0)), | ||
trueExpression: this.visitExpression(ctx.expression(1)), | ||
falseExpression: this.visitExpression(ctx.expression(2)), | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
@@ -871,21 +1141,25 @@ | ||
if ( | ||
toText(ctx.getChild(1)) === '[' && | ||
toText(ctx.getChild(2)) === ':' && | ||
toText(ctx.getChild(4)) === ']' | ||
this._toText(ctx.getChild(1)) === '[' && | ||
this._toText(ctx.getChild(2)) === ':' && | ||
this._toText(ctx.getChild(4)) === ']' | ||
) { | ||
return { | ||
const node: AST.IndexRangeAccess = { | ||
type: 'IndexRangeAccess', | ||
base: (this as any).visit(ctx.getChild(0)), | ||
indexEnd: (this as any).visit(ctx.getChild(3)), | ||
base: this.visitExpression(ctx.expression(0)), | ||
indexEnd: this.visitExpression(ctx.expression(1)), | ||
} | ||
return this._addMeta(node, ctx) | ||
} else if ( | ||
toText(ctx.getChild(1)) === '[' && | ||
toText(ctx.getChild(3)) === ':' && | ||
toText(ctx.getChild(4)) === ']' | ||
this._toText(ctx.getChild(1)) === '[' && | ||
this._toText(ctx.getChild(3)) === ':' && | ||
this._toText(ctx.getChild(4)) === ']' | ||
) { | ||
return { | ||
const node: AST.IndexRangeAccess = { | ||
type: 'IndexRangeAccess', | ||
base: (this as any).visit(ctx.getChild(0)), | ||
indexStart: (this as any).visit(ctx.getChild(2)), | ||
base: this.visitExpression(ctx.expression(0)), | ||
indexStart: this.visitExpression(ctx.expression(1)), | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
@@ -897,12 +1171,14 @@ break | ||
if ( | ||
toText(ctx.getChild(1)) === '[' && | ||
toText(ctx.getChild(3)) === ':' && | ||
toText(ctx.getChild(5)) === ']' | ||
this._toText(ctx.getChild(1)) === '[' && | ||
this._toText(ctx.getChild(3)) === ':' && | ||
this._toText(ctx.getChild(5)) === ']' | ||
) { | ||
return { | ||
const node: AST.IndexRangeAccess = { | ||
type: 'IndexRangeAccess', | ||
base: (this as any).visit(ctx.getChild(0)), | ||
indexStart: (this as any).visit(ctx.getChild(2)), | ||
indexEnd: (this as any).visit(ctx.getChild(4)), | ||
base: this.visitExpression(ctx.expression(0)), | ||
indexStart: this.visitExpression(ctx.expression(1)), | ||
indexEnd: this.visitExpression(ctx.expression(2)), | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
@@ -913,14 +1189,14 @@ break | ||
throw new Error('Unrecognized expression') | ||
}, | ||
} | ||
NameValueList(ctx: Ctx) { | ||
const names = [] | ||
const args = [] | ||
public visitNameValueList(ctx: SP.NameValueListContext): AST.NameValueList { | ||
const names: string[] = [] | ||
const args: AST.Expression[] = [] | ||
for (const nameValue of ctx.nameValue()) { | ||
names.push(toText(nameValue.identifier())) | ||
args.push((this as any).visit(nameValue.expression())) | ||
names.push(this._toText(nameValue.identifier())) | ||
args.push(this.visitExpression(nameValue.expression())) | ||
} | ||
return { | ||
const node: AST.NameValueList = { | ||
type: 'NameValueList', | ||
@@ -930,74 +1206,15 @@ names, | ||
} | ||
}, | ||
StateVariableDeclaration(ctx: Ctx) { | ||
const type = (this as any).visit(ctx.typeName()) | ||
const iden = ctx.identifier() | ||
const name = toText(iden) | ||
return this._addMeta(node, ctx) | ||
} | ||
let expression = null | ||
if (ctx.expression()) { | ||
expression = (this as any).visit(ctx.expression()) | ||
} | ||
let visibility = 'default' | ||
if (ctx.InternalKeyword(0)) { | ||
visibility = 'internal' | ||
} else if (ctx.PublicKeyword(0)) { | ||
visibility = 'public' | ||
} else if (ctx.PrivateKeyword(0)) { | ||
visibility = 'private' | ||
} | ||
let isDeclaredConst = false | ||
if (ctx.ConstantKeyword(0)) { | ||
isDeclaredConst = true | ||
} | ||
let override | ||
const overrideSpecifier = ctx.overrideSpecifier() | ||
if (overrideSpecifier.length === 0) { | ||
override = null | ||
} else { | ||
override = (this as any).visit(overrideSpecifier[0].userDefinedTypeName()) | ||
} | ||
let isImmutable = false | ||
if (ctx.ImmutableKeyword(0)) { | ||
isImmutable = true | ||
} | ||
const decl = (this as any).createNode( | ||
{ | ||
type: 'VariableDeclaration', | ||
typeName: type, | ||
name, | ||
expression, | ||
visibility, | ||
isStateVar: true, | ||
isDeclaredConst, | ||
isIndexed: false, | ||
isImmutable, | ||
override, | ||
}, | ||
iden | ||
) | ||
return { | ||
variables: [decl], | ||
initialValue: expression, | ||
} | ||
}, | ||
FileLevelConstant(ctx: Ctx) { | ||
const type = (this as any).visit(ctx.typeName()) | ||
public visitFileLevelConstant(ctx: SP.FileLevelConstantContext) { | ||
const type = this.visitTypeName(ctx.typeName()) | ||
const iden = ctx.identifier() | ||
const name = toText(iden) | ||
const name = this._toText(iden) | ||
let expression = null | ||
if (ctx.expression()) { | ||
expression = (this as any).visit(ctx.expression()) | ||
} | ||
const expression = this.visitExpression(ctx.expression()) | ||
return { | ||
const node: AST.FileLevelConstant = { | ||
type: 'FileLevelConstant', | ||
typeName: type, | ||
@@ -1007,27 +1224,37 @@ name, | ||
} | ||
}, | ||
ForStatement(ctx: Ctx) { | ||
let conditionExpression = (this as any).visit(ctx.expressionStatement()) | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitForStatement(ctx: SP.ForStatementContext) { | ||
let conditionExpression: any = this.visitExpressionStatement( | ||
ctx.expressionStatement()! | ||
) | ||
if (conditionExpression) { | ||
conditionExpression = conditionExpression.expression | ||
} | ||
return { | ||
initExpression: (this as any).visit(ctx.simpleStatement()), | ||
const node: AST.ForStatement = { | ||
type: 'ForStatement', | ||
initExpression: ctx.simpleStatement() | ||
? this.visitSimpleStatement(ctx.simpleStatement()!) | ||
: null, | ||
conditionExpression, | ||
loopExpression: { | ||
type: 'ExpressionStatement', | ||
expression: (this as any).visit(ctx.expression()), | ||
expression: | ||
ctx.expression() !== undefined ? this.visitExpression(ctx.expression()!) : null, | ||
}, | ||
body: (this as any).visit(ctx.statement()), | ||
body: this.visitStatement(ctx.statement()), | ||
} | ||
}, | ||
HexLiteral(ctx: Ctx) { | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitHexLiteral(ctx: SP.HexLiteralContext) { | ||
const parts = ctx | ||
.HexLiteralFragment() | ||
.map(toText) | ||
.map((x: any) => x.substring(4, x.length - 1)) | ||
.map((x) => this._toText(x)) | ||
.map((x) => x.substring(4, x.length - 1)) | ||
return { | ||
const node: AST.HexLiteral = { | ||
type: 'HexLiteral', | ||
@@ -1037,14 +1264,20 @@ value: parts.join(''), | ||
} | ||
}, | ||
PrimaryExpression(ctx: Ctx) { | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitPrimaryExpression( | ||
ctx: SP.PrimaryExpressionContext | ||
): AST.PrimaryExpression { | ||
if (ctx.BooleanLiteral()) { | ||
return { | ||
const node: AST.BooleanLiteral = { | ||
type: 'BooleanLiteral', | ||
value: toText(ctx.BooleanLiteral()) === 'true', | ||
value: this._toText(ctx.BooleanLiteral()!) === 'true', | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
if (ctx.hexLiteral()) { | ||
return (this as any).visit(ctx.hexLiteral()) | ||
return this.visitHexLiteral(ctx.hexLiteral()!) | ||
} | ||
@@ -1054,6 +1287,6 @@ | ||
const fragments = ctx | ||
.stringLiteral() | ||
.stringLiteral()! | ||
.StringLiteralFragment() | ||
.map((stringLiteralFragmentCtx: any) => { | ||
let text = toText(stringLiteralFragmentCtx) | ||
let text = this._toText(stringLiteralFragmentCtx)! | ||
@@ -1075,3 +1308,3 @@ const isUnicode = text.slice(0, 7) === 'unicode' | ||
return { | ||
const node: AST.StringLiteral = { | ||
type: 'StringLiteral', | ||
@@ -1082,17 +1315,25 @@ value: parts.join(''), | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
if (ctx.numberLiteral()) { | ||
return this.visitNumberLiteral(ctx.numberLiteral()!) | ||
} | ||
if (ctx.TypeKeyword()) { | ||
return { | ||
const node: AST.Identifier = { | ||
type: 'Identifier', | ||
name: 'type', | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
if ( | ||
ctx.children.length == 3 && | ||
toText(ctx.getChild(1)) === '[' && | ||
toText(ctx.getChild(2)) === ']' | ||
ctx.children!.length == 3 && | ||
this._toText(ctx.getChild(1)) === '[' && | ||
this._toText(ctx.getChild(2)) === ']' | ||
) { | ||
let node = (this as any).visit(ctx.getChild(0)) | ||
let node: any = this.visit(ctx.getChild(0)) | ||
if (node.type === 'Identifier') { | ||
@@ -1108,48 +1349,49 @@ node = { | ||
type: 'ElementaryTypeName', | ||
name: toText(ctx.getChild(0)), | ||
name: this._toText(ctx.getChild(0)), | ||
} | ||
} | ||
const typeName = { | ||
const typeName: AST.ArrayTypeName = { | ||
type: 'ArrayTypeName', | ||
baseTypeName: node, | ||
baseTypeName: this._addMeta(node, ctx), | ||
length: null, | ||
} | ||
return { | ||
const result: AST.TypeNameExpression = { | ||
type: 'TypeNameExpression', | ||
typeName, | ||
typeName: this._addMeta(typeName, ctx), | ||
} | ||
return this._addMeta(result, ctx) | ||
} | ||
return (this as any).visit(ctx.getChild(0)) | ||
}, | ||
return this.visitTupleExpression(ctx.tupleExpression()!) | ||
Identifier(ctx: Ctx) { | ||
return { | ||
name: toText(ctx), | ||
} | ||
}, | ||
throw new Error("Assertion error: unhandled primary expression") | ||
} | ||
TupleExpression(ctx: Ctx) { | ||
public visitTupleExpression(ctx: SP.TupleExpressionContext): AST.TupleExpression { | ||
// remove parentheses | ||
const children = ctx.children.slice(1, -1) | ||
const components = mapCommasToNulls(children).map((expr) => { | ||
const children = ctx.children!.slice(1, -1) | ||
const components = this._mapCommasToNulls(children).map((expr) => { | ||
// add a null for each empty value | ||
if (!expr) { | ||
if (expr === null) { | ||
return null | ||
} | ||
return (this as any).visit(expr) | ||
return this.visit(expr) | ||
}) | ||
return { | ||
const node: AST.TupleExpression = { | ||
type: 'TupleExpression', | ||
components, | ||
isArray: toText(ctx.getChild(0)) === '[', | ||
isArray: this._toText(ctx.getChild(0)) === '[', | ||
} | ||
}, | ||
IdentifierList(ctx: Ctx) { | ||
return this._addMeta(node, ctx) | ||
} | ||
public buildIdentifierList(ctx: SP.IdentifierListContext) { | ||
// remove parentheses | ||
const children = ctx.children.slice(1, -1) | ||
return mapCommasToNulls(children).map((iden) => { | ||
const children = ctx.children!.slice(1, -1) | ||
return this._mapCommasToNulls(children).map((iden) => { | ||
// add a null for each empty value | ||
@@ -1160,19 +1402,21 @@ if (!iden) { | ||
return (this as any).createNode( | ||
{ | ||
type: 'VariableDeclaration', | ||
name: toText(iden), | ||
storageLocation: null, | ||
typeName: null, | ||
isStateVar: false, | ||
isIndexed: false, | ||
}, | ||
iden | ||
) | ||
const node: AST.VariableDeclaration = { | ||
type: 'VariableDeclaration', | ||
name: this._toText(iden), | ||
isStateVar: false, | ||
isIndexed: false, | ||
typeName: null, | ||
storageLocation: null, | ||
expression: null, | ||
} | ||
return node | ||
}) | ||
}, | ||
} | ||
VariableDeclarationList(ctx: Ctx) { | ||
public buildVariableDeclarationList( | ||
ctx: SP.VariableDeclarationListContext | ||
): Array<AST.VariableDeclaration | null> { | ||
// remove parentheses | ||
return mapCommasToNulls(ctx.children).map((decl) => { | ||
return this._mapCommasToNulls(ctx.children!).map((decl: any) => { | ||
// add a null for each empty value | ||
@@ -1183,44 +1427,23 @@ if (!decl) { | ||
let storageLocation = null | ||
let storageLocation: string | null = null | ||
if (decl.storageLocation()) { | ||
storageLocation = toText(decl.storageLocation()) | ||
storageLocation = this._toText(decl.storageLocation()!) | ||
} | ||
return (this as any).createNode( | ||
{ | ||
type: 'VariableDeclaration', | ||
name: toText(decl.identifier()), | ||
typeName: (this as any).visit(decl.typeName()), | ||
storageLocation, | ||
isStateVar: false, | ||
isIndexed: false, | ||
}, | ||
decl | ||
) | ||
const result: AST.VariableDeclaration = { | ||
type: 'VariableDeclaration', | ||
name: this._toText(decl.identifier()), | ||
typeName: this.visitTypeName(decl.typeName()), | ||
storageLocation, | ||
isStateVar: false, | ||
isIndexed: false, | ||
expression: null, | ||
} | ||
return result | ||
}) | ||
}, | ||
} | ||
VariableDeclarationStatement(ctx: Ctx) { | ||
let variables | ||
if (ctx.variableDeclaration()) { | ||
variables = [(this as any).visit(ctx.variableDeclaration())] | ||
} else if (ctx.identifierList()) { | ||
variables = (this as any).visit(ctx.identifierList()) | ||
} else if (ctx.variableDeclarationList()) { | ||
variables = (this as any).visit(ctx.variableDeclarationList()) | ||
} | ||
let initialValue = null | ||
if (ctx.expression()) { | ||
initialValue = (this as any).visit(ctx.expression()) | ||
} | ||
return { | ||
variables, | ||
initialValue, | ||
} | ||
}, | ||
ImportDirective(ctx: Ctx) { | ||
const pathString = toText(ctx.StringLiteralFragment()) | ||
public visitImportDirective(ctx: SP.ImportDirectiveContext) { | ||
const pathString = this._toText(ctx.StringLiteralFragment())! | ||
let unitAlias = null | ||
@@ -1230,17 +1453,18 @@ let symbolAliases = null | ||
if (ctx.importDeclaration().length > 0) { | ||
symbolAliases = ctx.importDeclaration().map((decl: any) => { | ||
const symbol = toText(decl.identifier(0)) | ||
symbolAliases = ctx.importDeclaration().map((decl) => { | ||
const symbol = this._toText(decl.identifier(0)) | ||
let alias = null | ||
if (decl.identifier(1)) { | ||
alias = toText(decl.identifier(1)) | ||
if (decl.identifier().length > 1) { | ||
alias = this._toText(decl.identifier(1)) | ||
} | ||
return [symbol, alias] | ||
return [symbol, alias] as [string, string | null] | ||
}) | ||
} else if (ctx.children.length === 7) { | ||
unitAlias = toText(ctx.getChild(3)) | ||
} else if (ctx.children.length === 5) { | ||
unitAlias = toText(ctx.getChild(3)) | ||
} else if (ctx.children!.length === 7) { | ||
unitAlias = this._toText(ctx.getChild(3)) | ||
} else if (ctx.children!.length === 5) { | ||
unitAlias = this._toText(ctx.getChild(3)) | ||
} | ||
return { | ||
const node: AST.ImportDirective = { | ||
type: 'ImportDirective', | ||
path: pathString.substring(1, pathString.length - 1), | ||
@@ -1250,94 +1474,74 @@ unitAlias, | ||
} | ||
}, | ||
EventDefinition(ctx: Ctx) { | ||
return { | ||
name: toText(ctx.identifier()), | ||
parameters: (this as any).visit(ctx.eventParameterList()), | ||
isAnonymous: !!ctx.AnonymousKeyword(), | ||
} | ||
}, | ||
return this._addMeta(node, ctx) | ||
} | ||
EventParameterList(ctx: Ctx) { | ||
public buildEventParameterList(ctx: SP.EventParameterListContext) { | ||
return ctx.eventParameter().map((paramCtx: any) => { | ||
const type = (this as any).visit(paramCtx.typeName()) | ||
const type = this.visit(paramCtx.typeName()) | ||
let name = null | ||
if (paramCtx.identifier()) { | ||
name = toText(paramCtx.identifier()) | ||
name = this._toText(paramCtx.identifier()) | ||
} | ||
return (this as any).createNode( | ||
{ | ||
type: 'VariableDeclaration', | ||
typeName: type, | ||
name, | ||
isStateVar: false, | ||
isIndexed: !!paramCtx.IndexedKeyword(0), | ||
}, | ||
paramCtx | ||
) | ||
}, this) | ||
}, | ||
return { | ||
type: 'VariableDeclaration', | ||
typeName: type, | ||
name, | ||
isStateVar: false, | ||
isIndexed: !!paramCtx.IndexedKeyword(0), | ||
} | ||
}) | ||
} | ||
ReturnParameters(ctx: Ctx) { | ||
return (this as any).visit(ctx.parameterList()) | ||
}, | ||
public visitReturnParameters( | ||
ctx: SP.ReturnParametersContext | ||
): AST.VariableDeclaration[] { | ||
return this.visitParameterList(ctx.parameterList()) | ||
} | ||
ParameterList(ctx: Ctx) { | ||
return ctx.parameter().map((paramCtx: any) => (this as any).visit(paramCtx)) | ||
}, | ||
public visitParameterList( | ||
ctx: SP.ParameterListContext | ||
): AST.VariableDeclaration[] { | ||
return ctx.parameter().map((paramCtx: any) => this.visitParameter(paramCtx)) | ||
} | ||
Parameter(ctx: Ctx) { | ||
let storageLocation = null | ||
if (ctx.storageLocation()) { | ||
storageLocation = toText(ctx.storageLocation()) | ||
public visitInlineAssemblyStatement(ctx: SP.InlineAssemblyStatementContext) { | ||
let language: string | null = null | ||
if (ctx.StringLiteralFragment()) { | ||
language = this._toText(ctx.StringLiteralFragment()!)! | ||
language = language.substring(1, language.length - 1) | ||
} | ||
let name = null | ||
if (ctx.identifier()) { | ||
name = toText(ctx.identifier()) | ||
const node: AST.InlineAssemblyStatement = { | ||
type: 'InlineAssemblyStatement', | ||
language, | ||
body: this.visitAssemblyBlock(ctx.assemblyBlock()), | ||
} | ||
return { | ||
type: 'VariableDeclaration', | ||
typeName: (this as any).visit(ctx.typeName()), | ||
name, | ||
storageLocation, | ||
isStateVar: false, | ||
isIndexed: false, | ||
} | ||
}, | ||
return this._addMeta(node, ctx) | ||
} | ||
InlineAssemblyStatement(ctx: Ctx) { | ||
let language = null | ||
if (ctx.StringLiteralFragment()) { | ||
language = toText(ctx.StringLiteralFragment()) | ||
language = language.substring(1, language.length - 1) | ||
} | ||
public visitAssemblyBlock(ctx: SP.AssemblyBlockContext): AST.AssemblyBlock { | ||
const operations = ctx.assemblyItem().map((item) => this.visitAssemblyItem(item)) | ||
return { | ||
language, | ||
body: (this as any).visit(ctx.assemblyBlock()), | ||
const node: AST.AssemblyBlock = { | ||
type: 'AssemblyBlock', | ||
operations, | ||
} | ||
}, | ||
AssemblyBlock(ctx: Ctx) { | ||
const operations = ctx | ||
.assemblyItem() | ||
.map((it: any) => (this as any).visit(it)) | ||
return this._addMeta(node, ctx) | ||
} | ||
return { operations } | ||
}, | ||
AssemblyItem(ctx: Ctx) { | ||
public visitAssemblyItem(ctx: SP.AssemblyItemContext): AST.AssemblyItem { | ||
let text | ||
if (ctx.hexLiteral()) { | ||
return (this as any).visit(ctx.hexLiteral()) | ||
return this.visitHexLiteral(ctx.hexLiteral()!) | ||
} | ||
if (ctx.stringLiteral()) { | ||
text = toText(ctx.stringLiteral()) | ||
text = this._toText(ctx.stringLiteral()!)! | ||
const value = text.substring(1, text.length - 1) | ||
return { | ||
const node: AST.StringLiteral = { | ||
type: 'StringLiteral', | ||
@@ -1348,42 +1552,49 @@ value, | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
if (ctx.BreakKeyword()) { | ||
return { | ||
const node: AST.Break = { | ||
type: 'Break', | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
if (ctx.ContinueKeyword()) { | ||
return { | ||
const node: AST.Continue = { | ||
type: 'Continue', | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
return (this as any).visit(ctx.getChild(0)) | ||
}, | ||
return this.visit(ctx.getChild(0)) as AST.AssemblyItem | ||
} | ||
AssemblyExpression(ctx: Ctx) { | ||
return (this as any).visit(ctx.getChild(0)) | ||
}, | ||
public visitAssemblyExpression(ctx: SP.AssemblyExpressionContext) { | ||
return this.visit(ctx.getChild(0)) as AST.AssemblyExpression | ||
} | ||
AssemblyCall(ctx: Ctx) { | ||
const functionName = toText(ctx.getChild(0)) | ||
const args = ctx | ||
.assemblyExpression() | ||
.map((arg: any) => (this as any).visit(arg)) | ||
public visitAssemblyCall(ctx: SP.AssemblyCallContext) { | ||
const functionName = this._toText(ctx.getChild(0)) | ||
const args = ctx.assemblyExpression().map((assemblyExpr) => this.visitAssemblyExpression(assemblyExpr)) | ||
return { | ||
const node: AST.AssemblyCall = { | ||
type: 'AssemblyCall', | ||
functionName, | ||
arguments: args, | ||
} | ||
}, | ||
AssemblyLiteral(ctx: Ctx) { | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitAssemblyLiteral(ctx: SP.AssemblyLiteralContext): AST.AssemblyLiteral { | ||
let text | ||
if (ctx.stringLiteral()) { | ||
text = toText(ctx) | ||
text = this._toText(ctx)! | ||
const value = text.substring(1, text.length - 1) | ||
return { | ||
const node: AST.StringLiteral = { | ||
type: 'StringLiteral', | ||
@@ -1394,205 +1605,286 @@ value, | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
if (ctx.DecimalNumber()) { | ||
return { | ||
const node: AST.DecimalNumber = { | ||
type: 'DecimalNumber', | ||
value: toText(ctx), | ||
value: this._toText(ctx), | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
if (ctx.HexNumber()) { | ||
return { | ||
const node: AST.HexNumber = { | ||
type: 'HexNumber', | ||
value: toText(ctx), | ||
value: this._toText(ctx), | ||
} | ||
return this._addMeta(node, ctx) | ||
} | ||
if (ctx.hexLiteral()) { | ||
return (this as any).visit(ctx.hexLiteral()) | ||
return this.visitHexLiteral(ctx.hexLiteral()!) | ||
} | ||
}, | ||
AssemblySwitch(ctx: Ctx) { | ||
return { | ||
expression: (this as any).visit(ctx.assemblyExpression()), | ||
cases: ctx.assemblyCase().map((c: any) => (this as any).visit(c)), | ||
throw new Error('Should never reach here') | ||
} | ||
public visitAssemblySwitch(ctx: SP.AssemblySwitchContext) { | ||
const node: AST.AssemblySwitch = { | ||
type: 'AssemblySwitch', | ||
expression: this.visitAssemblyExpression(ctx.assemblyExpression()), | ||
cases: ctx.assemblyCase().map((c) => this.visitAssemblyCase(c)), | ||
} | ||
}, | ||
AssemblyCase(ctx: Ctx) { | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitAssemblyCase(ctx: SP.AssemblyCaseContext): AST.AssemblyCase { | ||
let value = null | ||
if (toText(ctx.getChild(0)) === 'case') { | ||
value = (this as any).visit(ctx.assemblyLiteral()) | ||
if (this._toText(ctx.getChild(0)) === 'case') { | ||
value = this.visitAssemblyLiteral(ctx.assemblyLiteral()!) | ||
} | ||
const node: any = { block: (this as any).visit(ctx.assemblyBlock()) } | ||
if (value) { | ||
node.value = value | ||
} else { | ||
node.default = true | ||
const node: AST.AssemblyCase = { | ||
type: 'AssemblyCase', | ||
block: this.visitAssemblyBlock(ctx.assemblyBlock()), | ||
value, | ||
default: value === null | ||
} | ||
return node | ||
}, | ||
return this._addMeta(node, ctx) | ||
} | ||
AssemblyLocalDefinition(ctx: Ctx) { | ||
let names = ctx.assemblyIdentifierOrList() | ||
if (names.identifier()) { | ||
names = [(this as any).visit(names.identifier())] | ||
} else if (names.assemblyMember()) { | ||
names = [(this as any).visit(names.assemblyMember())] | ||
public visitAssemblyLocalDefinition(ctx: SP.AssemblyLocalDefinitionContext): AST.AssemblyLocalDefinition { | ||
const ctxAssemblyIdentifierOrList = ctx.assemblyIdentifierOrList() | ||
let names | ||
if (ctxAssemblyIdentifierOrList.identifier()) { | ||
names = [this.visitIdentifier(ctxAssemblyIdentifierOrList.identifier()!)] | ||
} else if (ctxAssemblyIdentifierOrList.assemblyMember()) { | ||
names = [this.visitAssemblyMember(ctxAssemblyIdentifierOrList.assemblyMember()!)] | ||
} else { | ||
names = (this as any).visit(names.assemblyIdentifierList().identifier()) | ||
names = ctxAssemblyIdentifierOrList | ||
.assemblyIdentifierList()! | ||
.identifier()! | ||
.map((x) => this.visitIdentifier(x)) | ||
} | ||
return { | ||
const node: AST.AssemblyLocalDefinition = { | ||
type: 'AssemblyLocalDefinition', | ||
names, | ||
expression: (this as any).visit(ctx.assemblyExpression()), | ||
expression: this.visitAssemblyExpression(ctx.assemblyExpression()!), | ||
} | ||
}, | ||
AssemblyFunctionDefinition(ctx: Ctx) { | ||
let args = ctx.assemblyIdentifierList() | ||
args = args ? (this as any).visit(args.identifier()) : [] | ||
return this._addMeta(node, ctx) | ||
} | ||
let returnArgs = ctx.assemblyFunctionReturns() | ||
returnArgs = returnArgs | ||
? (this as any).visit(returnArgs.assemblyIdentifierList().identifier()) | ||
public visitAssemblyFunctionDefinition( | ||
ctx: SP.AssemblyFunctionDefinitionContext | ||
) { | ||
const ctxAssemblyIdentifierList = ctx.assemblyIdentifierList() | ||
const args = | ||
ctxAssemblyIdentifierList !== undefined | ||
? ctxAssemblyIdentifierList.identifier().map((x) => this.visitIdentifier(x)) | ||
: [] | ||
const ctxAssemblyFunctionReturns = ctx.assemblyFunctionReturns() | ||
const returnArgs = ctxAssemblyFunctionReturns | ||
? ctxAssemblyFunctionReturns | ||
.assemblyIdentifierList()! | ||
.identifier() | ||
.map((x) => this.visitIdentifier(x)) | ||
: [] | ||
return { | ||
name: toText(ctx.identifier()), | ||
const node: AST.AssemblyFunctionDefinition = { | ||
type: 'AssemblyFunctionDefinition', | ||
name: this._toText(ctx.identifier()), | ||
arguments: args, | ||
returnArguments: returnArgs, | ||
body: (this as any).visit(ctx.assemblyBlock()), | ||
body: this.visitAssemblyBlock(ctx.assemblyBlock()), | ||
} | ||
}, | ||
AssemblyAssignment(ctx: Ctx) { | ||
let names = ctx.assemblyIdentifierOrList() | ||
if (names.identifier()) { | ||
names = [(this as any).visit(names.identifier())] | ||
} else if (names.assemblyMember()) { | ||
names = [(this as any).visit(names.assemblyMember())] | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitAssemblyAssignment(ctx: SP.AssemblyAssignmentContext) { | ||
const ctxAssemblyIdentifierOrList = ctx.assemblyIdentifierOrList() | ||
let names | ||
if (ctxAssemblyIdentifierOrList.identifier()) { | ||
names = [this.visitIdentifier(ctxAssemblyIdentifierOrList.identifier()!)] | ||
} else if (ctxAssemblyIdentifierOrList.assemblyMember()) { | ||
names = [this.visitAssemblyMember(ctxAssemblyIdentifierOrList.assemblyMember()!)] | ||
} else { | ||
names = (this as any).visit(names.assemblyIdentifierList().identifier()) | ||
names = ctxAssemblyIdentifierOrList | ||
.assemblyIdentifierList()! | ||
.identifier() | ||
.map((x) => this.visitIdentifier(x)) | ||
} | ||
return { | ||
const node: AST.AssemblyAssignment = { | ||
type: 'AssemblyAssignment', | ||
names, | ||
expression: (this as any).visit(ctx.assemblyExpression()), | ||
expression: this.visitAssemblyExpression(ctx.assemblyExpression()), | ||
} | ||
}, | ||
AssemblyMember(ctx: Ctx) { | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitAssemblyMember(ctx: SP.AssemblyMemberContext): AST.AssemblyMemberAccess { | ||
const [accessed, member] = ctx.identifier() | ||
return { | ||
const node: AST.AssemblyMemberAccess = { | ||
type: 'AssemblyMemberAccess', | ||
expression: (this as any).visit(accessed), | ||
memberName: (this as any).visit(member), | ||
expression: this.visitIdentifier(accessed), | ||
memberName: this.visitIdentifier(member), | ||
} | ||
}, | ||
LabelDefinition(ctx: Ctx) { | ||
return { | ||
name: toText(ctx.identifier()), | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitLabelDefinition(ctx: SP.LabelDefinitionContext) { | ||
const node: AST.LabelDefinition = { | ||
type: 'LabelDefinition', | ||
name: this._toText(ctx.identifier()), | ||
} | ||
}, | ||
AssemblyStackAssignment(ctx: Ctx) { | ||
return { | ||
name: toText(ctx.identifier()), | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitAssemblyStackAssignment(ctx: SP.AssemblyStackAssignmentContext) { | ||
const node: AST.AssemblyStackAssignment = { | ||
type: 'AssemblyStackAssignment', | ||
name: this._toText(ctx.identifier()), | ||
} | ||
}, | ||
AssemblyFor(ctx: Ctx) { | ||
return { | ||
pre: (this as any).visit(ctx.getChild(1)), | ||
condition: (this as any).visit(ctx.getChild(2)), | ||
post: (this as any).visit(ctx.getChild(3)), | ||
body: (this as any).visit(ctx.getChild(4)), | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitAssemblyFor(ctx: SP.AssemblyForContext) { | ||
// TODO remove these type assertions | ||
const node: AST.AssemblyFor = { | ||
type: 'AssemblyFor', | ||
pre: this.visit(ctx.getChild(1)) as AST.AssemblyBlock | AST.AssemblyExpression, | ||
condition: this.visit(ctx.getChild(2)) as AST.AssemblyExpression, | ||
post: this.visit(ctx.getChild(3)) as AST.AssemblyBlock | AST.AssemblyExpression, | ||
body: this.visit(ctx.getChild(4)) as AST.AssemblyBlock, | ||
} | ||
}, | ||
AssemblyIf(ctx: Ctx) { | ||
return { | ||
condition: (this as any).visit(ctx.assemblyExpression()), | ||
body: (this as any).visit(ctx.assemblyBlock()), | ||
return this._addMeta(node, ctx) | ||
} | ||
public visitAssemblyIf(ctx: SP.AssemblyIfContext) { | ||
const node: AST.AssemblyIf = { | ||
type: 'AssemblyIf', | ||
condition: this.visitAssemblyExpression(ctx.assemblyExpression()), | ||
body: this.visitAssemblyBlock(ctx.assemblyBlock()), | ||
} | ||
}, | ||
} | ||
class ASTBuilder extends antlr4.tree.ParseTreeVisitor { | ||
public options: ParseOptions | ||
return this._addMeta(node, ctx) | ||
} | ||
constructor(options: ParseOptions) { | ||
super(options) | ||
private _toText(ctx: ParserRuleContext | ParseTree): string { | ||
const text = ctx.text | ||
if (text === undefined) { | ||
throw new Error('Assertion error: text should never be undefiend') | ||
} | ||
this.options = options | ||
return text | ||
} | ||
_loc(ctx: Ctx) { | ||
const sourceLocation = { | ||
private _stateMutabilityToText( | ||
ctx: SP.StateMutabilityContext | ||
): AST.FunctionDefinition['stateMutability'] { | ||
if (ctx.PureKeyword() !== undefined) { | ||
return 'pure' | ||
} | ||
if (ctx.ConstantKeyword() !== undefined) { | ||
return 'constant' | ||
} | ||
if (ctx.PayableKeyword() !== undefined) { | ||
return 'payable' | ||
} | ||
if (ctx.ViewKeyword() !== undefined) { | ||
return 'view' | ||
} | ||
throw new Error('Assertion error: non-exhaustive stateMutability check') | ||
} | ||
private _loc(ctx: ParserRuleContext): SourceLocation { | ||
const sourceLocation: SourceLocation = { | ||
start: { | ||
line: ctx.start.line, | ||
column: ctx.start.column, | ||
column: ctx.start.charPositionInLine, | ||
}, | ||
end: { | ||
line: ctx.stop ? ctx.stop.line : ctx.start.line, | ||
column: ctx.stop ? ctx.stop.column : ctx.start.column, | ||
column: ctx.stop | ||
? ctx.stop.charPositionInLine | ||
: ctx.start.charPositionInLine, | ||
}, | ||
} | ||
return { loc: sourceLocation } | ||
return sourceLocation | ||
} | ||
_range(ctx: Ctx) { | ||
return { range: [ctx.start.start, ctx.stop.stop] } | ||
_range(ctx: ParserRuleContext): [number, number] { | ||
return [ctx.start.startIndex, ctx.stop?.stopIndex ?? ctx.start.startIndex] | ||
} | ||
meta(ctx: Ctx) { | ||
const ret: any = {} | ||
private _addMeta<T extends AST.BaseASTNode>( | ||
node: T, | ||
ctx: ParserRuleContext | ||
): T { | ||
const nodeWithMeta: AST.BaseASTNode = { | ||
type: node.type, | ||
} | ||
if (this.options.loc === true) { | ||
Object.assign(ret, this._loc(ctx)) | ||
node.loc = this._loc(ctx) | ||
} | ||
if (this.options.range === true) { | ||
Object.assign(ret, this._range(ctx)) | ||
node.range = this._range(ctx) | ||
} | ||
return ret | ||
} | ||
createNode(obj: any, ctx: any) { | ||
return Object.assign(obj, this.meta(ctx)) | ||
return { | ||
...nodeWithMeta, | ||
...node, | ||
} | ||
} | ||
visit(ctx: Ctx): BaseASTNode | BaseASTNode[] | null { | ||
if (!ctx) { | ||
return null | ||
private _mapCommasToNulls(children: ParseTree[]) { | ||
if (children.length === 0) { | ||
return [] | ||
} | ||
if (Array.isArray(ctx)) { | ||
return ctx.map((child) => { | ||
return (this as any).visit(child) | ||
}, this) | ||
} | ||
const values: Array<ParseTree | null> = [] | ||
let comma = true | ||
let name: string = ctx.constructor.name | ||
if (name.endsWith('Context')) { | ||
name = name.substring(0, name.length - 'Context'.length) | ||
for (const el of children) { | ||
if (comma) { | ||
if (this._toText(el) === ',') { | ||
values.push(null) | ||
} else { | ||
values.push(el) | ||
comma = false | ||
} | ||
} else { | ||
if (this._toText(el) !== ',') { | ||
throw new Error('expected comma') | ||
} | ||
comma = true | ||
} | ||
} | ||
const node = { type: name } | ||
if (name in transformAST) { | ||
const visited = (transformAST as any)[name].call(this, ctx) | ||
if (Array.isArray(visited)) { | ||
return visited | ||
} | ||
Object.assign(node, visited) | ||
if (comma) { | ||
values.push(null) | ||
} | ||
return (this as any).createNode(node, ctx) | ||
return values | ||
} | ||
} | ||
export default ASTBuilder | ||
function isBinOp(op: string): op is AST.BinOp { | ||
return AST.binaryOpValues.includes(op as AST.BinOp) | ||
} |
122
src/index.ts
@@ -1,121 +0,3 @@ | ||
import SolidityLexer from './lib/SolidityLexer' | ||
import SolidityParser from './lib/SolidityParser' | ||
import { Token, ParseOptions, TokenizeOptions } from './types' | ||
import { AST, BaseASTNode } from './ast-types' | ||
export * from "./parser"; | ||
import antlr4 from 'antlr4' | ||
import { buildTokenList } from './tokens' | ||
import ASTBuilder from './ASTBuilder' | ||
import ErrorListener from './ErrorListener' | ||
interface ParserErrorItem { | ||
message: string | ||
line: number | ||
column: number | ||
} | ||
export class ParserError extends Error { | ||
public errors: ParserErrorItem[] | ||
constructor(args: { errors: ParserErrorItem[] }) { | ||
super() | ||
const { message, line, column } = args.errors[0] | ||
this.message = `${message} (${line}:${column})` | ||
this.errors = args.errors | ||
if (Error.captureStackTrace !== undefined) { | ||
Error.captureStackTrace(this, this.constructor) | ||
} else { | ||
this.stack = new Error().stack | ||
} | ||
} | ||
} | ||
export function tokenize( | ||
input: string, | ||
options: TokenizeOptions = {} | ||
): Token[] { | ||
const chars = new antlr4.InputStream(input) | ||
const lexer = new SolidityLexer(chars) | ||
const tokens = new antlr4.CommonTokenStream(lexer) | ||
return buildTokenList(tokens.tokenSource.getAllTokens(), options) | ||
} | ||
export function parse(input: string, options: ParseOptions = {}): AST { | ||
const chars = new antlr4.InputStream(input) | ||
const listener = new ErrorListener() | ||
const lexer: any = new SolidityLexer(chars) | ||
lexer.removeErrorListeners() | ||
lexer.addErrorListener(listener) | ||
const tokens = new antlr4.CommonTokenStream(lexer) | ||
const parser: any = new SolidityParser(tokens) | ||
parser.removeErrorListeners() | ||
parser.addErrorListener(listener) | ||
parser.buildParseTrees = true | ||
const tree = parser.sourceUnit() | ||
let tokenList: Token[] = [] | ||
if (options.tokens === true) { | ||
const tokenSource = tokens.tokenSource | ||
tokenSource.reset() | ||
tokenList = buildTokenList(tokenSource.getAllTokens(), options) | ||
} | ||
if (options.tolerant !== true && listener.hasErrors()) { | ||
throw new ParserError({ errors: listener.getErrors() }) | ||
} | ||
const visitor = new ASTBuilder(options) | ||
const ast = visitor.visit(tree) as AST | ||
if (options.tolerant === true && listener.hasErrors()) { | ||
ast.errors = listener.getErrors() | ||
} | ||
if (options.tokens === true) { | ||
ast.tokens = tokenList | ||
} | ||
return ast | ||
} | ||
function _isASTNode(node: any): node is BaseASTNode { | ||
return ( | ||
node !== null && | ||
typeof node === 'object' && | ||
Object.prototype.hasOwnProperty.call(node, 'type') | ||
) | ||
} | ||
export function visit(node: any, visitor: any): void { | ||
if (Array.isArray(node)) { | ||
node.forEach((child) => visit(child, visitor)) | ||
} | ||
if (!_isASTNode(node)) return | ||
let cont = true | ||
if (visitor[node.type] !== undefined) { | ||
cont = visitor[node.type](node) | ||
} | ||
if (cont === false) return | ||
for (const prop in node) { | ||
if (Object.prototype.hasOwnProperty.call(node, prop)) { | ||
visit((node as any)[prop], visitor) | ||
} | ||
} | ||
const selector = node.type + ':exit' | ||
if (visitor[selector] !== undefined) { | ||
visitor[selector](node) | ||
} | ||
} | ||
export type { ParseOptions } from "./types"; |
// This is an indirect file to import the tokens string | ||
// It needs to be a js file so that tsc doesn't complain | ||
import tokens from './lib/Solidity.tokens' | ||
export default tokens | ||
if (typeof BROWSER !== "undefined") { | ||
module.exports = require('./antlr/Solidity.tokens') | ||
} else { | ||
module.exports = require('fs') | ||
.readFileSync(require('path').join(__dirname, './antlr/Solidity.tokens')) | ||
.toString() | ||
} |
@@ -79,8 +79,8 @@ import { Token, AntlrToken, TokenizeOptions } from './types' | ||
if (options.range === true) { | ||
node.range = [token.start, token.stop + 1] | ||
node.range = [token.startIndex, token.stopIndex + 1] | ||
} | ||
if (options.loc === true) { | ||
node.loc = { | ||
start: { line: token.line, column: token.column }, | ||
end: { line: token.line, column: token.column + token.text.length }, | ||
start: { line: token.line, column: token.charPositionInLine }, | ||
end: { line: token.line, column: token.charPositionInLine + (token.text?.length ?? 0) }, | ||
} | ||
@@ -87,0 +87,0 @@ } |
@@ -0,1 +1,2 @@ | ||
import { Token as Antlr4TsToken } from "antlr4ts"; | ||
export interface Node { | ||
@@ -5,10 +6,3 @@ type: string | ||
export interface AntlrToken { | ||
type: string | ||
text: string | ||
start: number | ||
stop: number | ||
line: number | ||
column: number | ||
} | ||
export type AntlrToken = Antlr4TsToken; | ||
@@ -27,3 +21,3 @@ export interface TokenizeOptions { | ||
type: string | ||
value: string | ||
value: string | undefined | ||
range?: [number, number] | ||
@@ -30,0 +24,0 @@ loc?: { |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
9211110
49
99755
1