Specific actions to execute on annotated nodes

Note that the order of the visit is the one of the file @toimplement

Property definitions

nitc $ Phase :: process_annotated_node
	# Specific actions to execute on annotated nodes
	# Note that the order of the visit is the one of the file
	# @toimplement
	fun process_annotated_node(node: ANode, nat: AAnnotation) do end
src/phase.nit:254,2--257,65

nitc $ PkgconfigPhase :: process_annotated_node
	redef fun process_annotated_node(nmoduledecl, nat)
	do
		# Skip if we are not interested
		if nat.name != "pkgconfig" then return

		# Do some validity checks and print errors if the annotation is used incorrectly
		var modelbuilder = toolcontext.modelbuilder

		if not nmoduledecl isa AModuledecl then
			modelbuilder.error(nat, "Syntax Error: only the declaration of modules may use `pkgconfig`.")
			return
		end

		# retrieve module
		var nmodule = nmoduledecl.parent.as(AModule)
		var mmodule = nmodule.mmodule.as(not null)

		# target pkgs
		var pkgs = new Array[String]

		var args = nat.n_args
		if args.is_empty then
			# use module name
			pkgs.add(mmodule.name)
		else
			for arg in args do
				var pkg = arg.as_string
				if pkg == null then
					modelbuilder.error(nat, "Syntax Error: `pkgconfig` expects its arguments to be the name of the package as String literals.")
					return
				end

				pkgs.add(pkg)
			end
		end

		for pkg in pkgs do
			mmodule.pkgconfigs.add pkg
		end
	end
src/ffi/pkgconfig.nit:70,2--109,4

nitc $ CCompilerOptionsPhase :: process_annotated_node
	redef fun process_annotated_node(nmoduledecl, nat)
	do
		# Skip if we are not interested
		var annotation_name = nat.name
		if annotation_name != compiler_annotation_name and
		   annotation_name != linker_annotation_name and
		   annotation_name != cpp_compiler_annotation_name then return

		# Do some validity checks and print errors if the annotation is used incorrectly
		var modelbuilder = toolcontext.modelbuilder

		if not nmoduledecl isa AModuledecl then
			modelbuilder.error(nat, "Syntax Error: only the declaration of modules may use `{annotation_name}`.")
			return
		end

		var args = nat.n_args
		if args.is_empty then
			modelbuilder.error(nat, "Syntax Error: `{annotation_name}` expects at least one argument.")
			return
		end

		var options = new Array[CCompilerOption]
		for expr in args do
			if expr isa AStringFormExpr then
				var text = expr.collect_text
				text = text.substring(1, text.length-2)
				var opt = new DirectCCompilerOption(text)
				options.add(opt)
			else if expr isa ACallExpr then
				# We support calls to "exec" only
				var exec_args = expr.n_args.to_a
				if expr.n_qid.n_id.text != "exec" or exec_args.is_empty then
					modelbuilder.error(nat, "Syntax Error: `{annotation_name}` accepts only calls to `exec` with the command as arguments.")
					return
				end

				var exec_args_as_strings = new Array[String]
				for exec_arg in exec_args do
					if not exec_arg isa AStringFormExpr then
						modelbuilder.error(nat, "Syntax Error: calls to `exec` expects the arguments to be String literals.")
						return
					else
						var arg_string = exec_arg.collect_text
						arg_string = arg_string.substring(1, arg_string.length-2)
						exec_args_as_strings.add(arg_string)
					end
				end

				var opt = new ExecCCompilerOption(exec_args_as_strings, expr)
				options.add(opt)
			else
				modelbuilder.error(nat, "Syntax Error: `{annotation_name}` expects its arguments to be the name of the package as String literals.")
				return
			end
		end

		# process calls to external command
		var simplified_options = new Array[DirectCCompilerOption]
		for opt in options do
			if opt isa ExecCCompilerOption then
				# prepare to execute command
				var cmd_args = opt.command
				var proc
				if cmd_args.length == 1 then
					proc = new ProcessReader.from_a(cmd_args[0], new Array[String])
				else if cmd_args.length > 1 then
					var rest_args = cmd_args.subarray(1, cmd_args.length-1)
					proc = new ProcessReader.from_a(cmd_args[0], rest_args)
				else abort

				# wait for its completion
				proc.wait

				# check result
				var status = proc.status
				if status != 0 then
					modelbuilder.error(opt.exec_node, "Error: something went wrong when executing the argument of `{annotation_name}`, make sure the command is valid.")
					return
				end

				# process result
				var result = proc.read_all.replace("\n", " ")
				if result.is_empty then
					modelbuilder.error(opt.exec_node, "Error: got no result from the command, make sure it is valid.")
					return
				end
				simplified_options.add(new DirectCCompilerOption(result))
			else
				assert opt isa DirectCCompilerOption
				simplified_options.add(opt)
			end
		end

		# Retrieve module
		var mmodule = nmoduledecl.parent.as(AModule).mmodule.as(not null)

		# Get target platform from annotation on annotation
		var platform = ""

		## Is there an imported platform?
		var target_platform = mmodule.target_platform
		if target_platform != null then
			platform = target_platform.name or else ""
		end

		## Is the platform declared explicitly?
		var annots = nat.n_annotations
		if annots != null then
			var items = annots.n_items
			if items.length > 1 then
				modelbuilder.error(annots, "Syntax Error: `{annotation_name}` accepts only a single annotation, the platform name.")
				return
			end
			assert items.length == 1

			var item = items.first
			platform = item.name
		end

		# Store the flags in the module
		for opt in simplified_options do
			var arg = opt.option
			if annotation_name == compiler_annotation_name then
				mmodule.cflags.add_one(platform, arg)
			else if annotation_name == linker_annotation_name then
				mmodule.ldflags.add_one(platform, arg)
			else if annotation_name == cpp_compiler_annotation_name then
				mmodule.cppflags.add_one(platform, arg)
			else abort
		end
	end
