Engee documentation
Notebook

Compilation of strings

This example discusses alternative approaches to defining actions and variables using strings. We will take a detailed look at the parse, Meta.parse and eval functions, as well as demonstrate their practical application.

During the work, two files of various formats will be parsed: CSV and TXT, which will clearly show the capabilities of these tools for data processing and dynamic code execution.

The parse function in Julia is used to convert the string representation of data to the corresponding numeric or other basic type. It is especially useful when you need to work with data represented as strings that need to be converted, for example, to numbers, characters, or other types of values.

For example, if you have a string containing a number such as "5". You can use parse to convert it to an integer. Similarly, the floating-point string "1.23" can be converted to a floating-point number. This function supports specifying the type to which the conversion should be performed.

Usage examples:

In [ ]:
Pkg.add(["CSV"])
In [ ]:
num1 = parse(Int, "5")   
num2 = parse(Float64, "1.23")  
print("Результаты: $([num1, num2])")
Результаты: [5.0, 1.23]

The Meta.parse function in Julia is used to convert a string containing an expression or function call into an object of type Expr (Julia expression). It is a powerful metaprogramming tool that allows you to analyze and modify program code in the form of an abstract syntax tree (AST).

If the string contains an expression, such as a mathematical operation or a function call, Meta.parse converts it into a structured representation that can be used for analysis or execution.

An important difference from parse is:

  1. parse converts strings only to finite values of a given type, such as numbers, booleans, or symbols. It does not support complex expressions or function calls.
  2. Meta.parse works exclusively with code represented as a string and converts it into an Expr object that can be analyzed or passed for execution.
    Usage example:
In [ ]:
cmd = Meta.parse.("1+1")
Out[0]:
:(1 + 1)

The eval function in Julia is used to execute expressions represented as objects of type Expr (abstract syntax tree) or other valid expressions. It allows you to run code dynamically, which makes it a powerful tool for metaprogramming tasks, but requires careful use due to the potential risks of executing unwanted code.

The basic principle of operation is that
eval accepts an expression, analyzes it, and executes it in the global scope. This means that all variables and functions used inside an expression must be defined in the global scope.

Usage example:

In [ ]:
eval(cmd)
Out[0]:
2

Next, let's look at an example with a CSV file. To do this, we will download additional libraries.

In [ ]:
Pkg.add("CSV")
using DataFrames, CSV
   Resolving package versions...
  No Changes to `~/.project/Project.toml`
  No Changes to `~/.project/Manifest.toml`

Let's read the CSV.

In [ ]:
DataFrameCSV = CSV.read("$(@__DIR__)/$("data.csv")", DataFrame)
Out[0]:
9×1 DataFrame
RowTime_and_Ampl
String31
1-1.08,-96.6667
2-1.0799,-126.667
3-1.0798,-143.333
4-1.0797,-166.667
5-1.0796,-176.667
6-1.0795,-166.667
7-1.0794,-146.667
8-1.0793,-126.667
9-1.0792,-96.6667

Select a column with data from the resulting DataFrame.

In [ ]:
Data = DataFrameCSV.Time_and_Ampl
Out[0]:
9-element Vector{String31}:
 "-1.08,-96.6667"
 "-1.0799,-126.667"
 "-1.0798,-143.333"
 "-1.0797,-166.667"
 "-1.0796,-176.667"
 "-1.0795,-166.667"
 "-1.0794,-146.667"
 "-1.0793,-126.667"
 "-1.0792,-96.6667"

As you can see, we got a vector of strings. Now add square brackets to each line in order to get a set of commands to add elements to the vector, and perform these operations.

In [ ]:
DataVec = eval.(Meta.parse.("[".*Data.*"]"))
Out[0]:
9-element Vector{Vector{Float64}}:
 [-1.08, -96.6667]
 [-1.0799, -126.667]
 [-1.0798, -143.333]
 [-1.0797, -166.667]
 [-1.0796, -176.667]
 [-1.0795, -166.667]
 [-1.0794, -146.667]
 [-1.0793, -126.667]
 [-1.0792, -96.6667]

As you can see, as a result, we got a set of vectors consisting of 2 elements, each line was executed separately.

The next example that we will consider is the execution of a TXT file.

In [ ]:
txt = open(io->read(io, String), "$(@__DIR__)/data.txt") # Читаем TXT
Out[0]:
"1.0\n15.726465174717665\n118.19779715124804\n564.5945977782826\n1922.5628210505968\n4961.486649014338\n10069.27911225147\n16457.781390988064\n22003.338138220104\n24301.413576125095\n22293.787678765948\n17018.378514886626\n10791.5437594143\n5653.533013094259\n2423.11034196715\n836.6005210914026\n227.22904721973316\n46.794190857389\n6.873719057490497\n0.6421978090261691\n0.028701721170992484"

As you can see, we got a string with numbers and transitions to a new line in the text document after each value. Next, replace \n with commas and wrap the string in [].

In [ ]:
data_str = "[" * replace(txt, "\n" => ",") * "]"
Out[0]:
"[1.0,15.726465174717665,118.19779715124804,564.5945977782826,1922.5628210505968,4961.486649014338,10069.27911225147,16457.781390988064,22003.338138220104,24301.413576125095,22293.787678765948,17018.378514886626,10791.5437594143,5653.533013094259,2423.11034196715,836.6005210914026,227.22904721973316,46.794190857389,6.873719057490497,0.6421978090261691,0.028701721170992484]"

Now the string is a simple vector assignment. Convert the string into an expression and execute the expression to get a vector.

In [ ]:
data_vector = eval(Meta.parse(data_str))
Out[0]:
21-element Vector{Float64}:
     1.0
    15.726465174717665
   118.19779715124804
   564.5945977782826
  1922.5628210505968
  4961.486649014338
 10069.27911225147
 16457.781390988064
 22003.338138220104
 24301.413576125095
 22293.787678765948
 17018.378514886626
 10791.5437594143
  5653.533013094259
  2423.11034196715
   836.6005210914026
   227.22904721973316
    46.794190857389
     6.873719057490497
     0.6421978090261691
     0.028701721170992484

As a result, we got a vector of 21 values, which we can further interact with as with a regular vector, for example, round its elements to integers.

In [ ]:
plot(eval(Meta.parse("round.(data_vector)")))
Out[0]:

Conclusion

In this example, we examined three key Julia functions: parse, Meta.parse, and eval, which allow us to efficiently work with data and code represented as strings.

These three functions together provide a powerful set of tools for working with data and dynamic code, which is especially important in the tasks of word processing, automation and creating flexible solutions. Our experiments with different files have shown the effectiveness of these tools for solving problems with different types of input data.