How can I use Crystal compiler as a library?

Hello.

I am interested in learning more about the Crystal language.

I learned that the Crystal language creates executables via LLVM-IR. And I learned that the following commands work.

Crystal → assembly

crystal build --emit asm --prelude empty test.cr

Crystal → LLVM IR

crystal build --emit llvm-ir --prelude empty test.cr

LLVM IR → assembly

llc --relocation-model=pic test.ll

assembly → executable

clang test.s

Adding libraries to link

clang test.s -levent -lgc -lm

llvm .ll → .bc

llvm-as test.ll
lli test.bc

Then, I learned that there is such a thing as a Lexer or Parser.

require "./src/compiler/crystal/syntax"

str = ARGF.gets_to_end
a = Crystal::Lexer.new(str)
loop do
  t = a.next_token
  puts [t.type,
        t.value,
        t.number_kind,
        t.line_number,
        t.column_number,
        t.filename,
        t.delimiter_state.kind,
        t.delimiter_state.nest,
        t.delimiter_state.end,
        t.delimiter_state.open_count,
        t.delimiter_state.heredoc_indent,
        t.delimiter_state.allow_escapes,
        t.macro_state.whitespace,
        t.macro_state.nest,
        t.macro_state.control_nest,
        t.macro_state.delimiter_state,
        t.macro_state.beginning_of_line,
        t.macro_state.yields,
        t.macro_state.comment,
        t.macro_state.heredocs,
        t.passed_backslash_newline,
        t.doc_buffer,
        t.raw,
        t.start,
        t.invalid_escape,
        t.location].map { |i| i.to_s }.join("\t")
  break if t.type == Crystal::Token::Kind::EOF
end
require "./src/compiler/crystal/syntax"

str = ARGF.gets_to_end
a = Crystal::Parser.parse(str)

def puts_ast(ast)
  puts "\e[32m# #{ast.class}\e[0m" # green
  puts ast
  puts
end

if a.is_a?(Crystal::Expressions)
  a.expressions.each do |ast|
    puts_ast(ast)
  end
else
  puts_ast(a)
end

I would like to get to know Crystal a little deeper. It will take time, but it is not impossible. My small problem is that the compiler is not provided as a library. This makes it difficult to run experimental code using each class of the compiler. This is necessary to understand how the Crystal compiler works.

How can I learn Crystal well?
Or how did you learn it?

6 Likes

With the specs?

1 Like

Why is this a problem? The compiler’s source code is distributed with the standard library.
You can put a require "compiler/crystal/requires" (or a scoped-down version of it) in your code and the compiler will be available as a library. You can use that for running experimental code, or production code (ameba for example).

3 Likes

Thank you, I would like to read the spec little by little.

You are right. I thought that the compiler couldn’t be used as a library, but I was wrong.

# ameba code
require "compiler/crystal/syntax/*"

This code works fine. But when I try to use:

require "compiler/requires"

I get an error:

Showing last frame. Use --error-trace for full trace.

In /usr/lib/crystal/lib/compiler/crystal/tools/doc/generator.cr:1:1

 1 | require "../../../../../lib/markd/src/markd"
     ^
Error: can't find file '../../../../../lib/markd/src/markd' relative to '/usr/lib/crystal/lib/compiler/crystal/tools/doc/generator.cr'

How can I fix this?"

I’ve ran into this issue too, created a GitHub issue here Move vendored shards to "src/compiler/vendor/" · Issue #13784 · crystal-lang/crystal · GitHub

3 Likes

@nobodywasishere
Oh, this is exactly what I wanted to know. This is a good issue.
I ran shard init and added markd, but as expected require "compiler/requires" did not work.