src/ffi/c_compiler_options.nit:39,2--170,4

nitc $ PlatformPhase :: process_annotated_node
	redef fun process_annotated_node(nmoduledecl, nat)
	do
		var annotation_name = "platform"

		# Skip if we are not interested
		if nat.name != annotation_name then return

		# Do some validity checks and print errors if the annotation is used incorrectly
		var modelbuilder = toolcontext.modelbuilder
		if not nmoduledecl isa AModuledecl then
			modelbuilder.error(nat, "Syntax Error: only the declaration of modules may use `{annotation_name}`.")
			return
		end

		var args = nat.n_args
		var platform_name
		if args.length > 1 then
			modelbuilder.error(nat, "Syntax Error: `{annotation_name}` expects at most a single argument.")
			return
		else if args.is_empty then
			platform_name = nmoduledecl.n_name.collect_text
		else
			platform_name = args.first.as_string
			if platform_name == null then
				var format_error = "Syntax Error: `{annotation_name}` expects its argument to be the name of the target platform as a String literal."
				modelbuilder.error(nat, format_error)
				return
			end
		end

		var nmodule = nmoduledecl.parent.as(AModule)
		var mmodule = nmodule.mmodule

		var platform = toolcontext.platform_from_name(platform_name)
		if platform == null then
			toolcontext.error(nat.location, "Error: target platform `{platform_name}` unknown.")
			return
		end

		var previous_target_platform = mmodule.target_platform
		if previous_target_platform != null and previous_target_platform != platform then
			modelbuilder.error(nat, "Syntax Error: a target platform has already been defined as `{previous_target_platform}`.")
		end

		mmodule.local_target_platform = platform
	end
src/platform/platform.nit:39,2--84,4

