Engee documentation
Notebook

String compilation

This example explores alternative approaches to specifying actions and variables using strings. We will look in detail at the parse, Meta.parse and eval functions and demonstrate their practical application.

In the course of the work we will parse two files of different formats: CSV and TXT, which will clearly show the possibilities of these tools for data processing and dynamic code execution.

The parse function in Julia is used to convert a string representation of data into the appropriate numeric or other base 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 values of other types.

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 (a Julia expression). It is a powerful metaprogramming tool that allows you to analyse and modify program code in the form of an abstract syntax tree (AST).

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

The important differences from parse are:

  1. parse converts strings only to final values of a given type, such as numbers, logical values, or characters. 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 parsed or passed for execution. Example usage:
In [ ]:
cmd = Meta.parse.("1+1")
Out[0]:
:(1 + 1)

The eval function in Julia is used to execute expressions represented as Expr (abstract syntax tree) type objects or other valid expressions. It allows you to run code dynamically, making it a powerful tool for metaprogramming tasks, but should be used with care because of the potential risks of executing unwanted code.

Basic principle of operation: eval takes an expression, analyses it and executes it in global scope. This means that all variables and functions used inside the expression must be defined in the global scope.

Example usage:

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

Next, let's look at an example with a CSV file. For this purpose, let's load 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 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 the 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 have a vector of rows. Now let's add square brackets to each row to get a set of commands for adding 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, the result is a vector set consisting of 2 elements, each row executed separately.

The next example 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 a text document after each value. Next, we 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]"

The string is now a simple vector assignment. Convert the string into an expression and execute the expression to get the 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 have a vector of 21 values, which we can further interact with as an ordinary vector, for example, round its elements to integers.

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

Conclusion

In this example, we have learnt three key Julia functions: parse, Meta.parse, and eval, which allow you to work efficiently 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 for text processing, automation, and agile solutions. Our experiments with different files have shown the effectiveness of these tools for solving problems with different types of input data.