Class: Qrpm::Compiler

Inherits:
Object
  • Object
show all
Defined in:
lib/qrpm/compiler.rb

Overview

The main result data are #defs and #deps. #keys and #values are the parsed result of the keys and values of #deps and is used to interpolate strings when values of dependent variables are known

#defs is also partitioned into QRPM variables and directories

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(dict, system_dirs: true, defaults: true, srcdir: true) ⇒ Compiler

If :srcdir is true, a default $srcdir variable is prefixed to all local paths. This is the default. srcdir:false is only used when testing



31
32
33
34
35
36
37
38
39
40
# File 'lib/qrpm/compiler.rb', line 31

def initialize(dict, system_dirs: true, defaults: true, srcdir: true)
  constrain dict, Hash
  @ast = nil
  @defs = {}
  @deps = {}
  @dict = system_dirs ? INSTALL_DIRS.merge(dict) : dict
  @use_system_dirs = system_dirs
  @use_defaults = defaults
  @use_srcdir = srcdir
end

Instance Attribute Details

#astObject (readonly)

Root node



11
12
13
# File 'lib/qrpm/compiler.rb', line 11

def ast
  @ast
end

#defaultsObject (readonly)

Defaults. Map from key to source expression



27
28
29
# File 'lib/qrpm/compiler.rb', line 27

def defaults
  @defaults
end

#defsObject (readonly)

Variable definitions. Map from path to Node



14
15
16
# File 'lib/qrpm/compiler.rb', line 14

def defs
  @defs
end

#depsObject (readonly)

Map from path to list of variables it depends on. Paths with no dependencies have an empty list as value



18
19
20
# File 'lib/qrpm/compiler.rb', line 18

def deps
  @deps
end

#dictObject (readonly)

Dictionary. The dictionary object are compiled into the AST before it is evaluated so the dictionary elements can be refered to with the usual $var notation. #dict is automatically augmented with the default system directory definitions unless :system_dirs is false



24
25
26
# File 'lib/qrpm/compiler.rb', line 24

def dict
  @dict
end

Instance Method Details

#analyze(check_undefined: true, check_mandatory: true, check_field_types: true, check_directory_types: true) ⇒ Object

Analyze and decorate the AST tree. Returns the AST

The various check_* arguments are only used while testing to allow illegal but short expressions by suppressing specified checks in #analyze. The default is to apply all checks



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/qrpm/compiler.rb', line 86

def analyze(
    check_undefined: true, 
    check_mandatory: true, 
    check_field_types: true, 
    check_directory_types: true)

  # Set package/system directory of standard directories depending on the
  # number of files in the directory
  dirs = @ast.values.select { |n| n.is_a? DirectoryNode }
  freq = dirs.map { |d| (d.key_variables & STANDARD_DIRS) * dirs.size }.flatten.tally
  freq.each { |name, count|
    node = @defs[name]
    if count == 1
      node.setsys
    else
      node.setpck
    end
  }

  # Collect definitions and dependencies
  ast.values.each { |node| collect_variables(node) }

  # Detect undefined variables and references to hashes or arrays
  if check_undefined
    deps.each { |path, path_deps|
      path_deps.each { |dep|
        if !defs.key?(dep)
          error "Undefined variable '#{dep}' in definition of '#{path}'"
        elsif !defs[dep].is_a?(ValueNode)
          error "Can't reference non-variable '#{dep}' in definition of '#{path}'"
        end
      }
    }
  end

  # Check for mandatory variables
  if check_mandatory
    missing = MANDATORY_FIELDS.select { |f| @defs[f].to_s.empty? }
    missing.empty? or error "Missing mandatory fields '#{missing.join("', '")}'"
  end

  # Check types of built-in variables
  if check_field_types
    FIELDS.each { |f,ts|
      ast.key?(f) or next
      ts.any? { |t| ast[f].class <= t } or error "Illegal type of field '#{f}'"
    }
  end

  @ast
end

#compile(yaml) ⇒ Object

Compile YAML into a Qrpm object



139
140
141
142
143
# File 'lib/qrpm/compiler.rb', line 139

def compile(yaml)
  parse(yaml)
  analyze
  Qrpm.new(defs, deps)
end

#dumpObject



145
146
147
148
149
150
# File 'lib/qrpm/compiler.rb', line 145

def dump
  puts "Defs"
  indent { defs.each { |k,v| puts "#{k}: #{v.signature}" if v.interpolated? }}
  puts "Deps"
  indent { deps.each { |k,v| puts "#{k}: #{v.join(", ")}" if !v.empty? } }
end

#parse(yaml) ⇒ Object

Parse the YAML source to an AST of Node objects and assign it to #ast. Returns the AST



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/qrpm/compiler.rb', line 44

def parse(yaml)
  # Root node
  @ast = RootNode.new

  # Compile standard directories. Also enter them into @defs so that #analyze
  # can access them by name before @defs is built by #collect_variables
  STANDARD_DIRS.each { |name| @defs[name] = StandardDirNode.new(@ast, name) } if @use_system_dirs

  # Parse yaml node
  yaml.each { |key, value|
    builtin_array = FIELDS[key]&.include?(ArrayNode) || false
    case [key.to_s, value, builtin_array]
      in [/[\/\$]/, _, _]; parse_directory_node(@ast, key, value)
      in [/^#{PATH_RE}$/, String, true]; parse_node(@ast, key, [value])
      in [/^#{PATH_RE}$/, Array, false]; parse_directory_node(@ast, key, value)
      in [/^#{PATH_RE}$/, _, _]; parse_node(@ast, key, value)
    else
      error "Illegal key: #{key.inspect}"
    end
  }

  # Compile and add dictionary to the AST. This allows command line
  # assignments to override spec file values
  dict.each { |k,v| ValueNode.new(ast, k.to_s, Fragment::Fragment.parse(v)) }

  # Add defaults
  DEFAULTS.each { |k,v|
    next if k == "srcdir" # Special handling of $srcdir below
    parse_node(@ast, k, v) if !@ast.key?(k)
  } if @use_defaults

  # Only add a default $srcdir node when :srcdir is true
  parse_node(@ast, "srcdir", DEFAULTS["srcdir"]) if @use_srcdir && !@ast.key?("srcdir")

  @ast
end