nitc $ JavaExtraFilesPhase :: process_annotated_node
	redef fun process_annotated_node(nmoduledecl, nat)
	do
		# Skip if we are not interested
		var annot_name = "extra_java_files"
		if nat.name != annot_name then return

		# Do some validity checks and print errors if the annotation is used incorrectly
		var modelbuilder = toolcontext.modelbuilder

		if not nmoduledecl isa AModuledecl then
			modelbuilder.error(nat, "Syntax Error: only the declaration of modules may use `{annot_name}`.")
			return
		end

		var args = nat.n_args
		if args.is_empty then
			modelbuilder.error(nat, "Syntax Error: `{annot_name}` expects at least one argument.")
			return
		end

		# retrieve module
		var nmodule = nmoduledecl.parent.as(AModule)
		var mmodule = nmodule.mmodule.as(not null)
		var java_files = mmodule.extra_java_files
		if java_files == null then
			java_files = new Array[ExtraJavaFile]
			mmodule.extra_java_files = java_files
		end

		var format_error = "Syntax Error: `{annot_name}` expects its arguments to be paths to java files."
		for arg in args do
			var name = arg.as_string
			if name == null or name.is_empty then
				modelbuilder.error(arg, format_error)
				return
			end

			var path = name.split(".").last + ".java"

			# Append specified path to directory of the Nit source file
			var source_file = nat.location.file
			if source_file != null then path = "{source_file.filename.dirname}/{path}"

			if not path.file_exists then
				modelbuilder.error(nat, "FFI with Java Error: file `{path}` not found.")
				continue
			end

			var file = new ExtraJavaFile(name, path)
			mmodule.ffi_files.add file
			java_files.add file
		end
	end
src/ffi/extra_java_files.nit:38,2--90,4

nitc $ SerializationPhaseRename :: process_annotated_node
	redef fun process_annotated_node(node, nat)
	do
		var text = nat.n_atid.n_id.text
		if text != "serialize_as" then return

		if not node isa AAttrPropdef then
			toolcontext.error(node.location,
				"Syntax Error: annotation `{text}` applies only to attributes.")
			return
		end

		# Can't use `arg_as_string` because it needs the literal phase
		var args = nat.n_args
		if args.length != 1 or not args.first isa AStringFormExpr then
			toolcontext.error(node.location,
				"Syntax Error: annotation `{text}` expects a single string literal as argument.")
			return
		end

		var t = args.first.collect_text
		var val = t.substring(1, t.length-2)
		node.serialize_name = val
	end
src/frontend/serialization_model_phase.nit:60,2--82,4

nitc $ SerializationPhasePreModel :: process_annotated_node
	redef fun process_annotated_node(node, nat)
	do
		# Skip if we are not interested
		var text = nat.n_atid.n_id.text
		var serialize = text == "auto_serializable" or text == "serialize"
		var noserialize = text == "noserialize"
		if not (serialize or noserialize) then return

		# Check legality of annotation
		if node isa AModuledecl then
			if noserialize then toolcontext.error(node.location, "Syntax Error: superfluous use of `{text}`, by default a module is `{text}`")
			return
		else if not (node isa AStdClassdef or node isa AAttrPropdef) then
			toolcontext.error(node.location,
				"Syntax Error: only a class, a module or an attribute can be annotated with `{text}`.")
			return
		else if serialize and node.is_noserialize then
			toolcontext.error(node.location,
				"Syntax Error: an entity cannot be both `{text}` and `noserialize`.")
			return
		else if node.as(Prod).get_annotations(text).length > 1 then
			toolcontext.warning(node.location, "useless-{text}",
				"Warning: duplicated annotation `{text}`.")
		end

		# Check the `serialize` state of the parent
		if not node isa AModuledecl then
			var up_serialize = false
			var up: nullable ANode = node
			while up != null do
				up = up.parent
				if up == null then
					break
				else if up.is_serialize then
					up_serialize = true
					break
				else if up.is_noserialize then
					break
				end
			end

			# Check for useless double declarations
			if serialize and up_serialize then
				toolcontext.warning(node.location, "useless-serialize",
					"Warning: superfluous use of `{text}`.")
			else if noserialize and not up_serialize then
				toolcontext.warning(node.location, "useless-noserialize",
					"Warning: superfluous use of `{text}`.")
			end
		end
	end
