Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ task :compile, [:compiler, :target] do |t, args|
when "python"
require "interscript/compiler/python"
[Interscript::Compiler::Python, "py"]
when "json_ir"
require "interscript/compiler/json_ir"
[Interscript::Compiler::JsonIR, "json"]
end

FileUtils.mkdir_p(args[:target])
Expand Down
22 changes: 11 additions & 11 deletions lib/interscript.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
require "interscript/version"
require "yaml"

module Interscript
# Autoload all internal library code. No require_relative or internal
# require calls — everything loads lazily via autoload (OCP: adding a
# new child module = adding one autoload line here).
autoload :VERSION, "interscript/version"
autoload :Stdlib, "interscript/stdlib"
autoload :Compiler, "interscript/compiler"
autoload :Interpreter, "interscript/interpreter"
autoload :DSL, "interscript/dsl"
autoload :Node, "interscript/node"
autoload :Detector, "interscript/detector"

# An error caused by a lack of some map
class MapNotFoundError < StandardError; end
# An error caused by a missing dependency
Expand Down Expand Up @@ -185,13 +195,3 @@ def exclude_maps(maps, compiler:, platform: true)
end
end
end

require "interscript/stdlib"

require "interscript/compiler"
require "interscript/interpreter"

require "interscript/dsl"
require "interscript/node"

require "interscript/detector"
6 changes: 6 additions & 0 deletions lib/interscript/compiler.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# An Interscript compiler interface
class Interscript::Compiler
# Autoload all compiler variants (OCP: new compiler = one autoload).
autoload :Javascript, "interscript/compiler/javascript"
autoload :Python, "interscript/compiler/python"
autoload :Ruby, "interscript/compiler/ruby"
autoload :JsonIR, "interscript/compiler/json_ir"

attr_accessor :code

def self.call(map, **kwargs)
Expand Down
243 changes: 243 additions & 0 deletions lib/interscript/compiler/json_ir.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
# frozen_string_literal: true

require "json"

# JSON IR compiler — emits a data-only intermediate representation consumable
# by non-Ruby runtimes (e.g. interscript-ts). Unlike Compiler::Javascript,
# which emits imperative JS that calls into a runtime, this emits a pure-data
# JSON document describing the AST.
#
# The shape is versioned via SCHEMA_VERSION. Runtimes should refuse to load
# IR with an unknown schema.
class Interscript::Compiler::JsonIR < Interscript::Compiler
SCHEMA_VERSION = 1

CompiledResult = Struct.new(:code) do
# JsonIR output is already JSON. The runtime treats .code as opaque
# text; consumers parse via JSON.parse.
def to_json(*)
code
end
end

def compile(map, debug: false)
@map = map
@c = JSON.pretty_generate(serialise_document(map))
self
end

def code
@c
end

private

def serialise_document(doc)
# Merge aliases from the document AND from dependency documents so
# that consumers (interscript-ts) can resolve every alias reference
# without re-implementing the Ruby dep_aliases indirection.
all_aliases = {}

# Walk dependencies and merge their alias definitions.
# posix defines :upper, :lower; unicode defines :combining marks; etc.
doc.dependencies.each do |dep|
next unless dep.document
dep.document.aliases.each do |aname, defn|
all_aliases[aname.to_s] ||= serialise_item(defn.data)
end
end

# Also walk dep_aliases (for run-rule dependency resolution paths).
doc.dep_aliases.each_value do |dep|
next unless dep.document
dep.document.aliases.each do |aname, defn|
all_aliases[aname.to_s] ||= serialise_item(defn.data)
end
end

# Also merge ALL library aliases unconditionally. Libraries (posix,
# unicode, var-Cyrl, var-kor) define character classes that maps
# reference via alias() without listing the library as an explicit
# dependency in the dependency list.
Interscript.maps(libraries: true).each do |lib|
begin
libdoc = Interscript.parse(lib)
libdoc.aliases.each do |aname, defn|
all_aliases[aname.to_s] ||= serialise_item(defn.data)
end
rescue
# skip unparseable libraries
end
end

# Document's own aliases override everything.
doc.aliases.each do |name, defn|
all_aliases[name.to_s] = serialise_item(defn.data)
end

