]> icculus.org git repositories - duncan/yast2-ruby-bindings.git/blob - src/ruby/YRubyNamespace.cc
- Lot of fixes and support for the UI!!!
[duncan/yast2-ruby-bindings.git] / src / ruby / YRubyNamespace.cc
1 /*---------------------------------------------------------------------\
2 |                                                                      |
3 |                      __   __    ____ _____ ____                      |
4 |                      \ \ / /_ _/ ___|_   _|___ \                     |
5 |                       \ V / _` \___ \ | |   __) |                    |
6 |                        | | (_| |___) || |  / __/                     |
7 |                        |_|\__,_|____/ |_| |_____|                    |
8 |                                                                      |
9 |                                                                      |
10 | ruby language support                              (C) Novell Inc.   |
11 \----------------------------------------------------------------------/
12
13 Author: Duncan Mac-Vicar <dmacvicar@suse.de>
14
15 This program is free software; you can redistribute it and/or
16 modify it under the terms of the GNU General Public License
17 as published by the Free Software Foundation; either version
18 2 of the License, or (at your option) any later version.
19
20 */
21
22 #include "YRubyNamespace.h"
23
24 // Ruby stuff
25 #include <ruby.h>
26
27 #define y2log_component "Y2Ruby"
28 #include <ycp/y2log.h>
29
30 #include <ycp/YCPElement.h>
31 #include <ycp/Type.h>
32 #include <ycp/YCPVoid.h>
33 //#include <YCP.h>
34 #include "YRuby.h"
35 #include <stdio.h>
36
37 /**
38  * using this instead of plain strcmp
39  * enables embedding argument names into the typeinfo
40  */
41 static bool firstWordIs (const char *where, const char *what)
42 {
43   size_t n = strlen (what);
44   return !strncmp (where, what, n) &&
45          (where[n] == '\0' || isspace (where[n]));
46 }
47
48 /**
49  * The definition of a function that is implemented in Ruby
50  */
51 class Y2RubyFunctionCall : public Y2Function
52 {
53   //! module name
54   string m_module_name;
55   //! function name, excluding module name
56   string m_local_name;
57   //! function type
58   constFunctionTypePtr m_type;
59   //! data prepared for the inner call
60   YCPList m_call;
61
62 public:
63   Y2RubyFunctionCall (const string &module_name,
64                       const string &local_name,
65                       constFunctionTypePtr function_type
66                      ) :
67       m_module_name (module_name),
68       m_local_name (local_name),
69       m_type (function_type)
70   {
71     // placeholder, formerly function name
72     m_call->add (YCPVoid ());
73   }
74
75   //! if true, the ruby function is passed the module name
76   virtual bool isMethod () = 0;
77
78   //! called by YEFunction::evaluate
79   virtual YCPValue evaluateCall ()
80   {
81     return YRuby::yRuby()->callInner ( m_module_name,
82                                        m_local_name,
83                                        isMethod (),
84                                        m_call,
85                                        m_type->returnType() );
86   }
87   /**
88   * Attaches a parameter to a given position to the call.
89   * @return false if there was a type mismatch
90   */
91   virtual bool attachParameter (const YCPValue& arg, const int position)
92   {
93     m_call->set (position+1, arg);
94     return true;
95   }
96
97   /**
98    * What type is expected for the next appendParameter (val) ?
99    * (Used when calling from Ruby, to be able to convert from the
100    * simple type system of Ruby to the elaborate type system of YCP)
101    * @return Type::Any if number of parameters exceeded
102    */
103   virtual constTypePtr wantedParameterType () const
104   {
105     // -1 for the function name
106     int params_so_far = m_call->size ()-1;
107     return m_type->parameterType (params_so_far);
108   }
109
110   /**
111    * Appends a parameter to the call.
112    * @return false if there was a type mismatch
113    */
114   virtual bool appendParameter (const YCPValue& arg)
115   {
116     y2internal("Adding parameter to function %s::%s of type %s", m_module_name.c_str(), m_local_name.c_str(), arg->valuetype_str());
117     m_call->add (arg);
118     return true;
119   }
120
121   /**
122    * Signal that we're done adding parameters.
123    * @return false if there was a parameter missing
124    */
125   virtual bool finishParameters ()
126   {
127     return true;
128   }
129
130
131   virtual bool reset ()
132   {
133     m_call = YCPList ();
134     // placeholder, formerly function name
135     m_call->add (YCPVoid ());
136     return true;
137   }
138
139   /**
140    * Something for remote namespaces
141    */
142   virtual string name () const
143   {
144     return m_local_name;
145   }
146 };
147
148 class Y2RubySubCall : public Y2RubyFunctionCall
149 {
150 public:
151   Y2RubySubCall (const string &module_name,
152                  const string &local_name,
153                  constFunctionTypePtr function_type
154                 ) :
155       Y2RubyFunctionCall (module_name, local_name, function_type)
156   {}
157   virtual bool isMethod ()
158   {
159     return false;
160   }
161 };
162
163 class Y2RubyMethodCall : public Y2RubyFunctionCall
164 {
165 public:
166   Y2RubyMethodCall (const string &module_name,
167                     const string &local_name,
168                     constFunctionTypePtr function_type
169                    ) :
170       Y2RubyFunctionCall (module_name, local_name, function_type)
171   {}
172   virtual bool isMethod ()
173   {
174     return true;
175   }
176 };
177
178 \f
179
180 YRubyNamespace::YRubyNamespace (string name)
181     : m_name (name),
182     m_all_methods (true)
183 {
184   y2milestone("Creating namespace for '%s'", name.c_str());
185
186   //y2milestone("loadModule 3.5");
187   //VALUE result = rb_eval_string((require_module).c_str());
188   
189   VALUE module = rb_funcall( rb_mKernel, rb_intern("const_get"), 1, rb_str_new2(name.c_str()) );
190   if (module == Qnil)
191   {
192     y2error ("The Ruby module '%s' is not provided by its rb file", name.c_str());
193     return;
194   }
195   y2milestone("The module '%s' was found", name.c_str());
196
197   // we will perform operator- to determine the module methods
198   VALUE moduleklassmethods = rb_funcall( rb_cModule, rb_intern("methods"), 0);
199   VALUE mymodulemethods = rb_funcall( module, rb_intern("methods"), 0);
200   VALUE methods = rb_funcall( mymodulemethods, rb_intern("-"), 1, moduleklassmethods );
201       
202   if (methods == Qnil)
203   {
204     y2error ("Can't see methods in module '%s'", name.c_str());
205     return;
206   }
207   
208   int i;
209   for(i = 0; i < RARRAY(methods)->len; i++)
210   {
211     VALUE current = RARRAY(methods)->ptr[i];
212     y2milestone("New method: '%s'", RSTRING(current)->ptr);
213     
214     constTypePtr sym_tp = Type::Unspec;
215     //sym_tp = parseTypeinfo (*sym_ti)
216     if (sym_tp->isError ())
217     {
218       y2error ("Cannot parse $TYPEINFO{%s}", RSTRING(current)->ptr);
219       continue;
220     }
221     if (sym_tp->isUnspec ())
222     {
223       //sym_tp = new FunctionType (Type::Any, new FunctionType(Type::Any) );
224       // figure out arity.
225       y2milestone("1.");
226       Check_Type(module,T_MODULE);
227       VALUE methodobj = rb_funcall( module, rb_intern("method"), 1, current );
228       //VALUE methodobj = rb_funcall( module, rb_intern("send"), 2, rb_str_new2("method"), current );
229       if ( methodobj == Qnil )
230       {
231         y2error ("Cannot access method object '%s'", RSTRING(current)->ptr);
232         continue;
233       }
234       y2milestone("2.");
235       string signature = "any( ";
236       VALUE rbarity = rb_funcall( methodobj, rb_intern("arity"), 0);
237       y2milestone("3.");
238       int arity = NUM2INT(rbarity);
239       for ( int k=0; k < arity; ++k )
240       {
241         signature += "any";
242         if ( k < (arity - 1) )
243             signature += ",";
244       }
245       signature += ")";
246       y2internal("going to parse signature: '%s'", signature.c_str());
247       sym_tp = Type::fromSignature(signature);
248     }
249     
250     constFunctionTypePtr fun_tp = (constFunctionTypePtr) sym_tp;
251
252     // symbol entry for the function
253     SymbolEntry *fun_se = new SymbolEntry ( this,
254                                             i,// position. arbitrary numbering. must stay consistent when?
255                                             RSTRING(current)->ptr, // passed to Ustring, no need to strdup
256                                             SymbolEntry::c_function,
257                                             sym_tp);
258     fun_se->setGlobal (true);
259     // enter it to the symbol table
260     enterSymbol (fun_se, 0);
261     y2milestone("method: '%s' added", RSTRING(current)->ptr);
262     y2milestone("%s", symbolsToString().c_str());
263   }
264 }
265
266 YRubyNamespace::~YRubyNamespace ()
267 {}
268
269 const string YRubyNamespace::filename () const
270 {
271   // TODO improve
272   return ".../" + m_name;
273 }
274
275 // this is for error reporting only?
276 string YRubyNamespace::toString () const
277 {
278   y2error ("TODO");
279   return "{\n"
280          "/* this namespace is provided in Ruby */\n"
281          "}\n";
282 }
283
284 // called when running and the import statement is encountered
285 // does initialization of variables
286 // constructor is handled separately after this
287 YCPValue YRubyNamespace::evaluate (bool cse)
288 {
289   // so we don't need to do anything
290   y2debug ("Doing nothing");
291   return YCPNull ();
292 }
293
294 // It seems that this is the standard implementation. why would we
295 // ever want it to be different?
296 Y2Function* YRubyNamespace::createFunctionCall (const string name, constFunctionTypePtr required_type)
297 {
298   y2debug ("Creating function call for %s", name.c_str ());
299   TableEntry *func_te = table ()->find (name.c_str (), SymbolEntry::c_function);
300   if (func_te)
301   {
302     constTypePtr t = required_type ? required_type : (constFunctionTypePtr)func_te->sentry()->type ();
303     if (m_all_methods)
304     {
305       return new Y2RubyMethodCall (m_name, name, t);
306     }
307     else
308     {
309       return new Y2RubySubCall (m_name, name, t);
310     }
311   }
312   y2error ("No such function %s", name.c_str ());
313   return NULL;
314 }