src/frontend/serialization_model_phase.nit:88,2--138,4

nitc $ DerivingPhase :: process_annotated_node
	redef fun process_annotated_node(nclassdef, nat)
	do
		if nat.name == "auto_inspect" then
			if not nclassdef isa AStdClassdef then
				toolcontext.error(nclassdef.location, "Syntax Error: only a concrete class can be `{nat.name}`.")
			else
				generate_inspect_method(nclassdef)
			end
		end

		if nat.name == "auto_derive" then
			if not nclassdef isa AStdClassdef then
				toolcontext.error(nclassdef.location, "Syntax Error: only a concrete class can be `{nat.name}`.")
			else
				generate_derive_to_map_method(nclassdef, nat)
			end
		end
	end
src/frontend/deriving.nit:36,2--53,4

nitc $ CheckAnnotationPhase :: process_annotated_node
	redef fun process_annotated_node(node, nat)
	do
		var name = nat.name
		if primtives_annotations.has(name) then return

		var mmodule = self.mmodule
		if mmodule == null then return

		# Lazily build the full user-list
		var annots = user_annotations.get_or_null(mmodule)
		if annots == null then
			annots = new HashSet[String]
			annots.add_all(declared_annotations.lookup_joined_values(mmodule, private_visibility))
			user_annotations[mmodule] = annots
		end
		#nat.debug "for {mmodule}: {annots.join(" ")}"

		if annots.has(name) then return

		toolcontext.modelbuilder.warning(nat, "unknown-annotation", "Warning: unknown annotation `{name}`.")

		annots.add(name) # to avoid multiple errors on the same name
	end
src/frontend/check_annotation.nit:126,2--148,4

nitc $ GLSLValidationPhase :: process_annotated_node
	redef fun process_annotated_node(nstring, nat)
	do
		var annot_name = nat.n_atid.n_id.text
		var is_vertex = annot_name == annot_name_vertex
		var is_fragment = annot_name == annot_name_fragment

		# Skip if we are not interested
		if not is_vertex and not is_fragment then return

		# Only applicable on strings
		if not nstring isa AStringFormExpr then
			toolcontext.error(nstring.location,
				"Syntax Error: only a string literal can be annotated as `{annot_name}`.")
			return
		end

		# Do not double check if tool is in path
		var in_path = tool_is_in_path
		if in_path == null then
			# Is _glslangValidator_ installed?
			var proc_which = new ProcessReader("which", "glslangValidator")
			proc_which.wait
			proc_which.close
			var status = proc_which.status
			in_path = status == 0
			tool_is_in_path = in_path
		end

		if not in_path then
			toolcontext.advice(nat.location, "glslvalidator",
				"Warning: program `glslangValidator` not in PATH, cannot validate this shader.")
			return
		end

		# Get the shader source
		var shader = nstring.value

		# Copy the shader to a file
		# TODO make it more portable
		var tmp = "/tmp/"
		var ext
		if is_vertex then
			ext = "vert"
		else ext = "frag"
		var path = tmp / "nit_shader." + ext

		shader.write_to_file path

		# Execute the validator
		var proc_validator = new ProcessReader("glslangValidator", path)
		proc_validator.wait
		var lines = proc_validator.read_all.split('\n')
		proc_validator.close

		# Parse errors
		var regex = "[A-Z]+: ([0-9]+):([0-9]+): (.*)".to_re
		for line in lines do
			var match = line.search(regex)

			# Does it match an error?
			# If not, then it should be the summary
			if match != null then
				var shader_line_no = match.subs[1].to_s.to_i
				var msg = match.subs[2].to_s

				var line_start = nstring.location.line_start + shader_line_no
				var char_start = 0
				var char_end = 0
				var loc = new Location(nat.location.file,
					line_start, line_start,
					char_start, char_end)

				toolcontext.warning(loc, "glslvalidator",
					"Shader error on {msg}")
			end
		end
	end
