Advanced Magik Language

This page is intended for advanced programmers. See Magik Basic Language for more common language uses.

Compiling on the Fly

Do you need to load Magik code on the fly? Lets say a procedure is stored as a text join on an object and when you update the object, you want to let it run the procedure stored on the object.

The first thing you need is a handle to the string you want to "compile"
Then run this method: magik_rep.load_chunck(string.read_stream())

Stopping an Endless Loop on the Magik Prompt.

Have you started a (Near) endless loop on the Magik prompt? Here is a way to stop the loop.. You will need to open a Magik Input GUI from the opened application. In 3.X it was under Configuration or something like that. Once there type the following.

cli_thread.interrupt(_proc() _throw@wobbly _endproc)

From the Threads Manager, you can error the cli thread and it should stop the loop. Note the Threads Manager may only be available on 4.X releases.

Indirect Access to Globals

Have you wanted to write code that tests for the existence of a global variable without initializing the variable itself? You can use the following. Note the !current_package! could be replaced with packages such as sf_package.

!current_package![:global_symbol_name]

Another way. Good for getting procs too…

get_global_value(:!print_length!)
get_global_value(:startup)

You can "undeclare" a global by doing the following.

!current_package!.remove_key(:global_symbol_name)

Mixins

Adding a Mixin during a session

If you need to create a mixin in a current session and want to add the mixin ancestry to existing exemplars that you can't recreate during the session (record exemplars for example), you can add them using the following magik

def_mixin(:whatever_mixin)
whatever_mixin.add_child(whatever_exemplar)

Mixin Method Table

A defined mixin's method table is retrieved using mtable on the mixin itself. method_table on the mixin return's the core mixin method table. Also note, to find the core mixin methods in method browser, use mixin_mixin as the exemplar name.

Change Mixin Shared Variable

To change a shared variable defined on a mixin, you can do it through method.value.

def_mixin(:my_mixin)
$
my_mixin.define_shared_variable(:a_var,:test,:public)
$
my_mixin.method(:a_var).value << :changed

Getting Current Dynamics

Here is a way to get the available dynamic variables in your current thread.

_for k,v _over _thisthread.dynamic_environment(_true)
_loop
    show(k,v)
_endloop

Do you need to make a private method public?

Try this… Note you don't typically want to use this… But it is something to be aware of.

map_trail.method(:append_sectors_to_trail|()|).set_private(_false)

!!! THREAD !!! bpt on thread error

I was getting this when I was trying to do a code coverage analysis. The way to track this down is to add the following show() statement.

_pragma(classify_level=restricted)
_method coverage_analyser.insert_bpts_in(method)
show(method)
    method.value.set_breakpoint(0)
_endmethod
$

From the result you can add the problem method to coverage_analyser.excluded_code_vectors shared variable
_block
    _local ex << message_handler
    _for m _over {:human_string|()|
    }.fast_elements()
    _loop
        coverage_analyser.excluded_code_vectors.add(ex.method(m).value.code_vector)
    _endloop 
_endblock

Evaluating Strings into Magik results

You can evaluate a string like "_false" into a true Magik boolean.

MagikSF> !a << magik_text.new()
$
MagikSF> !a.add_last("_false")
$
MagiKSF> !a.evaluate() = _false
$
True

Temporarily Remove Method Functionality

Have you tried to use a method like split_by() only to see your leading and trailing spaces disappear? Here is a way you can overwrite the default behavior.. In this case we replace the trim_spaces() method with a simple procedure, run the split_by() and restore the original behavior.

    _local data
    _local str << " this is, a test ,  ,,"
    _local oProc << charindex_mixin.method(:trim_spaces|()|).value 
    _protect
        charindex_mixin.method(:trim_spaces|()|).value << 
                _proc()
                    >> _self 
                _endproc
        data<< str.split_by(%,,_true)
    _protection
        charindex_mixin.method(:trim_spaces|()|).value << oProc
    _endprotect

Lexicographic Sorting

Lexicographic sorting is sorting strings like in a dictionary. When sorting strings, there can be special characters like ï, é and so on. The special characters do not sort on the base character, but differently as is shown by the following example:

MagikSF> %ï _cf %j
$
False 
MagikSF> %i _cf %j
$
True 

