How to concatenate a string in a do block? - smalltalk

I'm trying to go though a array and add characters from that array to another object. The problem is I keep getting a error "Instances of character are not indexable". However when I run tag := tag,char outside of the do block then it works.
|data startTag tag|.
data := '123456778'
startTag := false.
tag := ''.
data asArray do: [:char |
tag := tag,char]

The , is defined as
Collection>>, aCollection
^self copy addAll: aCollection; yourself
so that tries to operate on your single character as if it were a collection. That explains the error.
For larger collections you do not want to build up using , because of the copy that happens each time. Therefore use the streaming protocol:
|data tag|
data := '123456778'.
tag := String streamContents: [:s |
data do: [ :char |
s nextPut: char]]
Also take a look at Collection>>do:separatedBy: to add separators between your data.
[edit] Ah, ok, that's something like
|data tag tags state|
data := '<html>bla 12 <h1/></html>'.
state := #outside.
tags := OrderedCollection new.
tag := ''.
data do: [ :char |
state = #outside ifTrue: [
char = $< ifTrue: [
state := #inside.
tag := '' ]]
ifFalse: [
char = $> ifTrue: [
state := #outside.
tags add: tag]
ifFalse: [ tag := tag, (char asString)]]].
tags
"an OrderedCollection('html' 'h1/' '/html')"

Related

Generate test method with their body in pharo