src/frontend/glsl_validation.nit:43,2--119,4

nitc $ ParallelizationPhase :: process_annotated_node
	redef fun process_annotated_node(nmethdef, nat)
	do
		if nat.n_atid.n_id.text != "threaded" then return

		if not nmethdef isa AMethPropdef then
			toolcontext.error(nat.location, "Syntax Error: only a method can be threaded.")
			return
		end

		#TODO: check for self calls

		# Get the module associated with this method
		var amod = nmethdef.parent.parent
		assert amod isa AModule

		# Construct the name of the generated class
		var classname = "Threaded"

		# Try to get the name of the class
		if nmethdef.parent isa AStdClassdef then
			classname += nmethdef.parent.as(AStdClassdef).n_qid.n_id.text
		end

		# Try to get the name of the method
		if nmethdef.n_methid isa AIdMethid then
			classname += nmethdef.n_methid.as(AIdMethid).n_id.text
		end

		# Handle methods with a return value
		var has_rvalue = nmethdef.n_signature.n_type != null
		var vtype = ""
		if has_rvalue then
			vtype = "redef type E: " + nmethdef.n_signature.n_type.n_qid.n_id.text
		end

		# create a return type
		var n_id = new TClassid
		n_id.text = classname
		var n_qid = new AQclassid
		n_qid.n_id = n_id
		var n_type = new AType
		n_type.n_qid = n_qid
		nmethdef.n_signature.n_type = n_type

		var params = new Array[String]
		for param in nmethdef.n_signature.n_params do
			var typ = param.n_type.n_qid.n_id.text
			if param.n_type.n_kwnullable != null then typ = "nullable {typ}"
			params.add """
var {{{param.n_id.text}}}: {{{typ}}}
"""
		end

		# String corresponding to the generated class
		var classdef_source = """
class {{{classname}}}
	super Thread

	{{{vtype}}}

	{{{params.join("\n")}}}
	redef fun main do
	end
end
"""

		# Parse newly obtained classdef
		var classdef = toolcontext.parse_classdef(classdef_source)
		assert classdef isa AStdClassdef

		# Get the `main` fun of the class
		var mainfun: nullable AMethPropdef = null
		for prop in classdef.n_propdefs do
			if prop isa AMethPropdef then mainfun = prop
		end
		assert mainfun != null

		# Make the statements from `main` fun be the statements from the "threaded" fun
		mainfun.n_block = nmethdef.n_block

		# Add "return null" to the end of the `main` function
		if not has_rvalue then
			var s_nullreturn = "return null"
			var nullreturn = toolcontext.parse_something(s_nullreturn)
			assert nullreturn isa AExpr
			mainfun.n_block.as(ABlockExpr).n_expr.add(nullreturn)
		end

		# Create new body for the annotated fun
		var s_newbody : String
		if nmethdef.n_signature.n_params.not_empty then
			var init_params = new Array[String]
			for param in nmethdef.n_signature.n_params do
				init_params.add(param.n_id.text)
			end
			s_newbody ="""
var thread = new {{{classname}}}({{{init_params.join(",")}}})
thread.start
return thread
"""
		else
			s_newbody = """
var thread = new {{{classname}}}
thread.start
return thread
"""
		end

		var newbody = toolcontext.parse_something(s_newbody)
		nmethdef.n_block = newbody.as(ABlockExpr)

		nmethdef.validate

		# Add the new class to the module
		amod.n_classdefs.add(classdef)
		classdef.validate
	end
src/frontend/parallelization_phase.nit:34,2--150,4

