You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
227 lines
6.7 KiB
JavaScript
227 lines
6.7 KiB
JavaScript
"use strict";
|
|
|
|
const matchValue = require("match-value");
|
|
const asExpression = require("as-expression");
|
|
|
|
const FIELD_MAPPINGS = {
|
|
attrset_expression: {
|
|
binding_set: "MERGE"
|
|
},
|
|
rec_attrset_expression: {
|
|
binding_set: "MERGE"
|
|
},
|
|
let_attrset_expression: {
|
|
binding_set: "MERGE"
|
|
},
|
|
let_expression: {
|
|
binding_set: "MERGE"
|
|
}
|
|
};
|
|
|
|
function mergeNode(parent, child) {
|
|
// NOTE: Mutates parent!
|
|
let { type, ... childArgs } = child;
|
|
Object.assign(parent, childArgs);
|
|
}
|
|
|
|
// TODO: Refactor this
|
|
function convertStringNodeParts(node) {
|
|
let fullText = node.text;
|
|
let currentIndex = node.startIndex;
|
|
let parts = [];
|
|
|
|
function relative(index) {
|
|
return index - node.startIndex;
|
|
}
|
|
|
|
for (let child of node.children) {
|
|
if (child.type === "interpolation") {
|
|
if (child.startIndex > currentIndex) {
|
|
// We skipped some literal string
|
|
let value = fullText.slice(relative(currentIndex), relative(child.startIndex));
|
|
parts.push({ type: "NixInterpolationLiteral", value: value });
|
|
}
|
|
|
|
parts.push(convertNode(child));
|
|
|
|
currentIndex = child.endIndex;
|
|
}
|
|
}
|
|
|
|
if (currentIndex < node.endIndex) {
|
|
// Last bit of string literal at the end
|
|
let value = fullText.slice(relative(currentIndex), relative(node.endIndex));
|
|
parts.push({ type: "NixInterpolationLiteral", value: value });
|
|
}
|
|
|
|
return parts.map((part, i) => {
|
|
// FIXME: Verify that this logic is correct, and also holds up for actual interpolated strings
|
|
let isLiteralString = (part.type === "NixInterpolationLiteral");
|
|
|
|
if (isLiteralString) {
|
|
let strippedString = part.value;
|
|
|
|
if (i === 0 && isLiteralString) {
|
|
// Strip prefix quote
|
|
strippedString = strippedString.slice(1);
|
|
}
|
|
|
|
if (i === parts.length - 1 && isLiteralString) {
|
|
// Strip suffix quote
|
|
strippedString = strippedString.slice(0, -1);
|
|
}
|
|
|
|
return { ... part, value: strippedString };
|
|
} else {
|
|
return part;
|
|
}
|
|
});
|
|
}
|
|
|
|
function convertNode(node) {
|
|
let { type } = node;
|
|
|
|
let result = (node.isNamed)
|
|
? { type: type }
|
|
: { type: "token", text: node.text }
|
|
|
|
if (node.fields != null) {
|
|
// console.log({ node, fields: node.fields, children: node.children });
|
|
for (let field of node.fields) {
|
|
let children = node[field];
|
|
let fieldName = field.replace(/Nodes?$/, "");
|
|
|
|
result[fieldName] = asExpression(() => {
|
|
if (children == null) {
|
|
return null;
|
|
} else if (Array.isArray(children)) {
|
|
return children.map(convertNode);
|
|
} else {
|
|
return convertNode(children);
|
|
}
|
|
});
|
|
}
|
|
|
|
let nodeFieldMappings = FIELD_MAPPINGS[node.type];
|
|
|
|
if (nodeFieldMappings != null) {
|
|
node.children.forEach((child, i) => {
|
|
let mapTo = nodeFieldMappings[child.type];
|
|
|
|
if (mapTo === "MERGE") {
|
|
// This means we should elide the child node, and merge it into the node we're currently processing; this is used to deal with non-hidden layers of indirection
|
|
mergeNode(result, convertNode(child));
|
|
} else if (mapTo != null) {
|
|
// TODO: Support for multi-child fields?
|
|
result[mapTo] = convertNode(child);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Special case: if we don't provide a Babel-compatible top-level Program entry, traversal will fail
|
|
if (type === "source_code") {
|
|
return {
|
|
type: "Program",
|
|
sourceType: "script",
|
|
body: [ result.expression ]
|
|
};
|
|
}
|
|
|
|
// Special case: this is just a wrapper for an identifier, and we don't really want to keep this wrapper
|
|
if (type === "variable_expression") {
|
|
return convertNode(node.nameNode);
|
|
}
|
|
|
|
// The below section is based on `alias` expressions throughout the grammar, and the rules in https://github.com/cstrahan/tree-sitter-nix/blob/83ee5993560bf15854c69b77d92e34456f8fb655/grammar.js#L53-L59
|
|
if (type === "identifier" || type === "attr_identifier") {
|
|
// FIXME: attr_identifier gone?
|
|
result.name = node.text;
|
|
} else if (type === "integer_expression" || type === "float_expression") {
|
|
result.value = node.text;
|
|
} else if (type === "string_expression") {
|
|
result.parts = convertStringNodeParts(node);
|
|
} else if (type === "path_expression" || type === "hpath_expression") {
|
|
result.path = node.text;
|
|
} else if (type === "spath_expression") {
|
|
// Strip the < and >
|
|
result.path = node.text.slice(1, -1);
|
|
} else if (type === "uri_expression") {
|
|
result.uri = node.text;
|
|
}
|
|
|
|
if (type === "binary_expression" || type === "unary_expression") {
|
|
// Unpack the anonymous token
|
|
result.operator = result.operator.text;
|
|
}
|
|
|
|
if (type === "rec_attrset_expression") {
|
|
result.recursive = true;
|
|
} else if (type === "attrset") {
|
|
result.recursive = false;
|
|
}
|
|
|
|
// FIXME: Is this code still needed after the parser updates?
|
|
if (type === "inherit") {
|
|
// We're inheriting from scope here, so the source expression is explicitly not set
|
|
result.expression = result.expression ?? null;
|
|
} else if (type === "inherit_from") {
|
|
// Already set
|
|
}
|
|
|
|
if (type === "list_expression") {
|
|
// TODO: Can this sort of renaming be done more neatly?
|
|
result.elements = result.element;
|
|
delete result.element;
|
|
}
|
|
|
|
// console.log(result);
|
|
|
|
result.type = matchValue(result.type, {
|
|
source_code: "NixProgram",
|
|
token: "NixAnonymousToken",
|
|
path_expression: "NixPathLiteral",
|
|
hpath_expression: "NixHomePathLiteral", /* FIXME: Do we have interpolation support here yet? */
|
|
spath_expression: "NixEnvironmentPathLiteral",
|
|
uri_expression: "NixURILiteral",
|
|
integer_expression: "NixIntegerLiteral",
|
|
float_expression: "NixFloatLiteral",
|
|
string_expression: "NixStringLiteral",
|
|
parenthesized_expression: "NixParenthesizedExpression",
|
|
attrset_expression: "NixAttributeSet",
|
|
rec_attrset_expression: "NixAttributeSet",
|
|
binding_set: "NixAttributeSetBindings",
|
|
let_expression: "NixLetIn",
|
|
let_attrset_expression: "NixLetAttributeSet",
|
|
identifier: "NixIdentifier",
|
|
attr_identifier: "NixAttributeIdentifier", // FIXME: Gone?
|
|
attrpath: "NixAttributePath",
|
|
inherit: "NixInherit",
|
|
inherit_from: "NixInherit",
|
|
inherited_attrs: "NixInheritAttributes",
|
|
attrs_inherited_from: "NixInheritAttributes", // FIXME: Gone?
|
|
binding: "NixBinding",
|
|
unary_expression: "NixUnaryOperation",
|
|
binary_expression: "NixBinaryOperation",
|
|
apply_expression: "NixFunctionCall",
|
|
select_expression: "NixAttributeSelection",
|
|
interpolation: "NixInterpolationExpression",
|
|
list_expression: "NixListLiteral",
|
|
with_expression: "NixWithExpression",
|
|
if_expression: "NixConditional",
|
|
// Function definitions
|
|
function_expression: "NixFunctionDefinition",
|
|
formals: "NixUnpackedAttributes",
|
|
formal: "NixUnpackedAttribute",
|
|
// _: (value) => value
|
|
// FIXME: assert_expression? if_expression? unary_expression? has_attr_expression? apply_expression? indented_string_expression? binding_set?
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Produces an AST that's roughly shape-compatible with ESTree so that Babel can deal with it */
|
|
module.exports = function prepareAST(tree) {
|
|
return convertNode(tree.rootNode);
|
|
};
|