I advanced a little in my code but I find myself facing another problem for two days. I would like to generate a test method using only the source code. But I have no idea how to do it.
I have a method that allows me to build the name of a test method but I can't write in it.
buildSelectorFor: aMethod
^ String streamContents: [:i || capitalize |
capitalize := true.
i << 'test'.
aMethod selector do: [:charactar |
charactar= $:
ifTrue: [ capitalize := true ]
ifFalse: [ capitalize
ifTrue: [
capitalize := false.
i << charactar asUppercase. ]
ifFalse:[ i << charactar ]]]]
so if I execute this method with this for example:
buildSelectorFor:Car>>#speed:mark:
I get this:
testSpeedMark
my goal is to get something like
testSpeedMark
self assert:....equals:...
I added a method writeTestMethod.
writeTestMethod: aMethod with: anObject
^(self buildTestSelectorFor: aMethod),'
|classMethod setter instObject method|
classMethod := aMethod methodClass.
setter := (classMethod allSelectorsInProtocol: #setter) asArray.
instObject := classMethod new.
(setter with: anObject do: [:set :ivar | instObject perform: set with: ivar]).
self assert: instObject class equals: (classMethod new) class.'
So here is what I get:
I don't know how to integrate the parameters of writetestMethod in the code I want to generate

Does HelpNDoc Pascal Script support structures?

I am trying to create a structure:
MyTopic
TopicID : String;
HelpID : Integer;
I wanted to create an array of these structures so I could sort them.
I have tried using this type / record syntax but it is failing.
Update
I defined this type and procedure:
type
TMyTopicRecord = record
idTopic : String;
idContextHelp : integer;
End;
procedure GetSortedTopicIDs(aTopics : array of String; size : Integer);
var
aMyTopicRecords : array of TMyTopicRecord;
temp : TMyTopicRecord;
iTopic, i, j : Integer;
begin
// Init the array
SetLength(aMyTopicRecords, size);
// Fill the array with the existing topid ids.
// Get the context ids at the same time.
for iTopic := 0 to size - 1 do
aMyTopicRecords[iTopic].idTopic := aTopics[iTopic];
aMyTopicRecords[iTopic].idContextHelp := HndTopics.GetTopicHelpContext(aTopics[iTopic]);
// Sort the array on context id
for i := size-1 DownTo 1 do
for j := 2 to i do
if (aMyTopicRecords[j-1].idContextHelp > aMyTopicRecords[j].idContextHelp) Then
begin
temp := aMyTopicRecords[j-1];
aMyTopicRecords[j-1] := aMyTopicRecords[j];
aMyTopicRecords[j] := temp;
end;
// Rebuild the original array of topic ids
for iTopic := 0 to size - 1 do
aTopics[iTopic] := aMyTopicRecords[iTopic].idTopic;
end;
The procedure gets called in a loop of the parent function (code snipped):
function GetKeywordsAsHtml(): string;
var
aKeywordList: THndKeywordsInfoArray;
aAssociatedTopics: array of string;
nBlocLevel, nDif, nClose, nCurKeywordLevel, nCurKeywordChildrenCnt: Integer;
nCurKeyword, nCurKeywordTopic: Integer;
nCountAssociatedTopics: Integer;
sCurrentKeyword, sKeywordLink, sKeywordRelated: string;
sKeywordJsCaption: string;
begin
Result := '<ul>';
nBlocLevel := 0;
try
aKeywordList := HndKeywords.GetKeywordList(False);
for nCurKeyword := 0 to length(aKeywordList) - 1 do
begin
sCurrentKeyword := aKeywordList[nCurKeyword].id;
nCurKeywordLevel := HndKeywords.GetKeywordLevel(sCurrentKeyword);
nCurKeywordChildrenCnt := HndKeywords.GetKeywordDirectChildrenCount(sCurrentKeyword);
sKeywordLink := '#';
sKeywordRelated := '[]';
aAssociatedTopics := HndTopicsKeywords.GetTopicsAssociatedWithKeyword(sCurrentKeyword);
nCountAssociatedTopics := Length(aAssociatedTopics);
if nCountAssociatedTopics > 0 then
begin
GetSortedTopicIDs(aAssociatedTopics, nCountAssociatedTopics);
// Code snipped
end;
end;
finally
Result := Result + '</ul>';
end;
end;
The script compiled in the HelpNDoc internal editor with no issues. But when I go to actually build my HTML documentation I encounter a problem:
The HelpNDoc API is explained here.
Is there something wrong with my code?
I decided to go about it a different way and used a simpler technique:
procedure GetSortedTopicIDs(var aTopics : array of String; iNumTopics : Integer);
var
iTopic : Integer;
// List of output
aList: TStringList;
begin
// Init list
aList := TStringList.Create;
// Build a new array of "nnn x"
// - nnn is the help context id
// - x is the topid id
// Note: I know that the context ID values are within the range 0 - 200
for iTopic := 0 to iNumTopics - 1 do
// We pad the context id with 0. We could increase the padding width to
// make the script mre useful
aList.Add(Format('%0.3d %s', [
HndTopics.GetTopicHelpContext(aTopics[iTopic]),
aTopics[iTopic]
]));
// Now we sort the new array (which basically sorts it by context id)
aList.Sort;
// Update original array
for iTopic := 0 to iNumTopics - 1 do
// We ignore the "nnn " part of the string to get just the topic id
aTopics[iTopic] := copy(aList[iTopic],5, length(aList[iTopic])-4);
// Tidy up
aList.Free;
end;
This compiles and I get the sorted array of topic IDs at the end of it. So the pop-up help is now listed as I want.

Creating a Caesar Cipher Method

So I need to get the Caesar Cipher code in smalltalk and create a method and use it so I can do the following test on it
|aString|
aString:=Caesar new encrypt: 'CAESAR'.
Transcript show: aString.
I already have the class made. But I need to make the method of it.
I found this but how can I make a method out of this so I can all the above code in playground.
| i c strCipherText strText iShiftValue iShift |
strText := 'the quick brown fox jumps over the lazy dog'.
iShiftValue := 3.
strCipherText := ''.
iShift := iShiftValue \\ 26.
i := 1.
[ i <= (strText size) ]
whileTrue: [
c := (strText at: i) asUppercase.
( ( c >= $A) & ( c <= $Z ) )
ifTrue: [
((c asciiValue) + iShift > $Z asciiValue)
ifTrue: [
strCipherText := strCipherText, (((c asciiValue) + iShift - 26)
asCharacter asString).
]
ifFalse: [
strCipherText := strCipherText, (((c asciiValue) + iShift)
asCharacter asString).
].
]
ifFalse: [
strCipherText := strCipherText, ' '.
].
i := i + 1.
].
Transcript show: strCipherText.
Transcript cr.
So to make thing clear, I need to make a method using the Caesar Cipher code and use the "aString" code at the beginning and test it with that. I have this code above but this has already text in it and can't be put into the method.
Any help will be appreciated.
As Max said in his comment the code above can be put in a method. The only missing part is a first line with the selector and the formal argument:
caesarCipherOf: strText
<insert the code here>
Another good suggestion by Max is to call the argument aString rather than strText because that's more aligned with how Smalltalkers name things.
But now let's take a look at the source code itself:
The comparison c >= $A & (c <= $Z) means c isLetter.
The conditional calculation of the next character means that we want to shift-rotate c by moving it 3 characters to the right, wrapping it around if it gets beyond $Z. This can be easily expressed as:
(c codePoint - 64 + 3 \\ 26 + 64) asCharacter
where 64 = $A codePoint - 1, is the offset between $A and any given uppercase character c. Note also that I've replaced asciiValue with codePoint.
With these two observations the method can be re-written as
caesarCipherOf: aString
^aString collect: [:c |
c isLetter
ifTrue: [(c asUppercase codePoint - 64 + 3 \\ 26 + 64) asCharacter]
ifFalse: [$ ]]
This is not only shorter, it is more efficient because it avoids creating two new instances of String at every character. Specifically, any expression of the form
string := string , <character> asString
creates two Strings: one as the result of sending #asString, another as the result of sending the concatenation message #,. Instead, #collect: creates only one instance, the one that the method returns.

Can not call "function" in GNU Smalltalk

I want to define block and call it in this way:
add := [ :a :b |
^(a+b).
].
n := add value: 1 value: 2.
But when I try it, I get an error:
$ gst 3.1.st
Object: 3 error: return from a dead method context
SystemExceptions.BadReturn(Exception)>>signal (ExcHandling.st:254)
SystemExceptions.BadReturn class(Exception class)>>signal (ExcHandling.st:151)
SmallInteger(Object)>>badReturnError (Object.st:1389)
UndefinedObject>>executeStatements (3.1.st:3)
How can I call a function in GNU Smalltalk?
Well, I dropped the return statement, and this code works fine. But when I try to define more complicated function, for example:
nod := [ :a :b |
(a=b) ifTrue: [a].
(a>b) ifTrue: [nod value: (a-b) value: b].
(a<b) ifTrue: [nod value: a value: (b-a)].
].
n := nod value: 1 value: 2.
n printNl.
It prints nil. And if I define with "early exit":
nod := [ :a :b |
(a=b) ifTrue: [^a].
(a>b) ifTrue: [nod value: (a-b) value: b].
(a<b) ifTrue: [nod value: a value: (b-a)].
].
n := nod value: 1 value: 2.
n printNl.
It gives me the same error: return from a dead method context.
I solve this problem in this way:
nod := [ :a :b |
(a=b) ifTrue: [
a
] ifFalse: [
(a>b) ifTrue: [nod value: (a-b) value: b] ifFalse: [nod value: a value: (b-a)].
]
].
n := nod value: 1 value: 2.
n printNl.
But I think, it is not beautiful way.
Drop the return statement (^) from your code and it will work fine.
In smalltalk, returning exits the method in which the return statement appears. It's used for early exits, for example a < b ifTrue: [^a] ifFalse:[^b].
In this case, you don't want the block to exit the containing method, you just want it to evaluate to something when sending it value:value:. A block evaluates to the last statement in it, so just make it a regular statement there and it will do the trick.
The error message you got, by the way, also explains the problem: you're trying to return 3 from a method which is already dead.
Remove non-local return (^), parenthesis, and period inside a block. And try doing it again.
You've already accepted an answer to your original question, then redefined your question.
To answer your updated question, you could use the fact that a block returns the value of its last statement, and use a local variable:
nod := [ :a :b | |r|
(a = b) ifTrue: [r := a].
(a > b) ifTrue: [r := nod value: (a-b) value: b].
(a < b) ifTrue: [r := nod value: a value: (b-a)].
r
].
I am curious, though, about the context. I suspect this might more appropriately be defined as a selector/method for a class.
By the way, the above implementation will go into an infinite recursion if either argument is negative.
The simplest way to get the results you're after would be:
nod := [ :a :b | a gcd: b ].
:)