nitc $ I18NPhase :: process_annotated_node
	redef fun process_annotated_node(nmodule, nat) do
		if not nat.name == "i18n" then return

		if not nmodule isa AModuledecl then
			toolcontext.error(nmodule.location, "Error: The localized language can only be used on module declarations.")
			return
		end

		var domain = nmodule.n_name.n_id.text

		var lang: nullable String = null
		if nat.n_args.length > 0 then
			lang = nat.arg_as_string(toolcontext.modelbuilder)
			if lang == null then return
		end

		var module_dir = nmodule.location.file.filename.dirname.realpath
		var locale_dir = module_dir / "languages"

		if not locale_dir.file_exists then locale_dir.mkdir

		var amodule = nmodule.parent.as(AModule)

		var vi = new StringFinder(domain, locale_dir, toolcontext, amodule)
		vi.enter_visit(amodule)

		var module_name = nmodule.location.file.filename.basename(".nit")

		var pot_path = locale_dir / module_name
		var arr = vi.strings.values.to_a
		var po = new POFile(arr)
		po.write_template(pot_path)

		if lang != null then
			for i in po.strings do
				i.msgstr = i.msgid
			end

			var lang_dir = locale_dir / lang
			if not lang_dir.file_exists then lang_dir.mkdir

			var messages_dir = lang_dir / "LC_MESSAGES"
			if not messages_dir.file_exists then messages_dir.mkdir

			po.write_to_file(messages_dir / module_name)
		end

		var lit = new LiteralVisitor(toolcontext)
		lit.enter_visit(amodule)
	end
src/frontend/i18n_phase.nit:31,2--80,4

nitc $ ActorPhase :: process_annotated_node
	redef fun process_annotated_node(nclass, nat)
	do
		if nat.n_atid.n_id.text != "actor" then return

		if not nclass isa AStdClassdef then
			toolcontext.error(nat.location, "Syntax Error: only a class can be annotated as an actor.")
			return
		end
	end
src/frontend/actors_generation_phase.nit:221,2--229,4

nitc $ RestfulPhase :: process_annotated_node
	redef fun process_annotated_node(node, nat)
	do
		# Skip if we are not interested
		var text = nat.n_atid.n_id.text
		if text != "restful" then return

		if not node isa AMethPropdef then
			toolcontext.error(nat.location,
				"Syntax Error: `restful` can only be applied on method definitions")
			return
		end

		var mpropdef = node.mpropdef
		if mpropdef == null then return
		var mproperty = mpropdef.mproperty
		var mclassdef = mpropdef.mclassdef
		var mmodule = mclassdef.mmodule

		var http_resources = new Array[String]
		var http_methods = new Array[String]
		for arg in nat.n_args do
			var str = arg.as_string
			var id = arg.collect_text
			if str != null then
				# String -> rename resource
				http_resources.add str
			else if arg isa ATypeExpr and not id.chars.has("[") then
				# Class id -> HTTP method
				http_methods.add id
			else if id == "async" then
				mproperty.restful_async = true
			else
				toolcontext.error(nat.location,
					"Syntax Error: `restful` expects String literals or ids as arguments.")
				return
			end
		end

		# Test subclass of `RestfulAction`
		var sup_class_name = "RestfulAction"
		var sup_class = toolcontext.modelbuilder.try_get_mclass_by_name(
			nat, mmodule, sup_class_name)
		var in_hierarchy = mclassdef.in_hierarchy
		if in_hierarchy == null or sup_class == null then return
		var sup_classes = in_hierarchy.greaters
		if not sup_classes.has(sup_class.intro) then
			toolcontext.error(nat.location,
				"Syntax Error: `restful` is only valid within subclasses of `{sup_class_name}`")
			return
		end

		# Register the property
		mclassdef.restful_methods.add mproperty
		restful_classes.add mclassdef

		if http_resources.not_empty then mproperty.restful_resources = http_resources
		mproperty.restful_verbs = http_methods
	end
src/nitrestful.nit:28,2--85,4