Here is a simple solution to the Lexicographic sorting problem with special characters.
_block
    _local l_special_chars << property_list.new_with(%ï,%i, %ë,%e, %ö,%o)
    _local l_normalise << _proc(a) 
                      _import l_special_chars
                      _local l_result << ""
                      _for i_char _over a.fast_elementS()
                      _loop
                          l_result +<< l_special_chars[i_char].default(i_char)
                      _endloop
                      _return l_result
                  _endproc 
    _local l_list << {"mals", "maak", "mazk", "maïs"}
    _local l_sorted_list << sorted_collection.new_from(l_list,
                               _proc(a,b) 
                                   _import l_normalise 
                                   _return l_normalise(a) _cf l_normalise(b)
                               _endproc)
    print(l_sorted_list)
_endblock
$
sorted_collection(1,4):
1     "maak" 
2     "maïs" 
3     "mals" 
4     "mazk" 

Alternatively one may use text translators
text_converter.new(:cp1250, _false).translate(text_converter.new(:cp1250, _true).translate("maïs"))

Renaming Methods

You can rename a method using the following. This is great to implement customized code without modifying the original code. This saves lots of time when TSBs or upgrades are installed!

meth << exemplar.method(method_name)
newMeth << :sw!+method_name
exemplar.define_method(newMeth,meth.value,_false)
This functionality has been wrapped up in the attached fcsi_method_manip.magik file. Use fcsi_method_manip.fcsi_copy_method().

Handling "does not understand"

You can subclass the method does_not_understand(msg,private?). This allows you to support functionality when you do not necessarily know what is going to be called. A good example of this usage is in the Explorer Add-on for Pseudo Records located here. This passes unknown method calls to an owning object. Another interesting solution would be to create a in-memory object whose fields may be anything. You can intercept the non () methods and create in-memory object representations of those fields and they would act just like a field or slot on the original exemplar…

Pseudo Slots

Pseudo slots (defined on the exemplar with define_pseudo_slot()) allow you to add functionality to an existing exemplar without subclassing it. I use it a lot when trying to add functionality to editors or something that is very difficult or very time consuming to replace the exemplar name in places.

One side affect I recently ran across is that when define_pseudo_slot() is called, it sets up other methods like shallow_copy() on the exemplar the slot is being added to. This can cause conflict methods. For example if you add a pseudo slot to database_view, any subclass of database_view will have shallow_copy() method conflicts. You will need to add the method to the subclass to use the appropriate method.

Pesky Reporting

Have you added debug code and didn't label it with something you can search for and then save the file? Then you see stuff going to the magik prompt and you can't figure where the heck it is coming from? Here is some code to help: find_writers_to_prompt.magik. Load the file and then when you find your culprit, use fcsi_restore_write_and_show() to get rid of the tracebacks!

Method Finder/Class Browser

Method Finder Mapping

You can map the location of source files so that the method finder can jump to the appropriate code. This is especially good for when a JAR file is delivered but only certain source files are delivered in a separate directory. You can use method_finder.add_mapping("NAME_OF_ENV","mapped_directory").

An example for the following condition:

  • the original location for compiling files was at c:\release\my_modules.
  • the installed directory is \\a_server\product\some_modules
  • you have an environment called SOME_MODS_DIR that points to \\a_server\product\some_modules

You can run method_finder.add_mapping("SOME_MODS_DIR","c:\release\my_modules").

NOTE : This seems to work ok for SW5, but not sure about 4.3. Could be user error. If anyone has a better example/use please share!

An example without using an evironment variable:

  • the orignal location for compiling files was at e:\gis
  • the installed directory is r:\

Using F3-j in the Class Browser results in: 'error in process filter: Cannot find file, e:\gis\…'.
Note: Exploring the mf-file with a text-editor resolves that the actual paths used are: 'E:\gis\…' (capital E).
Now the following mapping works:

method_finder.add_mapping("R:\", "E:\gis\", "")

Note the following obvious mapping did not work:

method_finder.add_mapping("r:\", "e:\gis\", "")

Check your mapping by:

print(method_finder)

a sw:method_finder
command method_finder
pid     6752
mapping E:\gis\ => R:\

Don't forget to apply the mappings by:

method_finder.apply_mappings()

Now the F3-j should jump to the right file.

For future use, you now may want to save the method_finder by:

method_finder.save(optional <target path to *.mf>)

To reset the original mappings, the easiest way is to reload the mf-file by:

method_finder.load(<source path to *.mf>)

or you can apply the reverse mapping by:

method_finder.add_mapping("E:\gis\", "R:\", "")
method_finder.apply_mappings()

*Packages

This needs a page of its own as there appears to be many things you can do with packages….

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License