#!/usr/bin/env python3
# Use this script to read diagnostics text from stdin and parse into JSON
# format. You can change the hashbang above to use another interpreter. The
# JSON output should have the following structure:
# [
# 	{
# 		"file": "/path/to/my/file", (optional, defaults to current file)
# 		"location": {
# 			"offset": absolute-char-offset,
# 			"length": length
# 				...or...
# 			"line": line,
# 			"column": column,
# 			"endLine": end-line, (optional)
# 			"endColumn": end-column (optional)
# 		},
# 		"message": "Something is wrong with this code...",
# 		"type": one of the following values: (optional)
# 			"error", "warning", "notice", "runtime_error", "runtime_warning"
# 		"messageID": (optional)
# 			Identifier for this particular message, used to ignore the message
# 		"fixes": [ (optional)
# 			{
# 				"location": { ...same location properties as above... },
# 				"replacement": "string that replaces characters in location",
# 				"title": button text (optional, Default: "Fix")
# 				"description": "description of fix" (optional),
# 			},
# 			...
# 		],
# 		"matchedLocations": [ (optional, only applicable for console text)
# 			Used for highlighting and producing links in output displayed in the
# 			console. Specifies the matched portion of the parsed diagnostics
# 			text producing this diagnostics message.
# 			{
# 				"location": { ...same location properties as above... },
# 					(The location within the parsed text to be highlighted)
# 				"link": Adds a link to the text. Use an empty string to use an
# 						automatically generated link to this diagnostic message.
# 				"scopes": [
# 					A list of scope names used to semantically and visually
# 					highlight the text. Recommended scopes are of the form:
# 					  console.error.subtypes.tool-name.language-name
# 					  console.warning.subtypes.tool-name.language-name
# 					  console.notice.subtypes.tool-name.language-name
# 				]
# 			},
# 			...
# 		],
# 		"children": [ (optional)
# 			Allows nesting of diagnostic messages. Child messages are displayed
# 			when their parent is clicked. They may be located in other files.
# 		]
# 	},
# 	...
# ]

import sys
import re
import json
import os
text = sys.stdin.read()
result = []
def makeLocation(span):
	try: return {"offset":len(text[:span[0]].encode('utf-8')), "length":len(text[span[0]:span[1]].encode('utf-8')), "byteOffsets":True}
	except: return {"offset":span[0], "length":span[1]-span[0]}
fileRegex = r'^\s+File (?P<location>"(?P<file>.*?)".*?(?P<line>\d+))(, in (?P<name>\w+))?'
for match in re.finditer(r'(?P<stacktrace>(?P<traceback>Traceback.*)?[\r\n]*(?P<stack>'+fileRegex+r'(.|\n)*?))^(?P<message>((?P<type>[A-Z]\w*): )?\S.*)', text, re.MULTILINE):
	diagnostic = {}
	
	children = []
	diagnostic["children"] = children
	
	stackMatches = []
	stackStart = match.start("stack")
	stackString = match.group("stack")
	for stackMatch in re.finditer(fileRegex, stackString, re.MULTILINE):
		if not stackMatch.group("file").startswith("<"):
			stackMatches.insert(0, stackMatch)
	deepest = stackMatches.pop(0)
	prevStackMatch = deepest
	stackMatches = stackMatches[0:100]
	for stackMatch in stackMatches:
		child = {}
		childMatchedLocations = []
		childSpan = stackMatch.span("location")
		childSpan = (childSpan[0]+stackStart, childSpan[1]+stackStart)
		
		childMatchedLocations.append({"location":makeLocation(childSpan), "link":""})
		child["matchedLocations"] = childMatchedLocations
		name = prevStackMatch.group("name")
		if name:
			child["message"] = "'"+name+"' called here"
		else:
			child["message"] = "Called from here"
		child["file"] = stackMatch.group("file")
		child["location"] = {"line":stackMatch.group("line")}
		children.append(child)
		children = []
		child["children"] = children
		prevStackMatch = stackMatch
	
	matchedLocations = []
	deepestSpan = deepest.span("location")
	deepestSpan = (deepestSpan[0]+stackStart, deepestSpan[1]+stackStart)
	type = match.group("type")
	if type: type = type.lower() + "."
	else: type = ""
	matchedLocations.append({"location":makeLocation(match.span("stacktrace")), "scopes":["console.notice.stacktrace.python"]})
	matchedLocations.append({"location":makeLocation(deepestSpan), "link":""})
	matchedLocations.append({"location":makeLocation(match.span("message")), "scopes":["console.error."+type+"python"]})
	diagnostic["location"] = {"line":deepest.group("line")}
	diagnostic["matchedLocations"] = matchedLocations
	diagnostic["message"] = match.group("message")
	diagnostic["file"] = deepest.group("file")
	if match.group("traceback") and not "CR_DIAGNOSTICS_COMMAND" in os.environ:
		diagnostic["type"] = "runtime_error"
	else:
		diagnostic["type"] = "error"
	result.append(diagnostic)
json.dump(result, sys.stdout)