{
schemaVersion: SCHEMA_VERSION,
systemCode: doc.name.to_s,
dependencies: doc.dependencies.map(&:full_name),
metadata: serialise_metadata(doc.metadata),
stages: serialise_stages(doc.stages),
aliases: all_aliases,
functions: {}
}
end

def serialise_metadata(metadata)
return {} unless metadata
out = {}
metadata.data.each do |k, v|
out[k.to_s] = case v
when Symbol then v.to_s
else v
end
end
out
end

def serialise_stages(stages)
# Document#stages returns Hash{name => Stage} for the document's own stages,
# but may also include imported stages. Iterate values and skip anything
# that isn't an Interscript::Node::Stage (e.g. imported Hash entries).
stages.values.map { |s| serialise_stage(s) if s.is_a?(Interscript::Node::Stage) }.compact
end

def serialise_stage(stage)
{
kind: "stage",
name: stage.name.to_s,
rules: stage.children.map { |r| serialise_rule(r) }
}
end

def serialise_rule(rule)
case rule
when Interscript::Node::Rule::Sub
serialise_sub_rule(rule)
when Interscript::Node::Rule::Run
serialise_run_rule(rule)
when Interscript::Node::Rule::Funcall
serialise_funcall_rule(rule)
when Interscript::Node::Group::Parallel
# Parallel rule groups contain sub-rules that are applied simultaneously.
# In IR, we emit them as a marker rule so the runtime can decide.
{
kind: "parallel",
rules: rule.children.map { |r| serialise_rule(r) }
}
when Interscript::Node::Group::Sequential
{
kind: "sequential",
rules: rule.children.map { |r| serialise_rule(r) }
}
else
raise Interscript::MapLogicError, "Cannot serialise rule of type #{rule.class}"
end
end

def serialise_sub_rule(rule)
out = {kind: "sub"}
out[:from] = serialise_item(rule.from) if rule.from
out[:to] = serialise_to(rule.to)
out[:before] = serialise_item(rule.before) if rule.before
out[:after] = serialise_item(rule.after) if rule.after
out[:notBefore] = serialise_item(rule.not_before) if rule.not_before
out[:notAfter] = serialise_item(rule.not_after) if rule.not_after
out[:priority] = rule.priority if rule.priority
out
end

def serialise_run_rule(rule)
stage = rule.stage
doc_name = stage.map
if doc_name && @map.respond_to?(:dep_aliases) && @map.dep_aliases[doc_name.to_sym]
resolved = @map.dep_aliases[doc_name.to_sym].document
doc_name = resolved.name.to_s if resolved && resolved.respond_to?(:name)
end
{
kind: "run",
stage: stage.name.to_s,
docName: doc_name&.to_s
}
end

def serialise_funcall_rule(rule)
{
kind: "funcall",
name: rule.name.to_s,
kwargs: symbolise_keys(rule.kwargs)
}
end

def serialise_to(to)
case to
when Symbol
{kind: "funcall_inline", name: to.to_s}
else
serialise_item(to)
end
end

def serialise_item(item)
case item
when Interscript::Node::Item::String
{kind: "string", value: item.data}
when Interscript::Node::Item::CaptureGroup
{kind: "capture_group", data: serialise_item(item.data)}
when Interscript::Node::Item::CaptureRef
{kind: "capture_ref", id: item.id}
when Interscript::Node::Item::Alias
out = {kind: "alias", name: item.name.to_s}
out[:map] = item.map if item.map
out
when Interscript::Node::Item::Any
# Any with a String payload becomes a character class in Ruby's
# regex compilation. Range payloads become `[first-last]`. Both
# must survive IR serialisation as char-class form, NOT expanded
# into alternatives — Ruby's String#succ expansion of "a".."ᖵ"
# produces nonsense like "zzz" and misses most of the BMP.
case item.value
when ::Range
{kind: "any_char_class", range: [item.value.first, item.value.last]}
when ::String
{kind: "any_char_class", chars: item.value.split("")}
else
data = item.data || []
{kind: "any", of: data.map { |i| serialise_item(i) }}
end
when Interscript::Node::Item::Group
{kind: "group", items: item.children.map { |i| serialise_item(i) }}
when Interscript::Node::Item::Repeat
{kind: "repeat", item: serialise_item(item.data), min: 0, max: nil}
when Interscript::Node::Item::Stage
{kind: "stage_ref", name: item.name.to_s}
when nil
nil
else
raise Interscript::MapLogicError, "Cannot serialise item of type #{item.class}"
end
end

