Skip to content

Commit

Permalink
Showing 7 changed files with 195 additions and 160 deletions.
17 changes: 17 additions & 0 deletions src/compiler/crystal/macros/macros.cr
Original file line number Diff line number Diff line change
@@ -8,6 +8,23 @@ module Crystal
end

class Program
# Temporary files which are generated by macro runs that need to be
# deleted after the compilation is finished.
getter tempfiles = [] of String

# Returns a new temporary file, which tries to be stored in the
# cache directory associated to a program. This file is then added
# to `tempfiles` so they can eventually be deleted.
def new_tempfile(basename)
filename = if cache_dir = @cache_dir
File.join(cache_dir, basename)
else
Crystal.tempfile(basename)
end
tempfiles << filename
filename
end

def expand_macro(a_macro : Macro, call : Call, scope : Type, type_lookup : Type?)
macro_expander.expand a_macro, call, scope, type_lookup || scope
end
209 changes: 121 additions & 88 deletions src/compiler/crystal/program.cr
Original file line number Diff line number Diff line change
@@ -2,66 +2,114 @@ require "llvm"
require "./types"

module Crystal
# A program contains all types and top-level methods related to one
# compilation of a program.
#
# It also carries around all information needed to compile a bunch
# of files: the unions, the symbols used, all global variables,
# all required files, etc. Because of this, a Program is usually passed
# around in every step of a compilation to record and query this information.
#
# In a way, a Program is an alternative implementation to having global variables
# for all of this data, but modelled this way one can easily test and exercise
# programs because each one has its own definition of the types created,
# methods instantiated, etc.
#
# Additionally, a Program acts as a regular type (a module) that can have
# types (the top-level types) and methods (the top-level methods), and which
# can also include other modules (this happens when you do `include Module`
# at the top-level).
#
# TODO: Because it's the top-level module, some parts of the code use `@mod`
# to denote the program, but these ocurrences should be renamed to just `@program`.
class Program < NonGenericModuleType
include DefContainer
include DefInstanceContainer
include MatchesLookup
include ClassVarContainer

getter! symbols : Set(String)
getter! global_vars : Hash(String, MetaTypeVar)
getter target_machine : LLVM::TargetMachine?
getter! splat_expansions : Hash(UInt64, Type)
getter! after_inference_types : Set(Type)
getter! file_modules : Hash(String, FileModule)
property! vars : MetaVars
property literal_expander : LiteralExpander?
property! initialized_global_vars : Set(String)
property? wants_doc : Bool?
property? color : Bool?

getter! requires : Set(String)
getter! temp_var_counter : Int32
getter! crystal_path : CrystalPath
getter! unions : Hash(Array(UInt64), Type)
getter! file_modules : Hash(String, FileModule)
getter! string_pool
@flags : Set(String)?

# All symbols (:foo, :bar) found in the program
getter symbols = Set(String).new

# All global variables in the program ($foo, $bar), indexed by their name.
# The names includes the `$` sign.
getter global_vars = {} of String => MetaTypeVar

# Hash that prevents recursive splat expansions. For example:
#
# ```
# def foo(*x)
# foo(x)
# end
#
# foo(1)
# ```
#
# Here x will be {Int32}, then {{Int32}}, etc.
#
# The way we detect this is by remembering the type of the splat,
# associated to a def's object id (the UInt64), and on an instantiation
# we compare the new type with the previous one and check if if contains
# the previous type.
getter splat_expansions = {} of UInt64 => Type

# All FileModules indexed by their filename.
# These store file-private defs, and top-level variables in files other
# than the main file.
getter file_modules = {} of String => FileModule

# Types that have instance vars initializers which need to be visited
# (transformed) by `CleanupTransformer` once the semantic analysis finishes.
#
# TODO: this probably isn't needed and we can just traverse all types at the
# end, and analyze all instance variables initializers that we found. This
# should simplify a bit of code.
getter after_inference_types = Set(Type).new

# Top-level variables found in a program (only in the main file).
getter vars = MetaVars.new

# If `true`, doc comments are attached to types and methods.
property? wants_doc = false

# If `true`, error messages can be colorized
property? color = true

# All required files. The set stores absolute files. This way
# files loaded by `require` nodes are only processed once.
getter requires = Set(String).new

# All created unions in a program, indexed by an array of opaque
# ids of each type in the union. The array (the key) is sorted
# by this opaque id.
#
# A program caches them this way so a union of `String | Int32`
# or `Int32 | String` is represented by a single, unique type
# in the program.
getter unions = {} of Array(UInt64) => UnionType

# A String pool to avoid creating the same strings over and over.
# This pool is passed to the parser, macro expander, etc.
getter string_pool = StringPool.new

# The cache directory where temporary files are placed.
setter cache_dir : String?

# Temporary files which are generated by macro runs that need to be
# deleted after the compilation is finished.
getter! tempfiles : Array(String)

# Here we store class var initializers and constants, in the
# order that they are used. They will be initialized as soon
# as the program starts, before the main code.
getter! class_var_and_const_initializers
getter class_var_and_const_initializers = [] of ClassVarInitializer | Const

# The constant for ARGC_UNSAFE
getter! argc : Const

# The constant for ARGV_UNSAFE
getter! argv : Const

def initialize
super(self, self, "main")

@symbols = Set(String).new
@global_vars = {} of String => MetaTypeVar
@requires = Set(String).new
@temp_var_counter = 0
@vars = MetaVars.new
@splat_expansions = {} of UInt64 => Type
@initialized_global_vars = Set(String).new
@file_modules = {} of String => FileModule
@unions = {} of Array(UInt64) => Type
@wants_doc = false
@color = true
@after_inference_types = Set(Type).new
@string_pool = StringPool.new
@class_var_and_const_initializers = [] of ClassVarInitializer | Const
@tempfiles = [] of String

