-
-
Notifications
You must be signed in to change notification settings - Fork 670
Transformer Class #866
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Transformer Class #866
Changes from all commits
261b8bf
8729f06
a5441c1
a564bda
0951215
3ab14f9
c068111
77dbb6c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,4 +3,7 @@ docs/ | |
node_modules/ | ||
out/ | ||
raw/ | ||
.history | ||
.history | ||
lib/visitor/dist/lib/ | ||
|
||
lib/visitor/dist/src/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -212,19 +212,31 @@ exports.main = function main(argv, options, callback) { | |
// Set up transforms | ||
const transforms = []; | ||
if (args.transform) { | ||
args.transform.forEach(transform => | ||
transforms.push( | ||
require( | ||
path.isAbsolute(transform = transform.trim()) | ||
? transform | ||
: path.join(process.cwd(), transform) | ||
) | ||
) | ||
); | ||
args.transform.forEach(transform => { | ||
transforms.push(require(path.isAbsolute(transform = transform.trim()) ? | ||
transform : path.join(process.cwd(), transform))); | ||
}); | ||
} | ||
function applyTransform(name, ...args) { | ||
transforms.forEach(transform => { | ||
if (typeof transform[name] === "function") transform[name](...args); | ||
try { | ||
// Export transform function function | ||
if (transform[name] && typeof transform[name] === "function") { | ||
transform[name](...args); | ||
} else if (transform.default) { | ||
// Export default class which needs to be constructor | ||
if (!isConstructor(transform.default)){ | ||
throw new Error("default exported transformer must be a constructor"); | ||
} | ||
const transformer = new transform.default(...args); | ||
if (typeof transformer[name] !== "function") { | ||
throw new Error("Transformer missing " + name + " method."); | ||
} | ||
transformer[name](); | ||
} | ||
} catch (e) { | ||
callback(e); | ||
} | ||
}); | ||
} | ||
|
||
|
@@ -426,7 +438,7 @@ exports.main = function main(argv, options, callback) { | |
} | ||
|
||
// Call afterParse transform hook | ||
applyTransform("afterParse", parser); | ||
applyTransform("afterParse", parser, writeFile, readFile, baseDir, writeStdout, writeStderr); | ||
|
||
// Parse additional files, if any | ||
{ | ||
|
@@ -796,6 +808,20 @@ exports.main = function main(argv, options, callback) { | |
} | ||
}); | ||
} | ||
|
||
function writeStderr(contents) { | ||
if (!writeStderr.used) { | ||
stats.writeCount++; | ||
writeStderr.used = true; | ||
} | ||
stats.writeTime += measure(() => { | ||
if (typeof contents === "string") { | ||
stderr.write(contents, { encoding: "utf8" }); | ||
} else { | ||
stderr.write(contents); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
/** Checks diagnostics emitted so far for errors. */ | ||
|
@@ -939,3 +965,7 @@ exports.tscOptions = { | |
types: [], | ||
allowJs: false | ||
}; | ||
|
||
function isConstructor(func) { | ||
return (func && typeof func === "function" && func.prototype && func.prototype.constructor) === func; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should be function isConstructor(fn) {
return typeof fn === "function" && !!fn.prototype && fn.prototype.constructor === fn;
} Just check: assert.equal(isConstructor(undefined), false);
assert.equal(isConstructor(null), false); Currently all this |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
# AST Transformer | ||
|
||
After parsing the files imported by the entry files, the Abstract Syntax Tree (AST) is made. With the `--transform` argument, the compiler will load any js files provided and if the exports include a Transformer class. | ||
|
||
## Transformer Class | ||
|
||
By extending the Transformer class your subclass gains access to the `Parser`, `writeFile` function, `baseDir` is where the project's root is located, and `stdout` and `stderr` | ||
|
||
```ts | ||
export abstract class Transformer { | ||
constructor( | ||
protected parser: Parser, | ||
protected writeFile: FileWriter, | ||
protected baseDir: string, | ||
protected stdout: (data: string) => void, | ||
protected stderr: (data: string) => void | ||
) {} | ||
|
||
get program(): Program { | ||
return this.parser.program; | ||
} | ||
|
||
abstract afterParse(): void; | ||
} | ||
``` | ||
|
||
### Example ASTPrinter | ||
|
||
Below is an example of visiting each source that is a user entry file and creating a string from visiting the AST. | ||
|
||
```ts | ||
/** | ||
* Example of using a transformer to print the AST for each entry file | ||
*/ | ||
export default class Printer extends Transformer { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
afterParse(): void { | ||
const files = this.parser.program.sources.filter( | ||
_source => _source.sourceKind == SourceKind.USER_ENTRY | ||
); | ||
files.forEach(source => { | ||
// Create a string of source rebuilt from the AST node `source` | ||
const sourceText: string = ASTBuilder.build(source); | ||
this.stdout(sourceText); | ||
}); | ||
} | ||
} | ||
``` | ||
|
||
To try it out first you might need to build the transformer files: | ||
|
||
```bash | ||
npm run build | ||
``` | ||
|
||
Then in the test directory build the sample assemblyscript file: | ||
|
||
```bash | ||
cd tests && npm run build | ||
``` | ||
|
||
## Visitors | ||
|
||
There is a base AST visitor class in `src/base.ts` which visits each node in the AST. If extended, overwritten methods visit methods will be called when traversing the tree. | ||
|
||
For example, `src/examples/functions.ts` collects the names of function and method declarations. | ||
|
||
```ts | ||
class FunctionVisitor extends BaseVisitor { | ||
funcsFound: string[] = []; | ||
currentClass: string; | ||
|
||
visitClassDeclaration(node: ClassDeclaration): void { | ||
// Remember current class | ||
this.currentClass = node.name.text; | ||
// Important to pass call parent visitor if you want to visit child nodes | ||
super.visitClassDeclaration(node); | ||
// Note can't call `super.visit(node) because it will call this function again. | ||
} | ||
|
||
visitFunctionDeclaration(node: FunctionDeclaration): void { | ||
this.funcsFound.push("Function: " + node.name.text); | ||
} | ||
|
||
visitMethodDeclaration(node: MethodDeclaration): void { | ||
this.funcsFound.push("Method: " + this.currentClass + "." + node.name.text); | ||
} | ||
|
||
static visit(node: Node): string { | ||
const visitor = new FunctionVisitor(); | ||
visitor.visit(node); | ||
return visitor.funcsFound.join("\n"); | ||
} | ||
} | ||
``` |
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
{ | ||
"name": "as-transformer", | ||
"description": "Tools for transforming the Abstract Syntax Tree (AST)", | ||
"main": "src/index.ts", | ||
"types": "src/index.ts", | ||
"scripts": { | ||
"build": "npm run webpack", | ||
"webpack": "../../node_modules/.bin/webpack --config=./webpack.config.js", | ||
"build:dev": "npm run webpack -- --mode=development", | ||
"test": "cd tests && npm run test" | ||
}, | ||
"author": "W", | ||
"license": "MIT" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's works only for
default
exports which could cause to problems