How to change the auto completion button in squeak?

is there a way to change the shortcut to invoke auto completion in the squeak vm (standard is tab)?
Thanks in advance
(I assume you use OCompletion or ECompletion)
The only way to change this currently is to change the code.
OController>>handleKeystrokeBefore: kbEvent editor: theEditor
"I return a boolean. true when I have handled the event and no futher processing is needed by the caller."
| keyValue ctrl cmd down tab colon alphanum del esc enter up |
self editor: theEditor.
self setModel: theEditor model.
keyValue := kbEvent keyValue.
ctrl := kbEvent controlKeyPressed.
cmd := kbEvent commandKeyPressed.
down := keyValue = 31.
up := keyValue = 30.
tab := kbEvent keyCharacter = Character tab. "<-- change this to your desired key"
enter := kbEvent keyCharacter = Character cr.
colon := kbEvent keyCharacter = $:.
alphanum := kbEvent keyCharacter isAlphaNumeric.
"..."
Or, when you only use ECompletion
ECController>>handleKeystrokeBefore: aKeyboardEvent editor: anEditor
"I return a boolean. true when I have handled the event and no futher processing is needed by the caller."
| theEditor keyValue controlKeyPressed isSpaceKey |
self editor: anEditor.
theEditor := self editor.
self setModel: theEditor model.
keyValue := aKeyboardEvent keyValue.
controlKeyPressed := aKeyboardEvent controlKeyPressed.
isSpaceKey := #(0 32 ) includes: keyValue.
"Ctrl-Space or Tab for open"
self isMenuOpen
ifFalse: [(isSpaceKey & controlKeyPressed
or: [keyValue = 9 " <-- change this to your desired key"
and: [theEditor isCaretBehindChar
and: [controlKeyPressed not]]])
"..."