scansion/parser.lua

Sat, 29 Dec 2018 02:52:55 -0500

author
Waqas Hussain <waqas20@gmail.com>
date
Sat, 29 Dec 2018 02:52:55 -0500
changeset 163
0e2150680a25
parent 162
f888f236321f
permissions
-rw-r--r--

parser: Prefix parse errors with "ParserError: "

local function parse(data)
	local parsed = {
		objects = {};
		actions = {};
		tags = {};
	};
	
	local line_number = 0;
	local last_object;
	local annotation;

	local function parse_error(text)
		return nil, "ParseError: " .. text .. " (line " .. line_number .. ")";
	end

	for line in data:gmatch("([^\r\n]*)\r?\n") do
		line_number = line_number + 1;
		if line:sub(1,1) == "[" then
			local obj_type, name = line:match("^%[(%a+)%] (.+)$");
			
			if parsed.objects[name] then
				return parse_error("Duplicate definition of '"..name.."'");
			end
			parsed.objects[name] = {
				type = obj_type:lower();
				name = name;
				defined_line = line_number;
			};
			last_object = parsed.objects[name];
		elseif line:match("^%s+[%a_]+:.+$") then
			if not last_object then
				return parse_error("Unexpected outside of an object definition");
			end
			local k, v = line:match("^%s+([%a_]+):%s*(.+)$")
			last_object[k] = v;
		elseif #parsed.actions > 0 and line:sub(1,1) == "\t" then
			table.insert(parsed.actions[#parsed.actions].extra, line:sub(2));
			parsed.actions[#parsed.actions].line_end = line_number;
		elseif line:match("^%s*$") or line:match("^#") or line:match("^([/-])%1") then
			-- Blank line or comment
			local in_header = (next(parsed.objects) == nil) and (next(parsed.actions) == nil);
			if in_header and #line > 0 then
				if not parsed.title and not line:match("^#!") then
					parsed.title = line:gsub("^[#-]+%s*", "");
				elseif line:match("^##") then
					local tag = line:gsub("^##%s*", "");
					local k, v = tag:match("^([^:]+):%s*(.+)$");
					if k then
						-- Tag format: ## tagkey:tagvalue
						parsed.tags[k] = v;
					else
						-- Tag format: ## tagfoobar
						parsed.tags[tag] = true;
					end
				else
					parsed.summary = (parsed.summary and parsed.summary.."\n" or "")..line:gsub("^[#-]+%s*", "");
				end
			else
				-- Save as annotation for the following action
				if #line == 0 then
					if annotation then
						annotation.closed = true;
					end
				elseif annotation or not line:match("^%-+$") then
					if (not annotation) or annotation.closed then
						annotation = { line };
					else
						table.insert(annotation, line);
					end
				end
			end
		else
			last_object = nil;
			local name, action, extra = line:match("^([^:]+) (%a+):?%s?(.*)$");
			if not name then
				return parse_error("Unable to parse action");
			end
			if not parsed.objects[name] then
				return parse_error("The object '"..name.."' was not declared");
			end
			table.insert(parsed.actions, {
				object_name = name;
				action = action:lower();
				extra = {#extra>0 and extra or nil};
				annotation = annotation and table.concat(annotation, "\n") or nil;
				line_start = line_number;
				line_end = line_number;
			});
			annotation = nil;
		end
	end
	return parsed;
end

return {
	parse = parse;
};

mercurial