From ba9e5964f8ce140f6b83a5f7191fa526181c5aff Mon Sep 17 00:00:00 2001 From: Aditya Gupta Date: Sat, 29 Jan 2022 02:37:12 +0530 Subject: [PATCH 1/2] Add support for 'include' statements --- makefile-parser.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/makefile-parser.js b/makefile-parser.js index 6863575..3070da8 100644 --- a/makefile-parser.js +++ b/makefile-parser.js @@ -1,3 +1,6 @@ +const {readFileSync} = require("fs"); +const path = require("path"); + class Matcher { constructor({name, re, match, handle, terminal}) { this.name = name @@ -55,6 +58,20 @@ const matchers = [ } } }, + { + name: 'include', + re: /^include (.*)/, + handle(ctx, line, filename) { + const include_token = { include: filename }; + let lastToken = ctx.ast[ctx.ast.length - 1] + if( lastToken && lastToken.comment ) { + include_token['comment'] = lastToken.comment; + } + + ctx.ast.pop() + ctx.ast.push(include_token) + } + }, { name: 'target', re: /^((?:[^:\t\s]|\:)+)\s*:([^=].*)?$/, @@ -94,12 +111,20 @@ const matchers = [ module.exports = function parseMakefile(str, options={}) { if (!('strict' in options)) options.strict = false if (!('unhandled' in options)) options.unhandled = false + if (!('isfilename' in options)) options.isfilename = false; function handleError(err) { if (options.strict) throw new Error(err) else console.error(err) } + // 'Maybe' a filename, depending on options.isfilename, DONT use this variable elsewhere + let filename = str; + + if( options.isfilename ) { + str = readFileSync(filename).toString(); + } + // Join continued lines str = str.replace(/\\\n\s*/g, '') const lines = str.split(/\n/) @@ -108,6 +133,7 @@ module.exports = function parseMakefile(str, options={}) { ast: [], unhandled: [] } + for (let line of lines) { const list = matchers.filter(m => m.match(ctx, line)) if (list.length === 0) { @@ -121,7 +147,26 @@ module.exports = function parseMakefile(str, options={}) { handleError(`!! AMBIGUOUS: (${list.map(x => x.name)}) '${line}'`) continue } else { + // Ensured in this branch, list contains only one element, so list[0] is fine list.map(m => m.handle(ctx, line)) + + if (list[0].name === 'include') { + if( options.isfilename === false ) { + handleError("Ignoring 'include' statements. For it to work, filename is required instead of content string.\nTo use it, use like this: parseMakefile(\"/path/to/makefile\", {isfilename: true})"); + } else { + const relative_path = ctx.ast[ctx.ast.length - 1].include; + const parent_dir = path.dirname(filename); + const absolute_path = path.join(parent_dir, relative_path); + + // recursively call parseMakefile on the included file + const included_ctx = parseMakefile(absolute_path, {...options, isfilename: true}); + + // `included_ctx` contains context for the included file, merge it with our context + ctx.PHONY = ctx.PHONY.concat( included_ctx.PHONY ); + ctx.ast = ctx.ast.concat( included_ctx.ast ); + ctx.unhandled = ctx.unhandled.concat( included_ctx.unhandled ); + } + } } } return ctx From d56d58fbe8b648b50462221b428ebd1dba091ca2 Mon Sep 17 00:00:00 2001 From: Aditya Gupta Date: Sat, 29 Jan 2022 02:45:49 +0530 Subject: [PATCH 2/2] Fix: Dont pop ast if not a comment There maybe problematic case, when say a 'include' statement doesn't have a comment as the lastToken, then we would be removing some other valid data otherwise This commit solves that --- makefile-parser.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/makefile-parser.js b/makefile-parser.js index 3070da8..a6137fb 100644 --- a/makefile-parser.js +++ b/makefile-parser.js @@ -65,10 +65,12 @@ const matchers = [ const include_token = { include: filename }; let lastToken = ctx.ast[ctx.ast.length - 1] if( lastToken && lastToken.comment ) { - include_token['comment'] = lastToken.comment; + include_token['comment'] = lastToken.comment; + + // Pop the comment token, since it's already added in include_token + ctx.ast.pop() } - ctx.ast.pop() ctx.ast.push(include_token) } },