ecma335

Version, currently 1.0.02 versions

github.com/mjblack/ecma335

A Crystal ECMA335 parser and tooling

0 stars
1 dependent
License: MIT

Installation

# Add this to your shard.yml
dependencies:
  ecma335:
    github: mjblack/ecma335
    version: ~> 1.0.0

Then run:

shards install

shard.yml

Crystal
>= 1.19.1
License
MIT
Author
Matthew J. Black
Target
  • ecma335-tool from src/ecma335_tool.cr

Dependencies

This version declares no dependencies.

README

This README is the one indexed from the repository at its latest ref, not from the tag for this version.

ecma335

ECMA 335 Parser

Installation

  1. Add the dependency to your shard.yml:

    dependencies:
      ecma335:
        github: mjblack/ecma335
  2. Run shards install

Usage

require "ecma335"

parsed = Ecma335.parse("winmd/Windows.Win32.winmd")

puts "Metadata version: #{parsed.metadata_root.version_string}"
parsed.metadata_root.streams.each do |stream|
  puts "#{stream.name}: offset=0x#{stream.offset.to_s(16)} size=#{stream.size}"
end

if tables = parsed.metadata_root.tables_stream
  puts "TypeDef rows: #{tables.row_count("TypeDef")}"
  puts "MethodDef rows: #{tables.row_count("MethodDef")}"
  puts "coverage: #{(tables.parse_coverage_ratio * 100).round(2)}%"
  p tables.skipped_tables

  tables.type_defs.first(5).each do |row|
    puts "TypeDef: #{row.type_namespace}.#{row.type_name}"
  end

  tables.method_defs.first(5).each do |row|
    puts "MethodDef: #{row.name} (rva=0x#{row.rva.to_s(16)})"
    if sig = row.decoded_signature
      params = sig.parameter_types.join(", ")
      puts "  signature: (#{params}) -> #{sig.return_type}"
      puts "  canonical: #{sig.to_signature_string(canonical: true)}"
    end
  end
end

Consumer API

This library is parser-focused. It does not generate Crystal bindings directly.

Ecma335.parse also builds a normalized API model suitable for a separate generator app:

parsed = Ecma335.parse("winmd/Windows.Win32.winmd")
api = parsed.api_model

if api
  type = api.type?("Windows.Win32.Foundation.HRESULT")
  p type.try(&.methods.size)
  p type.try(&.token)
  p type.try(&.generic_params)

  namespace_types = api.types_in_namespace("Windows.Win32.Foundation")
  puts "types in namespace: #{namespace_types.size}"

  method = api.method?("Windows.Win32.Foundation.HRESULT", "SomeMethod")
  p method.try(&.native_import)
  p method.try(&.token)
  p method.try(&.generic_params)

  # Classification comes from TypeDef flags and the base type, not from names.
  handle = api.type?("Windows.Win32.Foundation.HANDLE")
  if handle
    p handle.value_type?                      # true
    p handle.union?                           # value type with explicit layout
    p handle.enum?, handle.delegate?, handle.interface?, handle.static_class?
    p handle.base_type                        # "System.ValueType"
    p handle.packing_size, handle.class_size  # from ClassLayout, or nil

    # Custom attributes are available on types, fields, methods and params
    # with their constructor and named arguments decoded.
    p handle.has_attribute?("NativeTypedef")
    p handle.attribute?("InvalidHandleValue").try(&.fixed_arg?(0))  # "-1"
    p handle.fields.first.signature                                 # "nativeint"
  end

  apis = api.type?("Windows.Win32.System.Threading.Apis")
  if apis
    create_thread = apis.methods.find { |m| m.name == "CreateThread" }
    p create_thread.try(&.set_last_error?)                   # from ImplMap flags
    p create_thread.try(&.params.last.out?)                  # Param flags
    p create_thread.try(&.params.first.attribute?("Const"))
    p create_thread.try(&.return_attributes.map(&.name))
    infinite = apis.fields.find { |f| f.name == "INFINITE" }
    p infinite.try(&.constant_type), infinite.try(&.constant_value)  # "uint32", "4294967295"
  end

  # Nested types are unique only by token; enclosing/nested links carry both.
  slist = api.type?("Windows.Win32.System.Kernel.SLIST_HEADER")
  slist.try &.nested_type_tokens.each { |token| p api.type_by_token?(token).try(&.name) }
end

# Token-based metadata row helpers (TypeDef/MethodDef/Field)
p parsed.type_def_by_token?(0x02000001_u32).try(&.type_name)
p parsed.method_def_by_token?(0x06000001_u32).try(&.name)
p parsed.field_by_token?(0x04000001_u32).try(&.name)

# Optional strict mode: raise when parser has to skip undecoded tables.
strict_parsed = Ecma335.parse("winmd/Windows.Win32.winmd", strict: true)

Development

Run tests:

crystal spec

Fetch Windows.Win32.winmd (uses the version pinned in winmd.version):

pwsh ./scripts/fetch-winmd.ps1

Override the version or output path:

pwsh ./scripts/fetch-winmd.ps1 -Version 70.0.11-preview
pwsh ./scripts/fetch-winmd.ps1 -OutputPath winmd/Windows.Win32.winmd

WinMD Utility

This repo includes a small CLI utility for inspecting WinMD files:

crystal run src/ecma335_tool.cr -- winmd/Windows.Win32.winmd stats
crystal run src/ecma335_tool.cr -- winmd/Windows.Win32.winmd list types --limit 20
crystal run src/ecma335_tool.cr -- winmd/Windows.Win32.winmd list types --namespace Windows.Win32.UI.Controls
crystal run src/ecma335_tool.cr -- winmd/Windows.Win32.winmd list methods --type Windows.Win32.Foundation.HRESULT --limit 20
crystal run src/ecma335_tool.cr -- winmd/Windows.Win32.winmd list methods --all
crystal run src/ecma335_tool.cr -- winmd/Windows.Win32.winmd show type Windows.Win32.Foundation.HRESULT
crystal run src/ecma335_tool.cr -- winmd/Windows.Win32.winmd show method Windows.Win32.Foundation.HRESULT#SomeMethod
crystal run src/ecma335_tool.cr -- winmd/Windows.Win32.winmd show type Windows.Win32.Foundation.HRESULT --json
crystal run src/ecma335_tool.cr -- --json winmd/Windows.Win32.winmd stats
crystal run src/ecma335_tool.cr -- --json winmd/Windows.Win32.winmd list methods --limit 20
crystal run src/ecma335_tool.cr -- --json winmd/Windows.Win32.winmd search CreateFile --methods
crystal run src/ecma335_tool.cr -- winmd/Windows.Win32.winmd search CreateFile --methods --limit 20
crystal run src/ecma335_tool.cr -- winmd/Windows.Win32.winmd search CreateFile --methods --all

Notes:

  • list types hides special metadata entries like <Module> by default.
  • list types in text mode shows nested types as dotted paths (for example ParentType._Anonymous_e__Union).
  • Use --include-special to include those entries.
  • --namespace expects a namespace (for example Windows.Win32.UI.Controls), but if you pass an exact full type name it will still return that direct match.
  • list and search default to --limit 50; use --all for uncapped output.

Or build and run the shard target:

shards build ecma335-tool
./bin/ecma335-tool winmd/Windows.Win32.winmd stats

Integration test behavior:

  • spec/ecma335_spec.cr includes an integration example for winmd/Windows.Win32.winmd.
  • If that file exists, the integration example runs by default.
  • If the file is missing, that single integration example is marked pending with a helpful message.

Contributing

  1. Fork it (https://github.com/mjblack/ecma335/fork)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Contributors