def capture_index(_item)
# CaptureGroup index is determined by position in pattern compilation.
# Runtimes should treat as a placeholder; the actual index is computed
# during pattern compilation based on capture-group ordering.
0
end

def serialise_aliases(aliases)
out = {}
aliases.each do |name, defn|
out[name.to_s] = serialise_item(defn.data)
end
out
end

def symbolise_keys(hash)
return {} if hash.nil?
hash.transform_keys(&:to_s)
end
end
20 changes: 11 additions & 9 deletions lib/interscript/dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,14 @@ def self.parse(map_name, reverse: true)
end
end

require "interscript/dsl/symbol_mm"
require "interscript/dsl/items"

require "interscript/dsl/document"
require "interscript/dsl/group"
require "interscript/dsl/stage"
require "interscript/dsl/metadata"
require "interscript/dsl/tests"
require "interscript/dsl/aliases"
module Interscript::DSL
# Autoload all DSL modules (OCP: new DSL section = one autoload).
autoload :SymbolMM, "interscript/dsl/symbol_mm"
autoload :Items, "interscript/dsl/items"
autoload :Document, "interscript/dsl/document"
autoload :Group, "interscript/dsl/group"
autoload :Stage, "interscript/dsl/stage"
autoload :Metadata, "interscript/dsl/metadata"
autoload :Tests, "interscript/dsl/tests"
autoload :Aliases, "interscript/dsl/aliases"
end
4 changes: 3 additions & 1 deletion lib/interscript/dsl/group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,6 @@ def parallel(**kwargs, &block)
end
end

require "interscript/dsl/group/parallel"
class Interscript::DSL::Group
autoload :Parallel, "interscript/dsl/group/parallel"
end
24 changes: 12 additions & 12 deletions lib/interscript/node.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
class Interscript::Node
# Autoload all node types. Adding a new node type = one autoload
# line here. No require_relative (OCP).
autoload :Group, "interscript/node/group"
autoload :Document, "interscript/node/document"
autoload :MetaData, "interscript/node/metadata"
autoload :AliasDef, "interscript/node/alias_def"
autoload :Dependency, "interscript/node/dependency"
autoload :Tests, "interscript/node/tests"
autoload :Stage, "interscript/node/stage"
autoload :Rule, "interscript/node/rule"
autoload :Item, "interscript/node/item"

def initialize
raise NotImplementedError, "You can't construct a Node directly"
end
Expand All @@ -12,15 +24,3 @@ def to_hash
question: "is something missing?"}
end
end

require "interscript/node/group"
require "interscript/node/document"

require "interscript/node/metadata"
require "interscript/node/alias_def"
require "interscript/node/dependency"
require "interscript/node/tests"

require "interscript/node/stage"
require "interscript/node/rule"
require "interscript/node/item"
6 changes: 4 additions & 2 deletions lib/interscript/node/group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,7 @@ def inspect
end
end

require "interscript/node/group/parallel"
require "interscript/node/group/sequential"
class Interscript::Node::Group
autoload :Parallel, "interscript/node/group/parallel"
autoload :Sequential, "interscript/node/group/sequential"
end
22 changes: 15 additions & 7 deletions lib/interscript/node/item.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,18 @@ def self.try_convert(i)
end
end

require "interscript/node/item/alias"
require "interscript/node/item/string"
require "interscript/node/item/group"
require "interscript/node/item/any"
require "interscript/node/item/stage"
require "interscript/node/item/capture"
require "interscript/node/item/repeat"
class Interscript::Node::Item
# Autoload all item types (OCP: new item type = one autoload).
autoload :Alias, "interscript/node/item/alias"
autoload :String, "interscript/node/item/string"
autoload :Group, "interscript/node/item/group"
autoload :Any, "interscript/node/item/any"
autoload :Stage, "interscript/node/item/stage"
autoload :CaptureGroup, "interscript/node/item/capture"
autoload :Repeat, "interscript/node/item/repeat"
# Maybe, MaybeSome, Some are subclasses of Repeat defined in
# the same file. Autoload points to repeat.rb which defines all of them.
autoload :Maybe, "interscript/node/item/repeat"
autoload :MaybeSome, "interscript/node/item/repeat"
autoload :Some, "interscript/node/item/repeat"
end
Loading
Loading