Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
d6ffa08
Add DeclarationStore to transpiler
prestonvasquez Apr 7, 2022
880146b
Add documentaiton for DeclarationStore
prestonvasquez Apr 7, 2022
f4af2f7
remove unecessary comments
prestonvasquez Apr 7, 2022
3816816
clean up generateCall comments
prestonvasquez Apr 7, 2022
6238135
Merge branch 'main' into COMPASS-5685.addVariableDeclarationsToBSONTr…
prestonvasquez Apr 7, 2022
c586097
correct the objectID example
prestonvasquez Apr 7, 2022
0c59f37
Merge branch 'COMPASS-5685.addVariableDeclarationsToBSONTranspiler' o…
prestonvasquez Apr 7, 2022
1f58923
correct the objectID example
prestonvasquez Apr 7, 2022
7090261
Revert "correct the objectID example"
prestonvasquez Apr 7, 2022
e5202fe
Update packages/bson-transpilers/codegeneration/DeclarationStore.js
prestonvasquez Apr 8, 2022
b281d8d
Update packages/bson-transpilers/README.md
prestonvasquez Apr 8, 2022
eb132d5
add ruby to output languages in README.md
prestonvasquez Apr 8, 2022
90cac7d
Merge branch 'COMPASS-5685.addVariableDeclarationsToBSONTranspiler' o…
prestonvasquez Apr 8, 2022
e5a8848
Merge branch 'main' into COMPASS-5685.addVariableDeclarationsToBSONTr…
prestonvasquez Apr 8, 2022
25c1263
Merge branch 'main' into COMPASS-5685.addVariableDeclarationsToBSONTr…
prestonvasquez Apr 8, 2022
0128ecd
Merge branch 'main' into COMPASS-5685.addVariableDeclarationsToBSONTr…
prestonvasquez Apr 11, 2022
0057dcf
add state to the transpiler class
prestonvasquez Apr 11, 2022
410d906
Merge branch 'COMPASS-5685.addVariableDeclarationsToBSONTranspiler' o…
prestonvasquez Apr 11, 2022
e6e880e
Clean up README.md
prestonvasquez Apr 11, 2022
f786206
Clean up README.md
prestonvasquez Apr 11, 2022
4ce40ae
Clean up README.md
prestonvasquez Apr 11, 2022
acf2867
Prevent re-declaration
prestonvasquez Apr 12, 2022
b2822e0
require template string when adding declarations
prestonvasquez Apr 12, 2022
4a85d5f
update readme
prestonvasquez Apr 12, 2022
fc5f1a6
make comments uniform
prestonvasquez Apr 12, 2022
ff25b11
make store a hash
prestonvasquez Apr 12, 2022
c3abe60
change mock to candidate
prestonvasquez Apr 12, 2022
232c774
remove duplicate method
prestonvasquez Apr 12, 2022
6ba5175
fix more gramatical errors
prestonvasquez Apr 12, 2022
10cd687
add comment to candidate explanation
prestonvasquez Apr 12, 2022
baaf195
use bind instead of passing new args to existing templates
prestonvasquez Apr 12, 2022
97ae108
update readme
prestonvasquez Apr 12, 2022
d35869f
Merge branch 'main' into COMPASS-5685.addVariableDeclarationsToBSONTr…
prestonvasquez Apr 12, 2022
5752064
clean up how state is passed to bind
prestonvasquez Apr 12, 2022
60a78fd
clean up how state is passed to bind
prestonvasquez Apr 12, 2022
da7e26e
Add addFunc option to declaration store
prestonvasquez Apr 12, 2022
5f1a4f5
Merge branch 'main' into COMPASS-5685.addVariableDeclarationsToBSONTr…
prestonvasquez Apr 12, 2022
7296d51
fix typo
prestonvasquez Apr 12, 2022
46a167b
Merge branch 'COMPASS-5685.addVariableDeclarationsToBSONTranspiler' o…
prestonvasquez Apr 12, 2022
3420e5b
clean up syntax
prestonvasquez Apr 12, 2022
f9a7bd4
prepend variables with declarations
prestonvasquez Apr 13, 2022
31ab476
Merge branch 'main' into COMPASS-5685.addVariableDeclarationsToBSONTr…
prestonvasquez Apr 13, 2022
2df04ee
Merge branch 'main' into COMPASS-5685.addVariableDeclarationsToBSONTr…
prestonvasquez Apr 14, 2022
78c97b5
Merge branch 'main' into COMPASS-5685.addVariableDeclarationsToBSONTr…
mcasimir Apr 14, 2022
5441bab
Update packages/bson-transpilers/codegeneration/DeclarationStore.js
prestonvasquez Apr 15, 2022
97405dd
Merge branch 'main' into COMPASS-5685.addVariableDeclarationsToBSONTr…
prestonvasquez Apr 18, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 52 additions & 1 deletion packages/bson-transpilers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![downloads][5]][6]