# Every crystal program comes with some predefined types that we initialize here,
# like Object, Value, Reference, etc.
types = @types = {} of String => Type

types["Object"] = object = @object = NonGenericClassType.new self, self, "Object", nil
@@ -147,44 +195,48 @@ module Crystal
enum_t.allowed_in_generics = false

types["Proc"] = @proc = ProcType.new self, self, "Proc", value, ["T", "R"]

types["Union"] = @union = GenericUnionType.new self, self, "Union", value, ["T"]
types["Crystal"] = @crystal = NonGenericModuleType.new self, self, "Crystal"

types["Crystal"] = crystal_module = NonGenericModuleType.new self, self, "Crystal"

argc_primitive = Primitive.new(:argc)
argc_primitive.type = int32

argv_primitive = Primitive.new(:argv)
argv_primitive.type = pointer_of(pointer_of(uint8))

types["ARGC_UNSAFE"] = @argc = argc_unsafe = Const.new self, self, "ARGC_UNSAFE", argc_primitive
types["ARGV_UNSAFE"] = @argv = argv_unsafe = Const.new self, self, "ARGV_UNSAFE", argv_primitive
types["ARGC_UNSAFE"] = @argc = argc_unsafe = Const.new self, self, "ARGC_UNSAFE", Primitive.new(:argc, int32)
types["ARGV_UNSAFE"] = @argv = argv_unsafe = Const.new self, self, "ARGV_UNSAFE", Primitive.new(:argv, pointer_of(pointer_of(uint8)))

# Make sure to initialize ARGC and ARGV as soon as the program starts
class_var_and_const_initializers << argc_unsafe
class_var_and_const_initializers << argv_unsafe

argc_unsafe.initialized = true
argv_unsafe.initialized = true

types["GC"] = gc = NonGenericModuleType.new self, self, "GC"
gc.metaclass.add_def Def.new("add_finalizer", [Arg.new("object")], Nop.new)

@literal_expander = LiteralExpander.new self
@macro_expander = MacroExpander.new self
@nil_var = Var.new("<nil_var>", nil_t)

define_crystal_constants
end

private def crystal_path
# Returns a `LiteralExpander` useful to expand literal like arrays and hashes
# into simpler forms.
def literal_expander
@literal_expander ||= LiteralExpander.new self
end

# Returns a `CrystalPath` for this program.
def crystal_path
@crystal_path ||= CrystalPath.new(target_triple: target_machine.triple)
end

private def define_crystal_constants
types["Crystal"] = @crystal = crystal = NonGenericModuleType.new self, self, "Crystal"
# Returns a `MacroExpander` to expand macro code into crystal code.
def macro_expander
@macro_expander ||= MacroExpander.new self
end

# Returns a `Var` that has `Nil` as a type.
# This variable is bound to other nodes in the semantic phase for things
# that need to be nilable, for example to a variable that's only declared
# in one branch of an `if` expression.
def nil_var
@nil_var ||= Var.new("<nil_var>", nil_type)
end

# Defines a predefined constant in the Crystal module, such as BUILD_DATE and VERSION.
private def define_crystal_constants
version, sha = Crystal::Config.version_and_sha

if sha
@@ -210,18 +262,7 @@ module Crystal
end

private def define_crystal_constant(name, value)
crystal.types[name] = const = Const.new self, crystal, name, value
const.initialized = true
end

def new_tempfile(basename)
filename = if cache_dir = @cache_dir
File.join(cache_dir, basename)
else
Crystal.tempfile(basename)
end
tempfiles << filename
filename
crystal.types[name] = Const.new self, crystal, name, value
end

def add_def(node : Def)
@@ -264,7 +305,7 @@ module Crystal
file_module(filename)
end

setter target_machine
setter target_machine : LLVM::TargetMachine?

def target_machine
@target_machine ||= TargetMachine.create(LLVM.default_target_triple, "", false)
@@ -449,9 +490,6 @@ module Crystal
end
end

getter! literal_expander
getter! macro_expander

def struct
@struct_t.not_nil!
end
@@ -460,12 +498,6 @@ module Crystal
@class.not_nil!
end

getter! :nil_var

def uint8_pointer
pointer_of uint8
end

def pointer_of(type)
pointer.instantiate([type] of TypeVar)
end
@@ -478,10 +510,11 @@ module Crystal
Var.new(new_temp_var_name)
end

@temp_var_counter = 0

def new_temp_var_name
counter = temp_var_counter + 1
@temp_var_counter = counter
"__temp_#{counter}"
@temp_var_counter += 1
"__temp_#{@temp_var_counter}"
end

def type_desc
17 changes: 0 additions & 17 deletions src/compiler/crystal/semantic/cleanup_transformer.cr
Original file line number Diff line number Diff line change
@@ -220,14 +220,9 @@ module Crystal

if target.is_a?(Path)
const = const.not_nil!
const.initialized = true
const.value = const.value.transform self
end

if target.is_a?(Global)
@program.initialized_global_vars.add target.name
end

if node.target == node.value
node.raise "expression has no effect"
end
@@ -251,18 +246,6 @@ module Crystal
node
end

def transform(node : EnumDef)
super

if node.created_new_type
node.resolved_type.types.each_value do |const|
const.as(Const).initialized = true
end
end

node
end

def transform(node : Call)
if expanded = node.expanded
return expanded.transform self
Loading

0 comments on commit 4f55e90

Please sign in to comment.