Mailing List Archive: 49091 messages
  • Home
  • Script library
  • AltME Archive
  • Mailing list
  • Articles Index
  • Site search

[REBOL] contexts and words

From: galtbarber::mailandnews::com at: 10-Aug-2000 21:57

Hi, today I learned a little about rebol internals. I had an object that accumulated a lot of data from a run, and when I ran obj/Report which was supposed to give me my valuable results, I discovered that due to a minor bug it wouldn't complete. The object in question was too complex to just manually check a few fields. I tried re-loading the script after correcting it, but then I had a corrected prototype myobj! and my one real instance myboj which was originally made with myobj: make myobj! [] Now, I tried to repair myobj set in myobj 'Report get in myobj! 'Report Well, that almost worked. Now the code was correct as far as not having a bug, but unfortunately, the context of the /Report function words and so forth were all bound to the context of myobj! instead of myobj, and myobj! just was an empty prototype. My initial hack would have been to try to bind the words in /Report to the right context, but I couldn't seem to make it work, and while I was fooling around, I accidentally killed 'first as follows: first: get in myobj 'Report or something like that. Rebol really gives you the power to blast your foot off, and I say, why not, it's a free country, ain't it?! Well, I then found all sorts of functions no longer worked with first dead, and I couldn't figure out how to set first back to whatever native #41 it had originally been assigned to. Maybe that's not something we're supposed to be able to do... Anyway, I ended up figuring out that I could save %myobj.txt myobj and then I loaded up a new instance of Rebol.exe and did a myobj: do load %myobj.txt And with a little more fooling around, I had my object back and working, and I was eventually able to do /Report and get the data out that I needed. Apparently myobj was quite happy to work with its words bound to the global context that load gives it, but more about that later. I also had to walk thru myobj, changing any reducing any block values in it. E.g. I had a block called VU: copy [] declared in myobj and after it ran it was full of cool stuff. Unfortunately, "do load" ain't enough to get it back in shape. You need this little feller, self-redux, and handy loop. self-redux: func [ blk [block!] /local newblk ][ newblk: reduce blk clear blk insert blk newblk ] notice that I preserve the original block, just in case there were other references to that block they would still be preserved. Had I just set blk: somenewblock then it would have changed blk within self-redux, but the original value passed in would still be bound the the words of myobj. So, you need this to fix up loaded myboj: self-redux myobj/VU and if you have other objects in vu, say history: [] they will need help, too: repeat v length? myobj/VU [ self-redux myobj/VU/:v/history ] and so on. I wish there were something better than save and load. And also, I should warn you that if you save an object which has a word that has been unset, and my favorite example is the inBuffer: unset which you get inside a saved closed port object. You can cheat that too by doing a search and replace on the original %myobj.txt replacing all occurences of inBuffer: unset with inBuffer: none unset 'inBuffer Bizarre as it is, it works. Of course other objects may have other special requirements to get save and load to cooperate. You get to feeling very religious at a time like this. Lord, save me! The end result of that was that I had all my data back and /Report gave me the goodies! After that I played around with bind and tried to figure out how I would have got /Report re-bound. I am still not totally sure how that would work, but I did finally dig into functions, using first, second, and third. It's not as evil as it looks at first, and it is quite enlightening. first :somefunc will give you the parameter list it is basically just a block of words including refinements. You can save a reference to it in another variable parms: first :somefunc probe parms parms/3 etc. Note that you have to use : in front of somefunc or else when rebol evaluates the line you typed in it will think somefunc needs to be executed instead of just grabbing the function value associated with the word 'somefunc third :somefunc is almost the same thing, except that it is preserved the way you actually typed it and contains comments and so forth that the original code had when it was defined/executed, and which the 'help and 'source functions can take advantage of automatically. It also contains the types, e.g. [block!] if any were declared to check the param value types in the original somefunc definition. Obviously, the list of words in "first :somefunc" is used by the system when reducing and binding parameters in evaluating expressions and can checking number of params, etc... But the meat of your function is in "second :somefunc" which is basically nothing but words and literals and blocks, I guess. Comments are not contained in it. Even operators are just words? Well you can do this: x: second :somefunc repeat i length? x [print [i type? x/:i x/:i]] and you will get a cool list of the words, and their types and position numbers in the block. If you want to grab just a particular word, you can just say x/3 to get the third item. This is very handy. If x/3 returns a word, it is not just any word. Somehow, the context comes along with the word, and you can insert that word, carrying it's context and therefore what happens to it when you reduce it or evaluate it, you can insert that word into another block!!! You can go around having a good time now! You can also use find to find the word in the block x. e.g. find x '+ would find the first occurenc of the operator + that might occur somewhere inside that function body. Paths are a little weird, and so on... You can't get to the parts of a path as if it were a block. All I know is that you might be able to get a string representation of the path with "s: to string! x/7" and then s would be a string that looks like the path contained in the 7th position of our imaginary function somefunc. You could then try to parse the string into parts, rebind the right words, and then form a new path and then put that back into the code block. I haven't actually done this yet, as there are so many other wonderful things to wonder at. For instance, if you have a string like this: z: {[ answ: "tell me about it!" ;;; stupid comment print answ ]}
>> zz: load z
== [ answ: "tell me about it!" print answ ]
notice that there are no comments after load is done with it. also, while z is a string and z/1 is a single character [, zz is a block and zz/1 is the word 'answ bound to the global context. If you had someotherword like this someobject: make object! [ answ: "pray for a miracle!" ] someotherword: in someobject 'answ I tried stuff like bind zz/4 someotherword but it never worked, even though the help on bind implies that it would work. tmp: [answ] bind tmp/1 someotherword also doesnt work, you get no change in the word answ inside tmp. However this does work, if I recall right: bind tmp someotherword for some reason, binding a block of word(s) works, and binding a single word chokes. But bind goes through all words in the block and changes their context to that of someotherword. now we can do a little surgery: change at zz 4 tmp/1 this should mean that even though zz will set the global word 'answ to "tell me about it!", when we execute print answ this is going to be the word answ in the context of someobject where answ equals "pray for a miracle!" of course you could have just done this: change at zz 4 someotherword or this: change at zz 4 in someobject 'answ
>>do zz
pray for a miracle!
tell me about it! Just as we finagled about with the block of code in zz, you can fiddle around with it just as well if you get the third of the :somefunc, because that is just another block. So, now you see what load does to a string, stripping comments, converting to words and blocks and other tokens, I guess. Plus all of these newly created words in the load result have the global context bound to them. If you just then execute, do, reduce, whatever, the loaded block, then when each word is fetched and it's value is get then the value is going to be in the global context. By the way, this kind of solves the problem of forward references, e.g. Start fresh Rebol.exe,
>>value? 'y
== false
>>x: [print y ] ;; we haven't defined y yet, oh no! >>y: "happy new year!" >>value? 'y
== true
>>do x
happy new year! you will get the nice result. How? well, when 'y is created inside blk [print y], it has the global context. Even though y at this point has not been set to anything as shown by the function 'value? So, anyway, you can dick around with the block in the function, editing to your hearts delight, inserting, deleting, adding words from god knows where they been! And you know now how to get words from other blocks and objects, so let the Royal Rebol Rumpus begin! -Galt