Transpilers for building BSON documents in any language. Current support
provided for `shell` `javascript` and `python` as inputs. `java`, `c#`, `node`, `shell` and `python` as
provided for `shell` `javascript` and `python` as inputs. `java`, `c#`, `node`, `shell`, `python` and `ruby` as
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this say go?

Copy link
Contributor Author

@prestonvasquez prestonvasquez Apr 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah, we should definitely include go. I'll update that on #2991 so that this PR can remain language agnostic. I added ruby for this request.

outputs.

> ⚠️  `shell` output produces code that is compatible only with legacy `mongo` shell not the new `mongosh` shell. See [COMPASS-4930](https://jira.mongodb.org/browse/COMPASS-4930) for some additional context
Expand Down Expand Up @@ -59,6 +59,57 @@ Any transpiler errors that occur will be thrown. To catch them, wrap the
- __error.column:__ If it is a syntax error, will have the column.
- __error.symbol:__ If it is a syntax error, will have the symbol associated with the error.


### State

The `CodeGenerationVisitor` class manages a global state which is bound to the `argsTemplate` functions. This state is intended to be used as a solution for the `argsTemplate` functions to communicate with the `DriverTemplate` function. For example:

```yaml
ObjectIdEqualsArgsTemplate: &ObjectIdEqualsArgsTemplate !!js/function >
(_) => {
this.oneLineStatement = "Hello World";
return '';
}

DriverTemplate: &DriverTemplate !!js/function >
(_spec) => {
return this.oneLineStatement;
}
```

The output of the driver syntax for this language will be the one-line statement `Hello World`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool! 🧑‍🔧


#### DeclarationStore
A more practical use-case of state is to accumulate variable declarations throughout the `argsTemplate` to be rendered by the `DriverTemplate`. That is, the motivation for using `DeclarationStore` is to prepend the driver syntax with variable declarations rather than using non-idiomatic solutions such as closures.

The `DeclarationStore` class maintains an internal state concerning variable declarations. For example,

```javascript
// within the args template
(arg) => {
return this.declarations.add("Temp", "objectID", (varName) => {
return [
`${varName}, err := primitive.ObjectIDFromHex(${arg})`,
'if err != nil {',
' log.Fatal(err)',
'}'
].join('\n')
})
}
```

Note that each use of the same variable name will result in an increment being added to the declaration statement. For example, if the variable name `objectIDForTemp` is used two times the resulting declaration statements will use `objectIDForTemp` for the first declaration and `objectID2ForTemp` for the second declaration. The `add` method returns the incremented variable name, and is therefore what would be expected as the right-hand side of the statement defined by the `argsTemplate` function.

The instance of the `DeclarationStore` constructed by the transpiler class is passed into the driver, syntax via state, for use:

```javascript
(spec) => {
const comment = '// some comment'
const client = 'client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(cs.String()))'
return "#{comment}\n\n#{client}\n\n${this.declarations.toString()}"
}
```

### Errors
There are a few different error classes thrown by `bson-transpilers`, each with
their own error code:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const {
} = require('../helper/error');

const { removeQuotes } = require('../helper/format');
const DeclarationStore = require('./DeclarationStore');

/**
* Class for code generation. Goes in between ANTLR generated visitor and
Expand All @@ -24,6 +25,7 @@ module.exports = (ANTLRVisitor) => class CodeGenerationVisitor extends ANTLRVisi
super();
this.idiomatic = true; // PUBLIC
this.clearImports();
this.state = { declarations: new DeclarationStore() };
}

clearImports() {
Expand Down Expand Up @@ -53,15 +55,32 @@ module.exports = (ANTLRVisitor) => class CodeGenerationVisitor extends ANTLRVisi
return this[rule](ctx);
}

returnResultWithDeclarations(ctx) {
let result = this.returnResult(ctx);
if (this.getState().declarations.length() > 0) {
result = `${this.getState().declarations.toString() + '\n\n'}${result}`;
}
return result;
}

/**
* PUBLIC: This is the entry point for the compiler. Each visitor must define
* an attribute called "startNode".
*
* @param {ParserRuleContext} ctx
* @param {Boolean} useDeclarations - prepend the result string with declarations
* @return {String}
*/
start(ctx) {
return this.returnResult(ctx).trim();
start(ctx, useDeclarations = false) {
return (useDeclarations ? this.returnResultWithDeclarations(ctx) : this.returnResult(ctx)).trim();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is definitely of note, it's what allows us to prepend the raw document output with functions/variables:

Screen Shot 2022-04-13 at 11 41 22 AM

There might be a much better way of going about this, though.

}

getState() {
return this.state;
}

clearDeclarations() {
this.getState().declarations.clear();
}

/**
Expand Down Expand Up @@ -330,7 +349,7 @@ module.exports = (ANTLRVisitor) => class CodeGenerationVisitor extends ANTLRVisi
}
returnFunctionCallLhsRhs(lhs, rhs, lhsType, l) {
if (lhsType.argsTemplate) {
rhs = lhsType.argsTemplate(l, ...rhs);
rhs = lhsType.argsTemplate.bind(this.getState())(l, ...rhs);
} else {
rhs = `(${rhs.join(', ')})`;
}
Expand Down Expand Up @@ -494,7 +513,7 @@ module.exports = (ANTLRVisitor) => class CodeGenerationVisitor extends ANTLRVisi
? lhsType.template()
: defaultT;
const rhs = lhsType.argsTemplate
? lhsType.argsTemplate(lhsArg, ...args)
? lhsType.argsTemplate.bind(this.getState())(lhsArg, ...args)
: defaultA;
const lhs = skipLhs ? '' : lhsArg;
return this.Syntax.new.template
Expand All @@ -516,7 +535,7 @@ module.exports = (ANTLRVisitor) => class CodeGenerationVisitor extends ANTLRVisi
let args = '';
const keysAndValues = this.getKeyValueList(ctx);
if (ctx.type.argsTemplate) {
args = ctx.type.argsTemplate(
args = ctx.type.argsTemplate.bind(this.getState())(
this.getKeyValueList(ctx).map((k) => {
return [this.getKeyStr(k), this.visit(this.getValue(k))];
}),
Expand All @@ -526,7 +545,7 @@ module.exports = (ANTLRVisitor) => class CodeGenerationVisitor extends ANTLRVisi
}
ctx.indentDepth--;
if (ctx.type.template) {
return ctx.type.template(args, ctx.indentDepth);
return ctx.type.template.bind(this.getState())(args, ctx.indentDepth);
}
return this.visitChildren(ctx);
}
Expand All @@ -545,7 +564,7 @@ module.exports = (ANTLRVisitor) => class CodeGenerationVisitor extends ANTLRVisi
if (ctx.type.argsTemplate) { // NOTE: not currently being used anywhere.
args = visitedElements.map((arg, index) => {
const last = !visitedElements[index + 1];
return ctx.type.argsTemplate(arg, ctx.indentDepth, last);
return ctx.type.argsTemplate.bind(this.getState())(arg, ctx.indentDepth, last);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does bind just merge the variables in state with the ones already in this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It actually replaces the existing this of the prototypes with state. The solutions seems safe based on my conversations with @mcasimir , but there may definitely be underlying problems I'm not aware of. An alternative would be to merge the two objects before the bind.

}).join('');
} else {
args = visitedElements.join(', ');
Expand Down
90 changes: 90 additions & 0 deletions packages/bson-transpilers/codegeneration/DeclarationStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/**
* Stores declarations for use in the DriverTemplate
*
* @returns {object}
*/
class DeclarationStore {
constructor() {
this.clear();
}

/**
* Add declarations by templateID + varRoot + declaration combo. Duplications will not be collected, rather the add
* method will return the existing declaration's variable name.
*
* @param {string} templateID - Name/alias of the template the declaration is being made for
* @param {string} varRoot - The root of the variable name to be appended by the occurrence count
* @param {function} declaration - The code block to be prepended to the driver syntax
* @returns {string} the variable name with root and appended count
*/
addVar(templateID, varRoot, declaration) {
// Don't push existing declarations
const current = this.alreadyDeclared(templateID, varRoot, declaration);
if (current !== undefined) {
return current;
}
const varName = this.next(templateID, varRoot);
this.vars[declaration(varName)] = varName;
return varName;
}

/**
* Add a function to the funcs set
*
* @param {string} fn - String literal of a function
*/
addFunc(fn) {
if (!this.funcs[fn]) {
this.funcs[fn] = true;
}
}

alreadyDeclared(templateID, varRoot, declaration) {
const existing = this.candidates(templateID, varRoot);
for (var i = 0; i < existing.length; i++) {
const candidate = `${this.varTemplateRoot(templateID, varRoot)}${i > 0 ? i : ''}`;
const current = this.vars[declaration(candidate)];
if (current !== undefined) {
return current;
}
}
}

candidates(templateID, varRoot) {
const varTemplateRoot = this.varTemplateRoot(templateID, varRoot);
return Object.values(this.vars).filter(varName => varName.startsWith(varTemplateRoot));
}

clear() {
this.vars = {};
this.funcs = {};
}

length() {
return Object.keys(this.vars).length + Object.keys(this.funcs).length;
}

next(templateID, varRoot) {
const existing = this.candidates(templateID, varRoot);

// If the data does not exist in the vars, then the count should append nothing to the variable name
const count = existing.length > 0 ? existing.length : '';
return `${this.varTemplateRoot(templateID, varRoot)}${count}`;
}

/**
* Stringify the variable declarations
*
* @param {string} sep - Seperator string placed between elements in the resulting string of declarations
* @returns {string} all the declarations as a string seperated by a line-break
*/
toString(sep = '\n\n') {
return [...Object.keys(this.vars), ...Object.keys(this.funcs)].join(sep);
}

varTemplateRoot(templateID, varRoot) {
return `${varRoot}${templateID}`;
}
}

module.exports = DeclarationStore;
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ module.exports = (Visitor) => class Generator extends Visitor {
}

if (lhsType && lhsType.argsTemplate) {
return lhsType.argsTemplate(lhs, ...args);
return lhsType.argsTemplate.bind(this.getState())(lhs, ...args);
}

let expr;
Expand Down Expand Up @@ -163,7 +163,7 @@ module.exports = (Visitor) => class Generator extends Visitor {
return this.Syntax.equality.template(s, op, this.visit(arr[i + 1]));
}
if (op === 'in' || op === 'notin') {
return this.Syntax.in.template(s, op, this.visit(arr[i + 1]));
return this.Syntax.in.template.bind(this.state)(s, op, this.visit(arr[i + 1]));
}
throw new BsonTranspilersRuntimeError(`Unrecognized operation ${op}`);
}, this.visit(ctx.children[0]));
Expand Down
4 changes: 2 additions & 2 deletions packages/bson-transpilers/codegeneration/python/Visitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ module.exports = (CodeGenerationVisitor) => class Visitor extends CodeGeneration
if (ctx.type.argsTemplate) { // NOTE: not currently being used anywhere.
args = visitedElements.map((arg, index) => {
const last = !visitedElements[index + 1];
return ctx.type.argsTemplate(arg, ctx.indentDepth, last);
return ctx.type.argsTemplate.bind(this.getState())(arg, ctx.indentDepth, last);
});
join = '';
} else {
Expand Down Expand Up @@ -338,7 +338,7 @@ module.exports = (CodeGenerationVisitor) => class Visitor extends CodeGeneration
if (op === 'in' || op === 'notin') {
skip = true;
if (this.Syntax.in) {
return `${str}${this.Syntax.in.template(
return `${str}${this.Syntax.in.template.bind(this.state)(
this.visit(arr[i - 1]), op, this.visit(arr[i + 1]))}`;
}
return `${str} ${this.visit(arr[i - 1])} ${op} ${this.visit(arr[i + 1])}`;
Expand Down
6 changes: 4 additions & 2 deletions packages/bson-transpilers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,9 @@ const getTranspiler = (loadTree, visitor, generator, symbols) => {
idiomatic;
if (!driverSyntax) {
transpiler.clearImports();
transpiler.clearDeclarations();
}
return transpiler.start(tree);
return transpiler.start(tree, !driverSyntax);
} catch (e) {
if (e.code && e.code.includes('BSONTRANSPILERS')) {
throw e;
Expand All @@ -148,6 +149,7 @@ const getTranspiler = (loadTree, visitor, generator, symbols) => {
return {
compileWithDriver: (input, idiomatic) => {
transpiler.clearImports();
transpiler.clearDeclarations();

const result = {};
Object.keys(input).map((k) => {
Expand All @@ -171,7 +173,7 @@ const getTranspiler = (loadTree, visitor, generator, symbols) => {
'Generating driver syntax not implemented for current language'
);
}
return transpiler.Syntax.driver(result);
return transpiler.Syntax.driver.bind(transpiler.getState())(result);
},
compile: compile,
getImports: (driverSyntax) => {
Expand Down
Loading